### Basic random effects estimator for survey biomass smoothing in ADMB
### read in your survey estimates and CV into temp and tempcv however you want...
### start year and end year are your predictions
#### yrs_srv are your survey observation years
#### Example is "roughly" rougheye biomass estimates in the GOA
## Set working directory to location of script and "re.exe"
### use this if your default path is My Documents
#setwd<-path.expand("~/SimpleRE")
### Otherwise set path to working directory
### get the data
temp<-c(5091.1, 43680.7, 44836.6, 61862.6, 45913.1, 39559.5, 43202.1, 47862.3, 59880.1, 50773.6, 50000, 44115.4, 27580.5, 35000)
tempcv<-rep(0.2,14)
styr <-1984
endyr <-2016
yrs_srv<-c(1984,1987,1990,1993,1996,1999,2001,2003,2005,2007,2009,2011,2013,2015)
### assign variables and write dat file
nobs<-length(yrs_srv)
srv_est<-temp
srv_cv<-tempcv
cat(styr,"\n",endyr,"\n",nobs,"\n",yrs_srv,"\n",srv_est,"\n",srv_cv,"\n",sep=" ",file="re.dat")
### run compiled ADMB model
system("re.exe")
## read the stuf back in 
srv_re<-scan(file="rwout.rep",nlines=1,skip=11)
srv_recv<-scan(file="rwout.rep",nlines=1,skip=21)
## make a data frame of results
ranests<-data.frame(rbind(srv_re,srv_recv))
rownames(ranests)<-c("Estimate","CV")
all_yrs<-seq(styr,endyr)
colnames(ranests)<-all_yrs
ranests
## write them to current direcotry
write.csv(ranests, "ranests.csv")


  #include <admodel.h>
  #undef REPORT
  #define write_R(object) mysum << #object "\n" << object << endl;
  ofstream mysum("rwout.rep");
  adstring sppname;
#include <admodel.h>
#include <contrib.h>

#include <df1b2fun.h>

#include <adrndeff.h>

  extern "C"  {
    void ad_boundf(int i);
  }
#include <re.htp>

  df1b2_parameters * df1b2_parameters::df1b2_parameters_ptr=0;
  model_parameters * model_parameters::model_parameters_ptr=0;
model_data::model_data(int argc,char * argv[]) : ad_comm(argc,argv)
{
  pad_evalout = new ofstream("evalout.prj");;
  styr.allocate("styr");
  endyr.allocate("endyr");
  yrs.allocate(styr,endyr);
 yrs.fill_seqadd(styr,1);
  num_indx.allocate("num_indx");
  n_PE.allocate("n_PE");
  PE_vec.allocate(1,num_indx,"PE_vec");
  nobs.allocate("nobs");
  yrs_srv.allocate(1,nobs,"yrs_srv");
  srv_est.allocate(1,nobs,1,num_indx,"srv_est");
  srv_cv.allocate(1,nobs,1,num_indx,"srv_cv");
  srv_sd.allocate(1,nobs,1,num_indx);
 if (mean(srv_cv)>5) srv_cv = elem_div(srv_cv,srv_est+0.0001);
 srv_sd = elem_prod(srv_cv,srv_cv)+1;
 srv_sd = sqrt(log(srv_sd));
  yvar.allocate(1,nobs,1,num_indx);
  yconst.allocate(1,nobs,1,num_indx);
 yvar = elem_prod(srv_sd,srv_sd);
 yconst = log(2.0*M_PI*yvar);
}

model_parameters::model_parameters(int sz,int argc,char * argv[]) : 
 model_data(argc,argv) , function_minimizer(sz)
{
  model_parameters_ptr=this;
  initializationfunction();
  logSdLam.allocate(1,n_PE,-5,2,"logSdLam");
  biomsd.allocate(styr,endyr,1,num_indx,"biomsd");
  biomA.allocate(styr,endyr,1,num_indx,"biomA");
  biom.allocate(styr,endyr,1,num_indx,"biom");
  Like.allocate("Like");
  prior_function_value.allocate("prior_function_value");
  likelihood_function_value.allocate("likelihood_function_value");
  jnll.allocate("jnll");  /* ADOBJECTIVEFUNCTION */
}
void model_parameters::userfunction(void)
{
  jnll =0.0;
  ofstream& evalout= *pad_evalout;
  jnll=0.0;
  for(int j=1; j<=num_indx; ++j)
  {
  for(int i=styr+1; i<=endyr; ++i)
  {
    step(biom(i-1,j),biom(i,j),logSdLam(PE_vec(j)));
  }
  for(int i=1; i<=nobs; ++i)
  {
    if(srv_est(i,j)>-1) obs(biom(yrs_srv(i),j),i,j);
  }
  }
  if (sd_phase()) 
  {
    biomA = exp(biom);
    biomsd = biom;
  }
  if (mceval_phase()){
   evalout<<logSdLam<<" "<<jnll<<endl;}
}

void SEPFUN1  model_parameters::step(const dvariable& biom1, const dvariable& biom2, const dvariable& logSdLam)
{
  begin_df1b2_funnel();
  ofstream& evalout= *pad_evalout;
  dvariable var=exp(2.0*logSdLam);
  jnll+=0.5*(log(2.0*M_PI*var)+square(biom2-biom1)/var);
  end_df1b2_funnel();
}

void SEPFUN1  model_parameters::obs(const dvariable& biom, int i, int j)
{
  begin_df1b2_funnel();
  ofstream& evalout= *pad_evalout;
  jnll+=0.5*(yconst(i,j) + square(biom-log(srv_est(i,j)+0.0001))/yvar(i,j));
  end_df1b2_funnel();
}

void model_parameters::report(const dvector& gradients)
{
 adstring ad_tmp=initial_params::get_reportfile_name();
  ofstream report((char*)(adprogram_name + ad_tmp));
  if (!report)
  {
    cerr << "error trying to open report file"  << adprogram_name << ".rep";
    return;
  }
  biomsd = biom;
  Like = jnll;
  report << srv_sd <<endl;
}

void model_parameters::final_calcs()
{
  dvar_vector srv_est_TOT = rowsum(srv_est);
  dvar_vector biom_TOT = rowsum(biomA);
  dvar_vector SD_numer = rowsum(elem_prod(exp(2*biomsd+square(biomsd.sd)),(exp(square(biomsd.sd))-1)));
  dvar_vector SD_denom = square(rowsum(exp(biomsd+0.5*square(biomsd.sd))));
  dvar_vector SD_biom_TOT = sqrt(log(elem_div(SD_numer,SD_denom)+1));
  dvar_vector biom_TOT_UCI = exp(log(biom_TOT)+1.96*SD_biom_TOT);
  dvar_vector biom_TOT_LCI = exp(log(biom_TOT)-1.96*SD_biom_TOT);
  dvar_matrix UCI = exp(biomsd+1.96*biomsd.sd);
  dvar_matrix LCI = exp(biomsd-1.96*biomsd.sd);
  write_R(yrs_srv);
  write_R(srv_est_TOT);
  write_R(yrs);
  write_R(biom_TOT);
  write_R(SD_biom_TOT);
  write_R(biom_TOT_UCI);
  write_R(biom_TOT_LCI);
  write_R(yrs_srv);
  write_R(srv_est);
  write_R(srv_sd);
  write_R(yrs);
  write_R(LCI);
  write_R(biomA);
  write_R(UCI);
  write_R(biomsd);
  write_R(biomsd.sd);
  mysum.close();
}
  long int arrmblsize=0;

int main(int argc,char * argv[])
{
  ad_set_new_handler();
  ad_exit=&ad_boundf;
  gradient_structure::set_MAX_NVAR_OFFSET(777000);
  arrmblsize = 777000;
    gradient_structure::set_NO_DERIVATIVES();
    gradient_structure::set_YES_SAVE_VARIABLES_VALUES();
      if (!arrmblsize) arrmblsize=150000;
    df1b2variable::noallocate=1;
    df1b2_parameters mp(arrmblsize,argc,argv);
    mp.iprint=10;

    function_minimizer::random_effects_flag=1;
    df1b2variable::noallocate=0;
    mp.preliminary_calculations();
    initial_df1b2params::separable_flag=1;
    mp.computations(argc,argv);
    return 0;
}

extern "C"  {
  void ad_boundf(int i)
  {
    /* so we can stop here */
    exit(i);
  }
}

void model_parameters::preliminary_calculations(void){
  #if defined(USE_ADPVM)

  admaster_slave_variable_interface(*this);

  #endif

}

model_data::~model_data()
{}

model_parameters::~model_parameters()
{
  delete pad_evalout;
  pad_evalout = NULL;
}

void model_parameters::set_runtime(void){}

#ifdef _BORLANDC_
  extern unsigned _stklen=10000U;
#endif


#ifdef __ZTC__
  extern unsigned int _stack=10000U;
#endif

void df1b2_parameters::user_function(void)
{
  jnll =0.0;
  ofstream& evalout= *pad_evalout;
  jnll=0.0;
  for(int j=1; j<=num_indx; ++j)
  {
  for(int i=styr+1; i<=endyr; ++i)
  {
    step(biom(i-1,j),biom(i,j),logSdLam(PE_vec(j)));
  }
  for(int i=1; i<=nobs; ++i)
  {
    if(srv_est(i,j)>-1) obs(biom(yrs_srv(i),j),i,j);
  }
  }
  if (sd_phase()) 
  {
    biomA = exp(biom);
    biomsd = biom;
  }
  if (mceval_phase()){
   evalout<<logSdLam<<" "<<jnll<<endl;}
}

void   df1b2_pre_parameters::step(const funnel_init_df1b2variable& biom1, const funnel_init_df1b2variable& biom2, const funnel_init_df1b2variable& logSdLam)
{
  begin_df1b2_funnel();
  ofstream& evalout= *pad_evalout;
  df1b2variable var=exp(2.0*logSdLam);
  jnll+=0.5*(log(2.0*M_PI*var)+square(biom2-biom1)/var);
  end_df1b2_funnel();
}

void   df1b2_pre_parameters::obs(const funnel_init_df1b2variable& biom, int i, int j)
{
  begin_df1b2_funnel();
  ofstream& evalout= *pad_evalout;
  jnll+=0.5*(yconst(i,j) + square(biom-log(srv_est(i,j)+0.0001))/yvar(i,j));
  end_df1b2_funnel();
}
   
void df1b2_pre_parameters::setup_quadprior_calcs(void) 
{ 
  df1b2_gradlist::set_no_derivatives(); 
  quadratic_prior::in_qp_calculations=1; 
}  
  
void df1b2_pre_parameters::begin_df1b2_funnel(void) 
{ 
  (*re_objective_function_value::pobjfun)=0; 
  other_separable_stuff_begin(); 
  f1b2gradlist->reset();  
  if (!quadratic_prior::in_qp_calculations) 
  { 
    df1b2_gradlist::set_yes_derivatives();  
  } 
  funnel_init_var::allocate_all();  
}  
 
void df1b2_pre_parameters::end_df1b2_funnel(void) 
{  
  lapprox->do_separable_stuff(); 
  other_separable_stuff_end(); 
} 
  
void model_parameters::begin_df1b2_funnel(void) 
{ 
  if (lapprox)  
  {  
    {  
      begin_funnel_stuff();  
    }  
  }  
}  
 
void model_parameters::end_df1b2_funnel(void) 
{  
  if (lapprox)  
  {  
    end_df1b2_funnel_stuff();  
  }  
} 

void df1b2_parameters::allocate(void) 
{
  logSdLam.allocate(1,n_PE,-5,2,"logSdLam");
  biomsd.allocate(styr,endyr,1,num_indx,"biomsd");
  biomA.allocate(styr,endyr,1,num_indx,"biomA");
  biom.allocate(styr,endyr,1,num_indx,"biom");
  Like.allocate("Like");
  prior_function_value.allocate("prior_function_value");
  likelihood_function_value.allocate("likelihood_function_value");
  jnll.allocate("jnll");  /* ADOBJECTIVEFUNCTION */
}


#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Testing Thorson Data Import
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 3.28.17
#
#Purpose: 
#
#
#==================================================================================================
#NOTES:
#
#==================================================================================================
require(TMB)
require(VAST)
require(FishData)

source('R/load-RACE-data.r')
source('R/create-Data-Geostat.r')

species.codes <- 30060
species.name <- "Sebastes alutus"

working.dir <- getwd()

#============================================================================
# USING FISHSTAT PACKAGE




fd.nr <- download_catch_rates(survey='Aleutian_Islands', add_zeros=TRUE,
                                species_set=species.name)

head(fd.nr)

# fd <- na.omit(fd.nr)
# dim(fd)
# fd.ebs <- download_catch_rates(survey='Eastern_Bering_Sea', add_zeros=FALSE,
#                                species_set='', localdir=paste0(working.dir,'/Data'))
# 
# 
# 
# fd.ai <- download_catch_rates(survey='Aleutian_Islands', add_zeros=FALSE,
#                              species_set=species.codes, localdir=paste0(working.dir,'/Data'))
# 
# fd.goa <- download_catch_rates(survey='Gulf_of_Alaska', add_zeros=FALSE,
#                                species_set=species.codes, localdir=paste0(working.dir,'/Data'))



#============================================================================
# USING ALTERNATIVE FUNCTIONS



c.nr <- create_Data_Geostat(species.codes=species.codes, lat_lon.def='start', survey='AI')

dim(fd.nr)
dim(c.nr)
par(mfrow=c(2,1))
hist(fd.nr$Lat)
hist(c.nr$Lat)

hist(fd.nr$Long)
hist(c.nr$Lon)

#Do some comparative plots
par(mfrow=c(2,1))
hist(fd.nr$Wt, ylab='FishData')
hist(c.nr$Catch_KG)



summary(fd.nr$Wt)
summary(c.nr$Catch_KG)
summary(c.nr$Catch_KG*(0.01/c.nr$AreaSwept_km2))









Extrapolation_List  <- SpatialDeltaGLMM::Prepare_Extrapolation_Data_Fn(Region="Gulf_of_Alaska", strata.limits=strata.limits)
names(Extrapolation_List)







#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Example Script
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 3.28.17
#
#Purpose: Example of How to Use helper functions
#
#
#==================================================================================================
#NOTES:
#
#==================================================================================================
 source("R/create-VAST-input.r")
 source("R/create-Data-Geostat.r")
 source("R/load-RACE-data.r")
 source("R/plot-VAST-output.r")
 source("R/cleanup-VAST-file.r")
 
require(VAST)
require(TMB)

#=======================================================================
##### SETUP INPUT DATA #####
working.dir <- getwd()
#Generate a dataset
species.codes <- 30420#21740#10110 #21740# 21740 #c(30420) #Rockfish
combineSpecies <- FALSE

lat_lon.def <- "start"

survey <- "GOA"
#"EBS_SHELF"
#"AI"

#SPATIAL SETTINGS
Method <- c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km <- 25
n_x <- c(100, 250, 500, 1000, 2000)[1] # Number of stations
Kmeans_Config <- list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("All_areas"))


#DERIVED OBJECTS
Version <-  "VAST_v2_8_0"
###########################
trial.file <- paste0(getwd(),"/examples/VAST_output/")
dir.create(trial.file)



#MODEL SETTINGS
FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

DateFile <- paste0(trial.file,survey,"_",species.codes," n_x_",n_x," Rho_",
                   RhoConfig[1],RhoConfig[2],RhoConfig[3],RhoConfig[4],"/")

ObsModel = c(1, 0) #Lognormal
# ObsModel = c(2, 0) #Gamma
# ObsModel = c(1, 1) #Poisson-Process Link function approximating Tweedie distribution

#SPECIFY OUTPUTS
Options = c(SD_site_density = 0, SD_site_logdensity = 0,
            Calculate_Range = 1, Calculate_evenness = 0, Calculate_effective_area = 1,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)

bias.correct <- FALSE

#=======================================================================
##### READ IN DATA AND BUILD vAST INPUT #####




VAST_input <- create_VAST_input(species.codes=species.codes, combineSpecies=combineSpecies, 
                                     lat_lon.def=lat_lon.def, save.Record=TRUE,
                                     Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                     Kmeans_Config=Kmeans_Config,
                                     strata.limits=NULL, survey=survey,
                                     DateFile=DateFile,
                                     FieldConfig=FieldConfig, RhoConfig=RhoConfig, 
                                     OverdispersionConfig=OverdispersionConfig,
                                     ObsModel=ObsModel, Options=Options, Version=Version)

#Unpack
TmbData <- VAST_input$TmbData
Data_Geostat <- VAST_input$Data_Geostat
Spatial_List <- VAST_input$Spatial_List
Extrapolation_List <- VAST_input$Extrapolation_List #Becomes zeros for non-GOA
# head(Extrapolation_List$a_el)
# head(Extrapolation_List$Area_km2_x)
# head(Extrapolation_List$Data_Extrap)

#=======================================================================
##### RUN VAST #####



#Build TMB Object
#  Compilation may take some time
TmbList <- VAST::Build_TMB_Fn(TmbData = TmbData, RunDir = DateFile,
                                Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                                Method = Method)
Obj <- TmbList[["Obj"]]


Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
                          upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
                          bias.correct = bias.correct)

#Save output
Report = Obj$report()
Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
save(Save, file=paste0(DateFile,"Save.RData"))

#========================================================================
##### DIAGNOSTIC AND PREDICTION PLOTS #####
plot_VAST_output(Opt, Report, DateFile, survey, TmbData, Data_Geostat, Extrapolation_List, Spatial_List)

#========================================================================
##### CLEAN UP MODEL FILES #####
# cleanup_VAST_file(DateFile, Version=Version)

# 
# 
# #========================================================================
# ##### DIAGNOSTIC PLOTS #####
# 
# #Plot spatial distribution of data
# SpatialDeltaGLMM::Plot_data_and_knots(Extrapolation_List = Extrapolation_List,
#                                       Spatial_List = Spatial_List, Data_Geostat = Data_Geostat,
#                                       PlotDir = DateFile)
# 
# #Diagnostics for Encounter Probability
# #  "Diag--Encounter_prob"
# Enc_prob = SpatialDeltaGLMM::Check_encounter_prob(Report = Report,
#                                                   Data_Geostat = Data_Geostat,
#                                                   DirName = DateFile)
# 
# #Diagnostics for positive-catch-rate component
# Q = SpatialDeltaGLMM::QQ_Fn(TmbData = TmbData, Report = Report,
#                             FileName_PP = paste0(DateFile, "Posterior_Predictive.jpg"),
#                             FileName_Phist = paste0(DateFile, "Posterior_Predictive-Histogram.jpg"),
#                             FileName_QQ = paste0(DateFile, "Q-Q_plot.jpg"),
#                             FileName_Qhist = paste0(DateFile, "Q-Q_hist.jpg"))
# 
# 
# #Diagnostics for plotting residuals on a map
# 
# 
# MapDetails_List = SpatialDeltaGLMM::MapDetails_Fn( "Region"=Region,
#                                                    "NN_Extrap"=Spatial_List$PolygonList$NN_Extrap,
#                                                    "Extrapolation_List"=Extrapolation_List )
# 
# #Which Years to Include
# Year_Set = seq(min(Data_Geostat[,'Year']),max(Data_Geostat[,'Year']))
# Years2Include = which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))
# 
# #Or just include years with observations
# 
# #Plot Pearson Residuals
# #  Look for spatial patterns-- indication of "overshrinking"
# #  Creates "maps--" files
# SpatialDeltaGLMM:::plot_residuals(Lat_i = Data_Geostat[,"Lat"], Lon_i = Data_Geostat[, "Lon"], TmbData = TmbData,
#                                   Report = Report, Q = Q, savedir = DateFile, MappingDetails = MapDetails_List[["MappingDetails"]],
#                                   PlotDF = MapDetails_List[["PlotDF"]], MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
#                                   Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
#                                   FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
#                                   Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
#                                   Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
#                                   mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8)
# 
# 
# 
# 
# #========================================================================
# ##### MODEL OUTPUT PLOTS #####
# 
# #Direction of "geometric anisotropy"
# SpatialDeltaGLMM::PlotAniso_Fn(FileName = paste0(DateFile,"Aniso.png"),
#                                Report = Report, TmbData = TmbData)
# 
# #Density Surface for Each Year -- "Dens"
# SpatialDeltaGLMM::PlotResultsOnMap_Fn(plot_set = c(3),
#                                       MappingDetails = MapDetails_List[["MappingDetails"]],
#                                       Report = Report, Sdreport = Opt$SD, PlotDF = MapDetails_List[["PlotDF"]],
#                                       MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
#                                       Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
#                                       FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
#                                       Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
#                                       Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
#                                       mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8,
#                                       plot_legend_fig = TRUE)
# 
# 
# 
# #Generate Index of Abundance
# 
# Index = SpatialDeltaGLMM::PlotIndex_Fn(DirName = DateFile,
#                                        TmbData = TmbData, Sdreport = Opt[["SD"]],
#                                        Year_Set = Year_Set,
#                                        Years2Include = Years2Include, 
#                                        use_biascorr = TRUE)
# 
# idx <- Index$Table
# 
# 
# dev.off()
# #Plotting 
# 
# yrs.surv <- Year_Set[Years2Include]
# x.lim <- c(min(yrs.surv), max(yrs.surv))
# up.sd <- idx$Estimate_metric_tons + idx$SD_mt
# low.sd <- idx$Estimate_metric_tons - idx$SD_mt
# y.lim <- c(min(low.sd), max(up.sd))
# 
# loc.yrs <- which(idx$Year %in% yrs.surv)
# 
# 
# plot(x=NULL, y=NULL, xlim=x.lim, ylim=y.lim, ylab='Survey Estimate (metric Tons)', xlab='Year',
#      main='Gulf of Alaska\nNorthern Rockfish Survey Index')
# 
# polygon(x=c(yrs.surv, rev(yrs.surv)), y=c(low.sd[loc.yrs],rev(up.sd[loc.yrs])), col='lightblue', border=FALSE)
# lines(x=yrs.surv, y=idx$Estimate_metric_tons[loc.yrs], col='red')
# points(x=yrs.surv, y=idx$Estimate_metric_tons[loc.yrs], pch=21, bg='red')
# grid(col='black')
# 
# 
# #Center of gravity and range expansion/contraction
# #  For some reason I can't actually change the years to plot 
# SpatialDeltaGLMM::Plot_range_shifts(Report = Report,
#                                     TmbData = TmbData, Sdreport = Opt[["SD"]], Znames = colnames(TmbData$Z_xm),
#                                     PlotDir = DateFile, Year_Set = Year_Set)

setwd(working.dir)

#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Test Alternative Areas/Regions
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 3.28.17
#
#Purpose: Example of How to Use helper functions
#
#
#==================================================================================================
#NOTES:
#
#==================================================================================================
source('R/create-VAST-input.r')
source('R/calc-design-based-index.r')
source('R/create-Data-Geostat.r')
source('R/load-RACE-data.r')

require(VAST)
require(TMB)
require(ggmap)
require(ggplot2)

#=======================================================================
##### SETUP INPUT DATA #####

#Generate a dataset
species.codes <- 21720 #Pacific Cod #c(30420) #Rockfish

#==============================================================================
#TEST: reading RACE data
dat.goa <- load_RACE_data(species.codes=species.codes, survey='GOA')
dim(dat.goa)

dat.ai <- load_RACE_data(species.codes=species.codes, survey='AI')
dim(dat.ai)

dat.ebs <- load_RACE_data(species.codes=species.codes, survey='EBS') #DOESN'T WORK "bs" is not an area in RACE data

dat.shelf <- load_RACE_data(species.codes=species.codes, survey='EBS_SHELF') #Note that race data does not separate EBS and BS
dim(dat.shelf)

dat.slope <- load_RACE_data(species.codes=species.codes, survey='EBS_SLOPE')
dim(dat.slope)

#This should not vary by species because 0 catches have been included
goa.North_Rockfish <- load_RACE_data(species.codes=30420, survey='GOA')
goa.Pac_Cod <- load_RACE_data(species.codes=21720, survey='GOA')

dim(goa.North_Rockfish)==dim(goa.Pac_Cod) #Check!

#Try double extraction
goa_NR_PC <- load_RACE_data(species.codes=c(21720,30420), survey='GOA')
#Should be double long
dim(goa_NR_PC)[1] == 2*dim(goa.North_Rockfish)[1]#Check!

#==============================================================================
#TEST: creating Data_Geostat object
dgs.goa <- create_Data_Geostat(species.codes, lat_lon.def="mean", survey="GOA")
dim(dgs.goa)

dgs.ai <- create_Data_Geostat(species.codes, lat_lon.def="mean", survey="AI")
dim(dgs.ai)

dgs.slope <- create_Data_Geostat(species.codes, lat_lon.def="mean", survey="EBS_SLOPE")
dim(dgs.slope)

dgs.shelf <- create_Data_Geostat(species.codes, lat_lon.def="mean", survey="EBS_SHELF")
dim(dgs.shelf)

#Try multispecies

dgs.shelf.NR_PC <- create_Data_Geostat(species.codes=c(21720,30420), lat_lon.def="mean", survey="EBS_SHELF")
dim(dgs.shelf.NR_PC)
head(dgs.shelf.NR_PC)

#==============================================================================
#TEST: creating VAST input
# This is the only function that takes Region in place of area.
# It gets converted right off the bat.
# 
# create_Data_Geostat() uses "area"
# Prepare_Extrapolation_Data_Fn() uses Region

#NOTE: Might consider changing the input type around cor create VAST input

#==============================================================================
#TEST: Design-based Index Function

#
ggmap()

#Lets get a map
map.input <- dgs.goa



#Left, bottom, right, top
# map.dat <- get_map(location=c(lon = mean(dgs.goa$Lon), lat = mean(dgs.goa$Lat)),
#                      maptype='terrain', source='stamen', crop=FALSE)

#Left, bottom, right, top
map.dat <- get_map(location=c(min(map.input$Lon),min(map.input$Lat),
                              max(map.input$Lon),max(map.input$Lat)),
                   maptype='terrain', source='stamen', crop=FALSE)

map.dat.1 <- get_map(location=c(min(map.input$Lon),min(map.input$Lat),
                                max(map.input$Lon),max(map.input$Lat)),
                     maptype='watercolor', source='stamen', crop=FALSE)

map.dat.2 <- get_map(location=c(min(map.input$Lon),min(map.input$Lat),
                                max(map.input$Lon),max(map.input$Lat)),
                     maptype='toner-lite', source='stamen', crop=FALSE)

map.dat.3 <- get_map(location=c(min(map.input$Lon),min(map.input$Lat),
                                max(map.input$Lon),max(map.input$Lat)),
                     maptype='toner', source='stamen', crop=FALSE)

#DON'T USE
# map.dat.4 <- get_map(location=c(min(map.input$Lon),min(map.input$Lat),
#                                 max(map.input$Lon),max(map.input$Lat)),
#                      maptype='terrain', source='google', crop=FALSE)

ggmap(map.dat) #terrain: staimen
# ggmap(map.dat.4) #terrain: google
ggmap(map.dat.1) #watercolor
ggmap(map.dat.2) #toner-lite
ggmap(map.dat.3) #toner





g <- ggmap(map.dat.2) +
       geom_point(data=map.input, aes(x=Lon, y=Lat, color=Year), alpha=1, size=0.25)

g


#Plot bs
map.input <- dgs.bs

map.dat <- get_map(location=c(min(map.input$Lon),min(map.input$Lat),
                                max(map.input$Lon),max(map.input$Lat)),
                                maptype='toner-lite', source='stamen', crop=FALSE)

g.bs <- ggmap(map.dat) +
          geom_point(data=map.input, aes(x=Lon, y=Lat, color=Year),
                       alpha=1, size=0.25)
g.bs

#Plot goa
map.input <- dgs.goa

map.dat <- get_map(location=c(min(map.input$Lon),min(map.input$Lat),
                              max(map.input$Lon),max(map.input$Lat)),
                   maptype='toner-lite', source='stamen', crop=FALSE)

g.goa <- ggmap(map.dat) +
          geom_point(data=map.input, aes(x=Lon, y=Lat, color=Year),
                       alpha=1, size=0.25)
g.goa

#Plot AI
#Plot bs
# map.input <- dgs.ai
# 
# map.dat <- get_map(location=c(179,min(map.input$Lat),
#                               -150,max(map.input$Lat)),
#                    maptype='toner-lite', source='stamen', crop=FALSE)
# 
# g.ai <- ggmap(map.dat) +
#   geom_point(data=map.input, aes(x=Lon, y=Lat, color=Year),
#              alpha=1, size=0.25)
# g.ai

#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Gulf of Alaska Northern Rockfish for 2019 SAFE
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 10.22.19
#
#Purpose: Implementation of VAST for AI Dusky Northern
#
#
#==================================================================================================
#NOTES:
#Memory ProfilingJust


# [1] "bias.correct: FALSE"
# [1] "n_x: 100"
# [1] "START: Tue Oct 22 13:28:36 2019"
# [1] "END: Tue Oct 22 13:31:57 2019"

#==================================================================================================
source("R/create-VAST-input-new.r")
source("R/create-Data-Geostat.r")
source("R/load-RACE-data.r")
source("R/plot-VAST-output.r")
source("R/cleanup-VAST-file.r")
source("R/run-RE-model.r") 

require(VAST)
require(TMB)
require(tidyverse)
require(FishStatsUtils)

#=======================================================================
##### SETUP INPUT DATA #####

#Generate a dataset
species.codes <- c(30420)
combineSpecies <- FALSE

lat_lon.def <- "start"

survey <- "AI"
Region <- "Aleutian_Islands"

bias.correct <- FALSE #TARGET: TRUE

#SPATIAL SETTINGS
Method <- c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km <- 25
n_x <- c(100, 250, 500, 1000, 2000)[1] #TARGET: 3 (500)
Kmeans_Config <- list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("All_areas"))


#DERIVED OBJECTS
Version <-  "VAST_v4_0_0"
###########################
trial.file <- paste0(getwd(),"/examples/Species_Specific_Case_Studies/Test_AI_Northern_rockfish/")

#MODEL SETTINGS
FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

# ObsModel = c(1, 0) #Lognormal
# ObsModel = c(2, 0) #Gamma
ObsModel = c(2, 1) #Poisson-Process Link function approximating Tweedie distribution

#SPECIFY OUTPUTS
Options = c(SD_site_density = 1, SD_site_logdensity = 1,
            Calculate_Range = 0, Calculate_evenness = 0, Calculate_effective_area = 0,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)

# DEFINE SETTINGS
# settings <- make_settings(n_x=n_x, Region=Region, purpose = "index", fine_scale = FALSE,
#                           strata.limits = data.frame(STRATA = "All_areas"), zone = NA,
#                           FieldConfig=FieldConfig, RhoConfig=RhoConfig,
#                           OverdispersionConfig=OverdispersionConfig, ObsModel=ObsModel,
#                           bias.correct=bias.correct,
#                           Options=Options, use_anisotropy=TRUE, 
#                           vars_to_correct="Index_cyl", Version=Version,
#                           treat_nonencounter_as_zero=FALSE)#, n_categories, VamConfig)



DateFile <- paste0(trial.file,"AI Northern rockfish knots_",n_x," bias.correct_", bias.correct, 
                   " Rho_",RhoConfig[1],RhoConfig[2],RhoConfig[3],RhoConfig[4],
                   " ObsModel_",ObsModel[1],ObsModel[2],"/")

# Save options for future records
# Record <- list("Version"=Version,"Method"=Method,"grid_size_km"=grid_size_km,"n_x"=n_x,"FieldConfig"=FieldConfig,
#                "RhoConfig"=RhoConfig,"OverdispersionConfig"=OverdispersionConfig,"ObsModel"=ObsModel,"Region"=Region,
#                "Species_set"=Species_set,"strata.limits"=strata.limits)
# save( Record, file=file.path(DateFile,"Record.RData"))
# capture.output( Record, file=file.path(DateFile,"Record.txt"))

#=======================================================================
##### READ IN DATA AND BUILD vAST INPUT #####
VAST_input <- create_VAST_input_new(species.codes=species.codes, combineSpecies=combineSpecies,
                                    lat_lon.def=lat_lon.def, save.Record=FALSE,
                                    Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                    Kmeans_Config=Kmeans_Config,
                                    strata.limits=strata.limits, survey=survey,
                                    DateFile=DateFile,
                                    FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                    OverdispersionConfig=OverdispersionConfig,
                                    ObsModel=ObsModel, Options=Options, Version=Version)
#Unpack
TmbData <- VAST_input$TmbData
Data_Geostat <- VAST_input$Data_Geostat
Spatial_List <- VAST_input$Spatial_List
Extrapolation_List <- VAST_input$Extrapolation_List #Becomes zeros for non-AI
settings <- VAST_input$settings
MapDetails_List <- VAST_input$MapDetails_List

# head(Extrapolation_List$a_el)
# head(Extrapolation_List$Area_km2_x)
# head(Extrapolation_List$Data_Extrap)

#=======================================================================
##### RUN VAST #####



#Build TMB Object
#  Compilation may take some time
# ERROR HERE
TmbList <- VAST::make_model(TmbData = TmbData, RunDir = DateFile,
                            Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                            Method = Method)
Obj <- TmbList[["Obj"]]

start.time <- date()

#================================================
#TESTING OPTIMIZATION: Original Call

# Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                           upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                           bias.correct = bias.correct)


#================================================
#TESTING OPTIMIZATION: Updated call with nsplit to reduce memory load and 
#                        allow running bias.cor with kt > ~300
# Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                            upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                            bias.correct = bias.correct,
#                            bias.correct.control=list(nsplit=200, split=NULL, sd=FALSE))

#================================================
#TESTING OPTIMIZATION: New Alternative Following Jim's Suggestion
#  Should limit bias correction to single vector of interst: index

# nsplit <- 200
# 
# Opt = TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                            upper = TmbList[["Upper"]], getsd = TRUE, 
#                            savedir = DateFile, bias.correct=bias.correct )

# if(bias.correct==FALSE) {
#   Opt <- TMBhelper::fit_tmb(obj = Obj, lower = TmbList[["Lower"]],
#                             upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                             bias.correct = bias.correct)#, newtonsteps=1)
# }else {
#   #NEW: Only Bias Correct Index
#   Opt <- TMBhelper::fit_tmb(obj=Obj, lower=TmbList[["Lower"]],
#                             upper=TmbList[["Upper"]], getsd=TRUE, savedir=DateFile,
#                             bias.correct=bias.correct, #newtonsteps=1,
#                             bias.correct.control=list(sd=TRUE, #nsplit=200, split=NULL,
#                                                       vars_to_correct="Index_cyl"))
# }

# New Alternative ===========
fit <- fit_model(settings, Lat_i=Data_Geostat[["Lat"]], Lon_i=Data_Geostat[["Lon"]], 
                 t_iz=Data_Geostat[["Year"]], 
                 b_i=Data_Geostat[["Catch_KG"]],
                 a_i=Data_Geostat[["AreaSwept_km2"]], 
                 c_iz = rep(0, nrow(Data_Geostat)),#rep(0,length(b_i)), 
                 v_i = rep(0, nrow(Data_Geostat)),#Data_Geostat[["Vessel"]],
                 working_dir = DateFile, Xconfig_zcp = NULL,
                 covariate_data=NULL, formula = ~0, Q_ik = NULL, newtonsteps = 1,
                 silent = TRUE, run_model = TRUE, test_fit = FALSE)
# Opt <- fit

# Get sdreport for Plotting ======================
# Sdreport <- TMB::sdreport( obj=Obj, par.fixed=Opt$par) #No need to run as 
Sdreport <- Opt$SD

# First SD run 
# h <- optimHess(Opt$par, Obj$fn, Obj$gr)
# SD = sdreport( obj=Obj, par.fixed=Opt$par, hessian.fixed=h )

# Determine indices
# BiasCorrNames = c("Index_cyl")
# Which = which( rownames(summary(SD,"report")) %in% BiasCorrNames )
# Which = split( Which, cut(seq_along(Which), nsplit) )
# Which = Which[sapply(Which,FUN=length)>0]


# Repeat SD with indexing
# SD = sdreport( obj=Obj, par.fixed=Opt$par, hessian.fixed=h, bias.correct=TRUE, bias.correct.control=list(sd=FALSE, split=Which, nsplit=NULL) )

#================================================


end.time <- date()
#Save output
Report = Obj$report()
Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
save(Save, file=paste0(DateFile,"Save.RData"))

#========================================================================
##### DIAGNOSTIC AND PREDICTION PLOTS #####
# Get Index
Year_Set = seq(min(Data_Geostat[,'Year']),max(Data_Geostat[,'Year']))
Years2Include = which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))


# Only works when use fit_model
plot_results(fit=fit, settings = settings, plot_set = 3,
             working_dir = DateFile, year_labels = Year_Set,
             years_to_plot = Years2Include,
             use_biascorr = bias.correct, map_list=MapDetails_List,
             category_names="AI NR", check_residuals = TRUE, projargs = "+proj=longlat",
             n_samples = 100)


# Error in as_mapper(.f, ...) : argument ".f" is missing, with no default
plot_data(Extrapolation_List=Extrapolation_List, Spatial_List=Spatial_List, Data_Geostat=Data_Geostat,
          PlotDir = paste0(DateFile, "/"), Plot1_name = "Data_and_knots.png",
          Plot2_name = "Data_by_year.png", col = rep("red",nrow(Data_Geostat)), cex = 0.01)

# 
# # Diagnostics =====================================
# plot_encounter_diagnostic(Report, Data_Geostat, cutpoints_z = seq(0, 1,length = 21), 
#                           interval_width = 1.96, DirName = paste0(DateFile, "/"),
#                           PlotName = "Diag--Encounter_prob.png")
# 
# Q <- plot_quantile_diagnostic(TmbData=TmbData, Report=Report, DateFile=DateFile, 
#                               save_dir = paste0(DateFile, "/QQ_Fn/"),
#                               FileName_PP = "Posterior_Predictive",
#                               FileName_Phist = "Posterior_Predictive-Histogram",
#                               FileName_QQ = "Q-Q_plot", FileName_Qhist = "Q-Q_hist")
# 
# #Plot Pearson Residuals
# #  Look for spatial patterns-- indication of "overshrinking"
# #  Creates "maps--" files - ERROR: Error in as_mapper(.f, ...) : argument ".f" is missing, with no default
# 
# plot_residuals(Lat_i = Data_Geostat[,"Lat"], Lon_i = Data_Geostat[, "Lon"], TmbData = TmbData,
#                Report = Report, Q = Q, savedir = DateFile, MappingDetails = MapDetails_List[["MappingDetails"]],
#                PlotDF = MapDetails_List[["PlotDF"]], MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
#                Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
#                FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
#                Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
#                Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
#                mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8)
# 
# 
# 
# 
# 
# # Predictions =====================================
# 
# Index = SpatialDeltaGLMM::PlotIndex_Fn(DirName = DateFile,
#                                        TmbData = TmbData, Sdreport = Opt[["SD"]],
#                                        Year_Set = Year_Set,
#                                        Years2Include = Years2Include, 
#                                        use_biascorr = bias.correct)
# 
# # Plot shifts in distribution and area occupied
# plot_range_index(Sdreport, Report, TmbData, Year_Set = NULL,
#                  PlotDir = paste0(DateFile, "/"), 
#                  FileName_COG = paste0(DateFile,"/center_of_gravity.png"), 
#                  FileName_Area = paste0(DateFile,"/Area.png"), 
#                  FileName_EffArea = paste0(DateFile,"/Effective_Area.png"), 
#                  Znames = rep("", ncol(TmbData$Z_xm)),
#                  use_biascorr = bias.correct, category_names = NULL, interval_width = 1)
# 
# #Direction of "geometric anisotropy"
# SpatialDeltaGLMM::PlotAniso_Fn(FileName = paste0(DateFile,"Aniso.png"),
#                                Report = Report, TmbData = TmbData)
# 
# # plot_maps(plot_set = 3, MappingDetails, Report, #PlotDF,
# #           Sdreport = NULL, Xlim, Ylim, TmbData, Nknots = n_x,
# #           Panel = "Category", MapSizeRatio = c(`Width(in)` = 4, `Height(in)` = 4),
# #           Res = 200, FileName = paste0(DateFile, "/"), Year_Set = NULL,
# #           Years2Include = NULL, Rescale = FALSE, Rotate = 0,
# #           Format = "png", zone = NA, Cex = 0.01, add = FALSE,
# #           category_names = NULL, textmargin = NULL, pch = NULL,
# #           Legend = list(use = FALSE, x = c(10, 30), y = c(10, 30)),
# #           mfrow = NULL, plot_legend_fig = TRUE)
# #========================================================================
# ##### CLEAN UP MODEL FILES #####
# # cleanup_VAST_file(DateFile, Version=Version)
# 
# print(paste('bias.correct:',bias.correct))
# print(paste('n_x:',n_x))
# print(paste('START:',start.time))
# print(paste('END:',end.time))
# 
# 
# #========================================================================
# ##### APPORTIONMENT #####
# 
# 
# 
# 
# 
# 
# 
# 
# 
# 
# 
# 
# 
# 
# # 
# # 
# # #========================================================================
# # ##### DIAGNOSTIC PLOTS #####
# # 
# # #Plot spatial distribution of data
# # SpatialDeltaGLMM::Plot_data_and_knots(Extrapolation_List = Extrapolation_List,
# #                                       Spatial_List = Spatial_List, Data_Geostat = Data_Geostat,
# #                                       PlotDir = DateFile)
# # 
# # #Diagnostics for Encounter Probability
# # #  "Diag--Encounter_prob"
# # Enc_prob = SpatialDeltaGLMM::Check_encounter_prob(Report = Report,
# #                                                   Data_Geostat = Data_Geostat,
# #                                                   DirName = DateFile)
# # 
# # #Diagnostics for positive-catch-rate component
# # Q = SpatialDeltaGLMM::QQ_Fn(TmbData = TmbData, Report = Report,
# #                             FileName_PP = paste0(DateFile, "Posterior_Predictive.jpg"),
# #                             FileName_Phist = paste0(DateFile, "Posterior_Predictive-Histogram.jpg"),
# #                             FileName_QQ = paste0(DateFile, "Q-Q_plot.jpg"),
# #                             FileName_Qhist = paste0(DateFile, "Q-Q_hist.jpg"))
# # 
# # 
# # #Diagnostics for plotting residuals on a map
# # 
# # 
# # MapDetails_List = SpatialDeltaGLMM::MapDetails_Fn( "Region"=Region,
# #                                                    "NN_Extrap"=Spatial_List$PolygonList$NN_Extrap,
# #                                                    "Extrapolation_List"=Extrapolation_List )
# # 
# # #Which Years to Include
# # Year_Set = seq(min(Data_Geostat[,'Year']),max(Data_Geostat[,'Year']))
# # Years2Include = which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))
# # 
# # #Or just include years with observations
# # 
# # #Plot Pearson Residuals
# # #  Look for spatial patterns-- indication of "overshrinking"
# # #  Creates "maps--" files
# # SpatialDeltaGLMM:::plot_residuals(Lat_i = Data_Geostat[,"Lat"], Lon_i = Data_Geostat[, "Lon"], TmbData = TmbData,
# #                                   Report = Report, Q = Q, savedir = DateFile, MappingDetails = MapDetails_List[["MappingDetails"]],
# #                                   PlotDF = MapDetails_List[["PlotDF"]], MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
# #                                   Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
# #                                   FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
# #                                   Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
# #                                   Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
# #                                   mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8)
# # 
# # 
# # 
# # 
# # #========================================================================
# # ##### MODEL OUTPUT PLOTS #####
# # 
# # #Direction of "geometric anisotropy"
# # SpatialDeltaGLMM::PlotAniso_Fn(FileName = paste0(DateFile,"Aniso.png"),
# #                                Report = Report, TmbData = TmbData)
# # 
# # #Density Surface for Each Year -- "Dens"
# # SpatialDeltaGLMM::PlotResultsOnMap_Fn(plot_set = c(3),
# #                                       MappingDetails = MapDetails_List[["MappingDetails"]],
# #                                       Report = Report, Sdreport = Opt$SD, PlotDF = MapDetails_List[["PlotDF"]],
# #                                       MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
# #                                       Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
# #                                       FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
# #                                       Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
# #                                       Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
# #                                       mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8,
# #                                       plot_legend_fig = TRUE)
# # 
# # 
# # 
# # #Generate Index of Abundance
# # 
# # Index = SpatialDeltaGLMM::PlotIndex_Fn(DirName = DateFile,
# #                                        TmbData = TmbData, Sdreport = Opt[["SD"]],
# #                                        Year_Set = Year_Set,
# #                                        Years2Include = Years2Include, 
# #                                        use_biascorr = TRUE)
# # 
# # idx <- Index$Table
# # 
# # 
# # dev.off()
# # #Plotting 
# # 
# # yrs.surv <- Year_Set[Years2Include]
# # x.lim <- c(min(yrs.surv), max(yrs.surv))
# # up.sd <- idx$Estimate_metric_tons + idx$SD_mt
# # low.sd <- idx$Estimate_metric_tons - idx$SD_mt
# # y.lim <- c(min(low.sd), max(up.sd))
# # 
# # loc.yrs <- which(idx$Year %in% yrs.surv)
# # 
# # 
# # plot(x=NULL, y=NULL, xlim=x.lim, ylim=y.lim, ylab='Survey Estimate (metric Tons)', xlab='Year',
# #      main='Gulf of Alaska\nNorthern Rockfish Survey Index')
# # 
# # polygon(x=c(yrs.surv, rev(yrs.surv)), y=c(low.sd[loc.yrs],rev(up.sd[loc.yrs])), col='lightblue', border=FALSE)
# # lines(x=yrs.surv, y=idx$Estimate_metric_tons[loc.yrs], col='red')
# # points(x=yrs.surv, y=idx$Estimate_metric_tons[loc.yrs], pch=21, bg='red')
# # grid(col='black')
# # 
# # 
# # #Center of gravity and range expansion/contraction
# # #  For some reason I can't actually change the years to plot 
# # SpatialDeltaGLMM::Plot_range_shifts(Report = Report,
# #                                     TmbData = TmbData, Sdreport = Opt[["SD"]], Znames = colnames(TmbData$Z_xm),
# #                                     PlotDir = DateFile, Year_Set = Year_Set)

#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( ObsModel_ez(e_i(i),0)==11 ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( t_iz(i,zt)>=0 & t_iz(i,zt)<n_t ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Responses
      if( ObsModel_ez(c_iz(i,0),1)==0 | ObsModel_ez(c_iz(i,0),1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
          //if( !isNA(c_iz(i,zc)) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel_ez(e_i(i),0)==0 | ObsModel_ez(e_i(i),0)==1 | ObsModel_ez(e_i(i),0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
          if(ObsModel_ez(e_i(i),0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
          if(ObsModel_ez(e_i(i),0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel_ez(e_i(i),0)==4 | ObsModel_ez(e_i(i),0)==5 | ObsModel_ez(e_i(i),0)==6 | ObsModel_ez(e_i(i),0)==7 | ObsModel_ez(e_i(i),0)==9 | ObsModel_ez(e_i(i),0)==11){
        if(ObsModel_ez(e_i(i),0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        }
        if(ObsModel_ez(e_i(i),0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel_ez(e_i(i),0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel_ez(c,1)==0 | ObsModel_ez(c,1)==3 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( ObsModel_ez(e_i(i),0)==11 ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( t_iz(i,zt)>=0 & t_iz(i,zt)<n_t ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Responses
      if( ObsModel_ez(c_iz(i,0),1)==0 | ObsModel_ez(c_iz(i,0),1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
          //if( !isNA(c_iz(i,zc)) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel_ez(e_i(i),0)==0 | ObsModel_ez(e_i(i),0)==1 | ObsModel_ez(e_i(i),0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
          if(ObsModel_ez(e_i(i),0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
          if(ObsModel_ez(e_i(i),0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel_ez(e_i(i),0)==4 | ObsModel_ez(e_i(i),0)==5 | ObsModel_ez(e_i(i),0)==6 | ObsModel_ez(e_i(i),0)==7 | ObsModel_ez(e_i(i),0)==9 | ObsModel_ez(e_i(i),0)==11){
        if(ObsModel_ez(e_i(i),0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        }
        if(ObsModel_ez(e_i(i),0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel_ez(e_i(i),0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel_ez(c,1)==0 | ObsModel_ez(c,1)==3 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vf.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        SCALE( AR1(L_z(1)), exp(L_z(0)) ).simulate(Tmp_c);
        eta_vf.row(v) = Tmp_c;
      }
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        eta_vf(v,f) = rnorm( Type(0.0), Type(1.0) );
      }
    }}
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int timing, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  vector<Type> gmrf_s(n_s);
  matrix<Type> Cov_cc(n_c,n_c);
  array<Type> diff_gmrf_sc(n_s, n_c); // Requires an array
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(c) = gmrf_s + gmrf_mean_sf.col(c);
      }
      // Rescale
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    // Simulate new values when using obj.simulate()
    if(isDouble<Type>::value && of->do_simulate) {
      SEPARABLE( AR1(L_z(1)), gmrf_Q ).simulate(gmrf_input_sf);
      gmrf_input_sf += gmrf_input_sf;
    }
    // Rescale
    logtau = L_z(0) - logkappa;  //
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    // PDF if density-dependence/interactions occurs prior to correlated dynamics
    if( timing==0 ){
      for( int f=0; f<n_f; f++ ){
        // Calculate likelihood
        jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
        // Simulate new values when using obj.simulate()
        if(isDouble<Type>::value && of->do_simulate) {
          gmrf_Q.simulate(gmrf_s);
          gmrf_input_sf.col(f) = gmrf_s + gmrf_mean_sf.col(f);
        }
      }
      // Rescale
      matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
    }
    // PDF if density-dependence/interactions occurs after correlated dynamics (Only makes sense if n_f == n_c)
    if( timing==1 ){
      // Calculate difference without rescaling
      gmrf_sc = gmrf_input_sf.matrix();
      for( int s=0; s<n_s; s++){
      for( int c=0; c<n_c; c++){
        diff_gmrf_sc(s,c) = gmrf_sc(s,c) - gmrf_mean_sf(s,c);
      }}
      // Calculate likelihood
      matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
      Cov_cc = L_cf * L_cf.transpose();
      jnll_pointer += SEPARABLE(MVNORM(Cov_cc), gmrf_Q)( diff_gmrf_sc );
      gmrf_sc = gmrf_sc / exp(logtau);
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        SEPARABLE(MVNORM(Cov_cc), gmrf_Q).simulate( diff_gmrf_sc );
        gmrf_sc = gmrf_mean_sf + diff_gmrf_sc/exp(logtau);
      }
    }
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j;
  //Type pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// Calculate B_cc
template<class Type>
matrix<Type> calculate_B( int method, int n_f, int n_r, matrix<Type> Chi_fr, matrix<Type> Psi_fr ){
  matrix<Type> B_ff( n_f, n_f );
  matrix<Type> BplusI_ff( n_f, n_f );
  matrix<Type> Chi_rf = Chi_fr.transpose();
  matrix<Type> Psi_rf = Psi_fr.transpose();
  matrix<Type> Identity_ff( n_f, n_f );
  Identity_ff.setIdentity();

  // No interactions (default)
  if( method==0 ){
    B_ff.setZero();
  }
  // Simple co-integration -- complex unbounded eigenvalues
  if( method==1 ){
    B_ff = Chi_fr * Psi_rf;
  }
  // Real eigenvalues
  if( method==2 ){
    matrix<Type> Chi_ff( n_f, n_f );
    Chi_ff = Identity_ff;
    // Make Chi_ff
    vector<Type> colnorm_r( n_r );
    colnorm_r.setZero();
    for(int f=0; f<n_f; f++){
    for(int r=0; r<n_r; r++){
      Chi_ff(f,r) = Chi_fr(f,r);
      colnorm_r(r) += pow( Chi_ff(f,r), 2 );
    }}
    for(int f=0; f<n_f; f++){
    for(int r=0; r<n_r; r++){
      Chi_ff(f,r) /= pow( colnorm_r(r), 0.5 );
    }}
    // Make Psi_ff
    matrix<Type> Psi_ff( n_f, n_f );
    Psi_ff = Identity_ff;
    for(int f=n_r; f<n_f; f++){
    for(int r=0; r<n_r; r++){
      Psi_ff(f,r) = Psi_fr(f,r);
    }}
    // Make L_ff
    matrix<Type> L_ff(n_f, n_f);
    L_ff.setZero();
    for(int r=0; r<n_r; r++){
      L_ff(r,r) = Psi_fr(r,r);
    }
    // Build B_ff
    matrix<Type> invChi_ff = atomic::matinv( Chi_ff );
    matrix<Type> trans_Psi_ff = Psi_ff.transpose();
    matrix<Type> trans_invPsi_ff = atomic::matinv( Psi_ff ).transpose();
    B_ff = Chi_ff * trans_Psi_ff;
    B_ff = B_ff * L_ff;
    B_ff = B_ff * trans_invPsi_ff;
    B_ff = B_ff * invChi_ff;
    // Penalize colnorm_r
    //if( Options_vec(0)==3 ) jnll_comp(3) += PenMult_z(1) * ( log(colnorm_r)*log(colnorm_r) ).sum();
  }
  // Complex bounded eigenvalues
  if( method==3 ){
    BplusI_ff = Chi_fr * Psi_rf + Identity_ff;
    // Extract eigenvalues
    vector< std::complex<Type> > eigenvalues_B_ff = B_ff.eigenvalues();
    vector<Type> real_eigenvalues_B_ff = eigenvalues_B_ff.real();
    vector<Type> imag_eigenvalues_B_ff = eigenvalues_B_ff.imag();
    vector<Type> mod_eigenvalues_B_ff( n_f );
    // Calculate maximum eigenvalues
    Type MaxEigen = 1;
    for(int f=0; f<n_f; f++){
      mod_eigenvalues_B_ff(f) = pow( pow(real_eigenvalues_B_ff(f),2) + pow(imag_eigenvalues_B_ff(f),2), 0.5 );
      MaxEigen = CppAD::CondExpGt(mod_eigenvalues_B_ff(f), MaxEigen, mod_eigenvalues_B_ff(f), MaxEigen);
    }
    // Rescale interaction matrix
    BplusI_ff = BplusI_ff / MaxEigen;
    B_ff = BplusI_ff - Identity_ff;
    //jnll_comp(3) += PenMult_z(0) * CppAD::CondExpGe( MaxEigen, Type(1.0), pow(MaxEigen-Type(1.0),2), Type(0.0) );
  }
  return B_ff;
}

// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on beta1 (year intercepts for 1st linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- AR1 on beta2 (year intercepts for 2nd linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(VamConfig);
  // Slot 0 -- method for calculating n_c-by-n_c interaction matrix, B_ff
  // Slot 1 -- rank of interaction matrix B_ff
  // Current implementation only makes sense when (1) intercepts are constant among years; (2) using a Poisson-link delta model; (3) n_f=n_c for spatio-temporal variation; (4) starts near equilibrium manifold
  DATA_INTEGER(include_data);   // Always use TRUE except for internal usage to extract GRMF normalization when turn off GMRF normalization in CPP
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SE for Index_xctl
  // Slot 1: Calculate SE for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: Calculate SE for D_i (expected density for every observation)
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  // Slot 8: Calculate proportions and SE
  // Slot 9: Include normalization in GMRF PDF
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  PARAMETER_MATRIX(Chi_fr);   // error correction responses
  PARAMETER_MATRIX(Psi_fr);   // error correction loadings, B_ff = Chi_fr %*% t(Psi_fr)

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,t,c;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( (Options_vec(7)==0) & (Options_vec(0)==0) ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( (Options_vec(7)==0) & (Options_vec(0)==1) ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF( Q1, bool(Options(9)) );
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), VamConfig(2), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0), this);
  // Define interaction matrix for Epsilon1
  matrix<Type> B_ff( n_f, n_f );
  B_ff = calculate_B( VamConfig(0), n_f, VamConfig(1), Chi_fr, Psi_fr );
  // PDF for Epsilon1
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), VamConfig(2), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
    if(t>=1){
      // Prediction for spatio-temporal component
      // Default, and also necessary whenever VamConfig(2)==1 & n_f!=n_c
      if( (VamConfig(0)==0) | ((n_f!=n_c) & (VamConfig(2)==1)) ){
        // If no interactions, then just autoregressive for factors
        Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      }else{
        // Impact of interactions, B_ff
        Epsilonmean1_sf.setZero();
        for(int s=0; s<n_s; s++){
        for(int f1=0; f1<n_f; f1++){
        for(int f2=0; f2<n_f; f2++){
          if( VamConfig(2)==0 ){
            Epsilonmean1_sf(s,f1) += B_ff(f1,f2) * Epsiloninput1_sft(s,f2,t-1);
            if( f1==f2 ) Epsilonmean1_sf(s,f1) += Epsilon_rho1 * Epsiloninput1_sft(s,f2,t-1);
          }
          if( VamConfig(2)==1 ){
            Epsilonmean1_sf(s,f1) += B_ff(f1,f2) * Epsilon1_sct(s,f2,t-1);
            if( f1==f2 ) Epsilonmean1_sf(s,f1) += Epsilon_rho1 * Epsilon1_sct(s,f2,t-1);
          }
        }}}
      }
      // Hyperdistribution for spatio-temporal component
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), VamConfig(2), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF( Q2, bool(Options(9)) );
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), VamConfig(2), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2), this);
  // PDF for Epsilon1
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), VamConfig(2), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
    if(t>=1){
      // Prediction for spatio-temporal component
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      // Hyperdistribution for spatio-temporal component
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), VamConfig(2), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
  }

  // Normalization of GMRFs to normalize during outer-optimization step in R
  Type jnll_GMRF = jnll_comp(0) + jnll_comp(1) + jnll_comp(2) + jnll_comp(3);
  if( include_data == 0 ){
    return( jnll_GMRF );
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4), this );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5), this );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta1_ct(c,t) = rnorm( Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1) );
      }
    }}
  }
  if( Options_vec(3)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta2_ct(c,t) = rnorm( Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2) );
      }
    }}
  }

  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( (ObsModel_ez(e_i(i),0)==11) | (ObsModel_ez(e_i(i),0)==14) ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        delta_i(i) = rnorm( Type(0.0), Type(1.0) );
      }
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  LogProb1_i.setZero();
  LogProb2_i.setZero();
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( (t_iz(i,zt)>=0) & (t_iz(i,zt)<n_t) ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Apply link function to calculate responses
      if( (ObsModel_ez(c_iz(i,0),1)==0) | (ObsModel_ez(c_iz(i,0),1)==3) ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if( (ObsModel_ez(e_i(i),0)==0) | (ObsModel_ez(e_i(i),0)==1) | (ObsModel_ez(e_i(i),0)==2) ){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0){
            LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rnorm( R2_i(i), SigmaM(e_i(i),0) );
            }
          }
          if(ObsModel_ez(e_i(i),0)==1){
            LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = exp(rnorm( log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0) ));
            }
          }
          if(ObsModel_ez(e_i(i),0)==2){
            LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rgamma( 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2) );
            }
          }
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Likelihood #2 for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      ///// Likelihood for models with discrete support
      // Zero-inflated negative binomial (not numerically stable!)
      if(ObsModel_ez(e_i(i),0)==5){
        var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rnbinom2( R2_i(i), var_i(i) );
          }
        }
      }
      // Conway-Maxwell-Poisson
      if(ObsModel_ez(e_i(i),0)==6){
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Poisson
      if(ObsModel_ez(e_i(i),0)==7){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i) );
          }
        }
      }
      // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
        /// Doesn't appear stable given spatial or spatio-temporal variation
      if(ObsModel_ez(e_i(i),0)==9){
        vector<Type> logdBinPois(4);
        logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
        logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
        for(int j=3; j<=10; j++){
          logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
        }
        logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
        if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
        if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
        if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
        if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Lognormal Poisson
      if(ObsModel_ez(e_i(i),0)==11){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
          }
        }
      }
      // Non-zero-inflated Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==12){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
        }
      }
      // Non-zero-inflated Bernoulli using cloglog link from 1st lilnear predict
      if(ObsModel_ez(e_i(i),0)==13){
        if( b_i(i)==0 ){
          LogProb2_i(i) = dpois(Type(0), R1_i(i), true);
        }else{
          LogProb2_i(i) = logspace_sub( log(Type(1.0)), dpois(Type(0), R1_i(i), true) );
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = 1;
          }
        }
      }
      // Non-zero-inflated Lognormal-Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==14){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
        }
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( (t_yz(y,z)>=0) & (t_yz(y,z)<n_t) ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( (ObsModel_ez(c,1)==0) | (ObsModel_ez(c,1)==3) ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( B_ff );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );

  SIMULATE{
    REPORT( b_i );
  }

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }
  if( Options(3)==1 ){
    vector<Type> D_i( n_i );
    D_i = R1_i * R2_i;
    ADREPORT( D_i );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( ObsModel_ez(e_i(i),0)==11 ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( t_iz(i,zt)>=0 & t_iz(i,zt)<n_t ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Responses
      if( ObsModel_ez(c_iz(i,0),1)==0 | ObsModel_ez(c_iz(i,0),1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
          //if( !isNA(c_iz(i,zc)) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel_ez(e_i(i),0)==0 | ObsModel_ez(e_i(i),0)==1 | ObsModel_ez(e_i(i),0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
          if(ObsModel_ez(e_i(i),0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
          if(ObsModel_ez(e_i(i),0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel_ez(e_i(i),0)==4 | ObsModel_ez(e_i(i),0)==5 | ObsModel_ez(e_i(i),0)==6 | ObsModel_ez(e_i(i),0)==7 | ObsModel_ez(e_i(i),0)==9 | ObsModel_ez(e_i(i),0)==11){
        if(ObsModel_ez(e_i(i),0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        }
        if(ObsModel_ez(e_i(i),0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel_ez(e_i(i),0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel_ez(c,1)==0 | ObsModel_ez(c,1)==3 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Gulf of Alaska Northern Rockfish for Comparison to spatialDeltaGLMM()
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 5.22.17
#
#Purpose: Example implementation of VAST model for GOA Northern rockfish, 
#           to be proposed for 2018 stock assessment
#
#
#==================================================================================================
#NOTES:
#
#==================================================================================================
source("R/create-VAST-input.r")
source("R/create-Data-Geostat.r")
source("R/load-RACE-data.r")
source("R/plot-VAST-output.r")
source("R/cleanup-VAST-file.r")
source("R/run-RE-model.r") 

require(VAST)
require(TMB)

#=======================================================================
##### SETUP INPUT DATA #####

#Generate a dataset
species.codes <- c(30420)
combineSpecies <- FALSE

lat_lon.def <- "start"

survey <- "GOA"
Region <- 'Gulf_of_Alaska'

bias.correct <- TRUE

#SPATIAL SETTINGS
Method <- c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km <- 25
n_x <- 500 #c(100, 250, 500, 1000, 2000)[1] # Number of stations
Kmeans_Config <- list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("All_areas"))


#DERIVED OBJECTS
Version <-  "VAST_v4_0_0"
###########################
trial.file <- paste0(getwd(),"/examples/Species_Specific_Case_Studies/Test_GOA_Northern_rockfish/")

#MODEL SETTINGS
FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

ObsModel = c(1, 0) #Lognormal
# ObsModel = c(2, 0) #Gamma
# ObsModel = c(1, 1) #Poisson-Process Link function approximating Tweedie distribution

#SPECIFY OUTPUTS
Options = c(SD_site_density = 0, SD_site_logdensity = 0,
            Calculate_Range = 1, Calculate_evenness = 0, Calculate_effective_area = 1,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)


DateFile <- paste0(trial.file,"GOA Northern rockfish knots_",n_x," bias.correct_", bias.correct, " Rho_",RhoConfig[1],RhoConfig[2],RhoConfig[3],RhoConfig[4],"/")
#=======================================================================
##### READ IN DATA AND BUILD vAST INPUT #####


#Extract Dusy data for comparison with Dana's version 10.12.17
temp.data <- load_RACE_data(species.codes=species.codes, combineSpecies=combineSpecies, survey=survey, writeCSV=FALSE)
write.csv(temp.data, file=paste0(trial.file,"/Northern rockfish Input Data Curry.csv"))


VAST_input <- create_VAST_input(species.codes=species.codes, combineSpecies=combineSpecies,
                                     lat_lon.def=lat_lon.def, save.Record=TRUE,
                                     Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                     Kmeans_Config=Kmeans_Config,
                                     strata.limits=strata.limits, survey=survey,
                                     DateFile=DateFile,
                                     FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                     OverdispersionConfig=OverdispersionConfig,
                                     ObsModel=ObsModel, Options=Options, Version=Version)

#Unpack
TmbData <- VAST_input$TmbData
Data_Geostat <- VAST_input$Data_Geostat
Spatial_List <- VAST_input$Spatial_List
Extrapolation_List <- VAST_input$Extrapolation_List #Becomes zeros for non-GOA
# head(Extrapolation_List$a_el)
# head(Extrapolation_List$Area_km2_x)
# head(Extrapolation_List$Data_Extrap)

#=======================================================================
##### RUN VAST #####



#Build TMB Object
#  Compilation may take some time
TmbList <- VAST::Build_TMB_Fn(TmbData = TmbData, RunDir = DateFile,
                                Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                                Method = Method)
Obj <- TmbList[["Obj"]]


# Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                           upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                           bias.correct = bias.correct,
#                           bias.correct.control=list(sd=FALSE, nsplit=200, split=NULL,
#                                vars_to_correct="Index_cyl"),
#                           newtonsteps = 2)
#                           # bias.correct.control=list(nsplit=200, split=NULL, sd=FALSE))

if(bias.correct==FALSE) {
  Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
                             upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
                             bias.correct = bias.correct, newtonsteps=2)
}else {
  #NEW: Only Bias Correct Index
  Opt <- TMBhelper::Optimize(obj=Obj, lower=TmbList[["Lower"]], 
                             upper=TmbList[["Upper"]], getsd=TRUE, savedir=DateFile, 
                             bias.correct=bias.correct, newtonsteps=2,
                             bias.correct.control=list(sd=TRUE, nsplit=NULL, split=NULL,
                                                       vars_to_correct="Index_cyl"))
}
#Save output
Report = Obj$report()
Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
save(Save, file=paste0(DateFile,"Save.RData"))

#Load Data
load(paste0(DateFile,"Save.RData"))
names(Save)
Opt <- Save$Opt
Report <- Save$Report
ParHat <- Save$ParHat
TmbData <- Save$TmbData

#========================================================================
##### DIAGNOSTIC AND PREDICTION PLOTS #####
# plot_VAST_output(Opt, Report, DateFile, survey, TmbData, Data_Geostat, Extrapolation_List, Spatial_List)

#========================================================================
##### CLEAN UP MODEL FILES #####
# cleanup_VAST_file(DateFile, Version=Version)


# #========================================================================
# ##### DIAGNOSTIC PLOTS #####
# 
#Plot spatial distribution of data
SpatialDeltaGLMM::Plot_data_and_knots(Extrapolation_List = Extrapolation_List,
                                      Spatial_List = Spatial_List, Data_Geostat = Data_Geostat,
                                      PlotDir = DateFile)

#Diagnostics for Encounter Probability
#  "Diag--Encounter_prob"
Enc_prob = SpatialDeltaGLMM::Check_encounter_prob(Report = Report,
                                                  Data_Geostat = Data_Geostat,
                                                  DirName = DateFile)

#Diagnostics for positive-catch-rate component - WARNINGS
Q = SpatialDeltaGLMM::QQ_Fn(TmbData = TmbData, Report = Report,
                            FileName_PP = paste0(DateFile, "Posterior_Predictive.jpg"),
                            FileName_Phist = paste0(DateFile, "Posterior_Predictive-Histogram.jpg"),
                            FileName_QQ = paste0(DateFile, "Q-Q_plot.jpg"),
                            FileName_Qhist = paste0(DateFile, "Q-Q_hist.jpg"))

# Q = SpatialDeltaGLMM::QQ_Fn(TmbData = TmbData, Report = Report,
#                             FileName_PP = NULL,
#                             FileName_Phist = NULL,
#                             FileName_QQ = NULL,
#                             FileName_Qhist = NULL)

# dev.off()
#Diagnostics for plotting residuals on a map


MapDetails_List = SpatialDeltaGLMM::MapDetails_Fn( "Region"=Region,
                                                   "NN_Extrap"=Spatial_List$PolygonList$NN_Extrap,
                                                   "Extrapolation_List"=Extrapolation_List )

#Which Years to Include
Year_Set = seq(min(Data_Geostat[,'Year']),max(Data_Geostat[,'Year']))
Years2Include = which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))
Years2Include = c(1: length(sort(unique(Data_Geostat[,'Year']))))
#Or just include years with observations

#Plot Pearson Residuals - NOT WORKING
#  Look for spatial patterns-- indication of "overshrinking"
#  Creates "maps--" files
# SpatialDeltaGLMM:::plot_residuals(Lat_i = Data_Geostat[,"Lat"], Lon_i = Data_Geostat[, "Lon"], TmbData = TmbData,
#                                   Report = Report, Q = Q, savedir = DateFile, MappingDetails = MapDetails_List[["MappingDetails"]],
#                                   PlotDF = MapDetails_List[["PlotDF"]], MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
#                                   Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
#                                   FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
#                                   Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
#                                   Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
#                                   mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8)




SpatialDeltaGLMM:::plot_residuals(Lat_i = Data_Geostat[,"Lat"], Lon_i = Data_Geostat[, "Lon"], TmbData = TmbData, 
                                  Report = Report, Q = Q, savedir = DateFile, MappingDetails = MapDetails_List[["MappingDetails"]], 
                                  PlotDF = MapDetails_List[["PlotDF"]], MapSizeRatio = MapDetails_List[["MapSizeRatio"]], 
                                  Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]], 
                                  FileName = DateFile, Year_Set = Year_Set, Rotate = MapDetails_List[["Rotate"]], 
                                  Cex = MapDetails_List[["Cex"]], Legend = MapDetails_List[["Legend"]], 
                                  zone = MapDetails_List[["Zone"]], mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8)

#========================================================================
##### MODEL OUTPUT PLOTS #####

#Direction of "geometric anisotropy"
SpatialDeltaGLMM::PlotAniso_Fn(FileName = paste0(DateFile,"Aniso.png"),
                               Report = Report, TmbData = TmbData)

#Density Surface for Each Year -- "Dens"
SpatialDeltaGLMM::PlotResultsOnMap_Fn(plot_set = c(3),
                                      MappingDetails = MapDetails_List[["MappingDetails"]],
                                      Report = Report, Sdreport = Opt$SD, PlotDF = MapDetails_List[["PlotDF"]],
                                      MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
                                      Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
                                      FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
                                      Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
                                      Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
                                      mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8,
                                      plot_legend_fig = TRUE)



#Generate Index of Abundance

Index = SpatialDeltaGLMM::PlotIndex_Fn(DirName = DateFile,
                                       TmbData = TmbData, Sdreport = Opt[["SD"]],
                                       Year_Set = Year_Set,
                                       Years2Include = Years2Include,
                                       use_biascorr = TRUE)

idx <- Index$Table


dev.off()
#Plotting

# Extract VAST Index =======================================================
source("R/get-VAST-index.r")
vast_est = get_VAST_index(TmbData=TmbData, Sdreport=Opt[["SD"]], bias.correct=bias.correct, Data_Geostat=Data_Geostat)
#Limit to years with observations
vast_est = vast_est[Years2Include,]

# Compare VAST and Design-based ============================================
#Calculate Design-Based
source("R/load-RACE-data.R")
source("R/calc-design-based-index.R")
db_est = calc_design_based_index(species.codes=species.codes, survey=survey)

# Plot 

png(paste0(DateFile,"/VAST DB Index Comparison.png"), height=8, width=9, units='in', res=500)  
par(mfrow=c(1,1), oma=c(0,0,0,0), mar=c(4,4,3,1))

y.lim = c(0, max(vast_est$Estimate_metric_tons+2*vast_est$SD_mt,
                 db_est$Biomass+2*db_est$SD, na.rm=TRUE))
x.lim = c(min(Year_Set), max(Year_Set))

#Survey years to plot
years = Year_Set[Years2Include]

#Plot it out
plot(x=NULL, y=NULL, xlim=x.lim, ylim=y.lim, xlab='Year', ylab='Abundance (metric tonnes)',
     main=paste0(' n=', TmbData$n_x, ' knots'))
grid(col='black')

legend('top', legend=c('Design-based', 'VAST'), fill=c('blue', 'red'), ncol=2, bg='white')

#Design-based
polygon(x=c(years, rev(years)), y=c(db_est$Biomass+2*db_est$SD, rev(db_est$Biomass-2*db_est$SD)),
        border=FALSE, col=rgb(0,0,1, alpha=0.25))

polygon(x=c(years, rev(years)), y=c(db_est$Biomass+1*db_est$SD, rev(db_est$Biomass-1*db_est$SD)),
        border=FALSE, col=rgb(0,0,1, alpha=0.25))

lines(x=years, y=db_est$Biomass, lwd=2, col='blue')
points(x=years, y=db_est$Biomass, pch=21, bg='blue')

#VAST
polygon(x=c(years, rev(years)), y=c(vast_est$Estimate_metric_tons+2*vast_est$SD_mt,
                                    rev(vast_est$Estimate_metric_tons-2*vast_est$SD_mt)),
        border=FALSE, col=rgb(1,0,0, alpha=0.25))

polygon(x=c(years, rev(years)), y=c(vast_est$Estimate_metric_tons+1*vast_est$SD_mt, 
                                    rev(vast_est$Estimate_metric_tons-1*vast_est$SD_mt)),
        border=FALSE, col=rgb(1,0,0, alpha=0.25))

lines(x=years, y=vast_est$Estimate_metric_tons, lwd=2, col='red')
points(x=years, y=vast_est$Estimate_metric_tons, pch=21, bg='red')
dev.off()



#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Gulf of Alaska Dusky Rockfish for 2019 SAFE
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 10.22.19
#
#Purpose: Implementation of VAST for GOA Dusky Rockfish Assessment
#
#
#==================================================================================================
#NOTES:
#Memory ProfilingJust


# [1] "bias.correct: FALSE"
# [1] "n_x: 1000"
# [1] "START: Tue Oct 22 15:01:16 2019"
# [1] "END: Wed Oct 23 00:02:27 2019"

# NEW FIT METHOD
# [1] "bias.correct: FALSE"
# [1] "n_x: 1000"
# [1] "START: Wed Oct 23 23:11:49 2019"
# [1] "END: Thu Oct 24 06:09:33 2019"

#==================================================================================================
source("R/create-VAST-input-new.r")
source("R/create-Data-Geostat.r")
source("R/load-RACE-data.r")
source("R/plot-VAST-output.r")
source("R/cleanup-VAST-file.r")
source("R/run-RE-model.r") 

require(VAST)
require(TMB)
require(tidyverse)
require(FishStatsUtils)

#=======================================================================
##### SETUP INPUT DATA #####

#Generate a dataset
species.codes <- c(30150,30152)
combineSpecies <- TRUE

lat_lon.def <- "start"

survey <- "GOA"
Region <- "Gulf_of_Alaska"

bias.correct <- FALSE #TARGET: FALSE

#SPATIAL SETTINGS
Method <- c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km <- 25
n_x <- c(100, 250, 500, 1000, 2000)[3] #TARGET: 4 (1000)
Kmeans_Config <- list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("All_areas"))


#DERIVED OBJECTS
Version <-  "VAST_v2_8_0"
###########################
trial.file <- paste0(getwd(),"/examples/Species_Specific_Case_Studies/Test_GOA_Dusky_rockfish/")

#MODEL SETTINGS
FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

ObsModel = c(1, 0) #Lognormal
# ObsModel = c(2, 0) #Gamma
# ObsModel = c(1, 1) #Poisson-Process Link function approximating Tweedie distribution

#SPECIFY OUTPUTS
Options = c(SD_site_density = 1, SD_site_logdensity = 1,
            Calculate_Range = 1, Calculate_evenness = 1, Calculate_effective_area = 1,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)

# DEFINE SETTINGS
settings <- make_settings(n_x=n_x, Region=Region, purpose = "index", fine_scale = FALSE,
                          strata.limits = data.frame(STRATA = "All_areas"), zone = NA,
                          FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                          OverdispersionConfig=OverdispersionConfig, ObsModel=ObsModel,
                          bias.correct=bias.correct,
                          Options=Options, use_anisotropy=TRUE, 
                          vars_to_correct="Index_cyl", Version=Version)#,
# treat_nonencounter_as_zero, n_categories, VamConfig)

# DateFile <- paste0(trial.file,"GOA Dusky rockfish knots_",n_x," bias.correct_", bias.correct, " Rho_",RhoConfig[1],RhoConfig[2],RhoConfig[3],RhoConfig[4],"/")

DateFile <- paste0(trial.file,"Dusky rockfish knots_",n_x," bias.correct_", bias.correct, 
                   " Rho_",RhoConfig[1],RhoConfig[2],RhoConfig[3],RhoConfig[4],
                   " ObsModel_",ObsModel[1],ObsModel[2],"/")

# Save options for future records
# Record <- list("Version"=Version,"Method"=Method,"grid_size_km"=grid_size_km,"n_x"=n_x,"FieldConfig"=FieldConfig,
#                "RhoConfig"=RhoConfig,"OverdispersionConfig"=OverdispersionConfig,"ObsModel"=ObsModel,"Region"=Region,
#                "Species_set"=Species_set,"strata.limits"=strata.limits)
# save( Record, file=file.path(DateFile,"Record.RData"))
# capture.output( Record, file=file.path(DateFile,"Record.txt"))

#=======================================================================
##### READ IN DATA AND BUILD vAST INPUT #####
VAST_input <- create_VAST_input_new(species.codes=species.codes, combineSpecies=combineSpecies,
                                    lat_lon.def=lat_lon.def, save.Record=FALSE,
                                    Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                    Kmeans_Config=Kmeans_Config,
                                    strata.limits=strata.limits, survey=survey,
                                    DateFile=DateFile,
                                    FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                    OverdispersionConfig=OverdispersionConfig,
                                    ObsModel=ObsModel, Options=Options, Version=Version)
#Unpack
TmbData <- VAST_input$TmbData
Data_Geostat <- VAST_input$Data_Geostat
Spatial_List <- VAST_input$Spatial_List
Extrapolation_List <- VAST_input$Extrapolation_List #Becomes zeros for non-GOA
settings <- VAST_input$settings
MapDetails_List <- VAST_input$MapDetails_List

# head(Extrapolation_List$a_el)
# head(Extrapolation_List$Area_km2_x)
# head(Extrapolation_List$Data_Extrap)

#=======================================================================
##### RUN VAST #####



#Build TMB Object
#  Compilation may take some time
TmbList <- VAST::make_model(TmbData = TmbData, RunDir = DateFile,
                            Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                            Method = Method)
Obj <- TmbList[["Obj"]]

start.time <- date()

#================================================
#TESTING OPTIMIZATION: Original Call

# Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                           upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                           bias.correct = bias.correct)


#================================================
#TESTING OPTIMIZATION: Updated call with nsplit to reduce memory load and 
#                        allow running bias.cor with kt > ~300
# Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                            upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                            bias.correct = bias.correct,
#                            bias.correct.control=list(nsplit=200, split=NULL, sd=FALSE))

#================================================
#TESTING OPTIMIZATION: New Alternative Following Jim's Suggestion
#  Should limit bias correction to single vector of interst: index

# nsplit <- 200
# 
# Opt = TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                            upper = TmbList[["Upper"]], getsd = TRUE, 
#                            savedir = DateFile, bias.correct=bias.correct )

# if(bias.correct==FALSE) {
#   Opt <- TMBhelper::fit_tmb(obj = Obj, lower = TmbList[["Lower"]],
#                             upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                             bias.correct = bias.correct)#, newtonsteps=1)
# }else {
#   #NEW: Only Bias Correct Index
#   Opt <- TMBhelper::fit_tmb(obj=Obj, lower=TmbList[["Lower"]], 
#                             upper=TmbList[["Upper"]], getsd=TRUE, savedir=DateFile, 
#                             bias.correct=bias.correct, #newtonsteps=1,
#                             bias.correct.control=list(sd=TRUE, #nsplit=200, split=NULL,
#                                                       vars_to_correct="Index_cyl"))
# }

# New Alternative ===========
fit <- fit_model(settings, Lat_i=Data_Geostat[["Lat"]], Lon_i=Data_Geostat[["Lon"]], 
                 t_iz=Data_Geostat[["Year"]], 
                 b_i=Data_Geostat[["Catch_KG"]],
                 a_i=Data_Geostat[["AreaSwept_km2"]], 
                 c_iz = rep(0, nrow(Data_Geostat)),#rep(0,length(b_i)), 
                 v_i = rep(0, nrow(Data_Geostat)),#Data_Geostat[["Vessel"]],
                 working_dir = DateFile, Xconfig_zcp = NULL,
                 covariate_data=NULL, formula = ~0, Q_ik = NULL, newtonsteps = 1,
                 silent = TRUE, run_model = TRUE, test_fit = FALSE)

Opt <- fit
# Get sdreport for Plotting ======================
# Sdreport <- TMB::sdreport( obj=fit, par.fixed=fit$par) #No need to run as
# Sdreport <- Opt$SD

# First SD run 
# h <- optimHess(Opt$par, Obj$fn, Obj$gr)
# SD = sdreport( obj=Obj, par.fixed=Opt$par, hessian.fixed=h )

# Determine indices
# BiasCorrNames = c("Index_cyl")
# Which = which( rownames(summary(SD,"report")) %in% BiasCorrNames )
# Which = split( Which, cut(seq_along(Which), nsplit) )
# Which = Which[sapply(Which,FUN=length)>0]


# Repeat SD with indexing
# SD = sdreport( obj=Obj, par.fixed=Opt$par, hessian.fixed=h, bias.correct=TRUE, bias.correct.control=list(sd=FALSE, split=Which, nsplit=NULL) )

#================================================


end.time <- date()
#Save output
Report = Obj$report()
Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
save(Save, file=paste0(DateFile,"Save.RData"))

#========================================================================
##### DIAGNOSTIC AND PREDICTION PLOTS #####
# Get Index
Year_Set = seq(min(Data_Geostat[,'Year']),max(Data_Geostat[,'Year']))
Years2Include = which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))


# Only works when use fit_model
plot_results(fit=fit, settings = settings, plot_set = 3,
             working_dir = DateFile, year_labels = Year_Set,
             years_to_plot = Years2Include,
             use_biascorr = bias.correct, map_list=MapDetails_List,
             category_names="GOA Dusky rockfish", check_residuals = TRUE, projargs = "+proj=longlat",
             n_samples = 100)

# Plot Data and Knots
FishStatsUtils::plot_data(Extrapolation_List=Extrapolation_List, Spatial_List=Spatial_List, 
                          Data_Geostat=Data_Geostat,
          PlotDir = paste0(DateFile, "/"), Plot1_name = "Data_and_knots.png",
          Plot2_name = "Data_by_year.png", col = rep("red",nrow(Data_Geostat)), cex = 0.01)


# Diagnostics =====================================
# FishStatsUtils::map_hypervariance(report=TmbList[["Obj"]], Spatial_List=Spatial_List, 
#                                   method="anisotropic")


FishStatsUtils::plot_encounter_diagnostic(Report, Data_Geostat, 
                                          cutpoints_z = seq(0, 1,length = 21), 
                          interval_width = 1.96, DirName = paste0(DateFile, "/"),
                          PlotName = "Diag--Encounter_prob.png")

Q <- FishStatsUtils::plot_quantile_diagnostic(TmbData=TmbData, Report=Report, 
                                              DateFile=DateFile, 
                              save_dir = paste0(DateFile, "/QQ_Fn/"),
                              FileName_PP = "Posterior_Predictive",
                              FileName_Phist = "Posterior_Predictive-Histogram",
                              FileName_QQ = "Q-Q_plot", FileName_Qhist = "Q-Q_hist")

#Plot Pearson Residuals
#  Look for spatial patterns-- indication of "overshrinking"
#  Creates "maps--" files - ERROR: Error in as_mapper(.f, ...) : argument ".f" is missing, with no default

# FishStatsUtils::plot_residuals(Lat_i = Data_Geostat[,"Lat"], Lon_i = Data_Geostat[, "Lon"], TmbData = TmbData,
#                Report = Report, Q = Q, savedir = DateFile, MappingDetails = MapDetails_List[["MappingDetails"]],
#                PlotDF = MapDetails_List[["PlotDF"]], MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
#                Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
#                FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
#                Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
#                Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
#                mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8,
#                spatial_list=Spatial_List,
#                extrapolation_list=Extrapolation_List)

# Updated - NOT WORKING
FishStatsUtils::plot_residuals(Lat_i=Data_Geostat[,"Lat"], Lon_i=Data_Geostat[, "Lon"],
                               TmbData=TmbData, Report=Report, Q=Q,
                               projargs = "+proj=longlat", working_dir = DateFile,
                               spatial_list=Spatial_List, 
                               extrapolation_list=Extrapolation_List, Year_Set = Year_Set,
                               Years2Include = Years2Include)



# Predictions =====================================
# Plot Biomass Index and generate .xlsx
FishStatsUtils::plot_biomass_index(TmbData=TmbData, 
                                    Sdreport = Opt[["SD"]], 
                                    Year_Set = Year_Set, 
                                    Years2Include = Years2Include,
                                    use_biascorr=bias.correct,
                                    DirName = DateFile)

# Plot shifts in distribution and area occupied
FishStatsUtils::plot_range_index(Sdreport=Sdreport, Report=Report, 
                                 TmbData=TmbData, Year_Set = Year_Set,
                 PlotDir = paste0(DateFile, "/"), 
                 FileName_COG = paste0(DateFile,"/center_of_gravity.png"), 
                 FileName_Area = paste0(DateFile,"/Area.png"), 
                 FileName_EffArea = paste0(DateFile,"/Effective_Area.png"), 
                 Znames = rep("", ncol(TmbData$Z_xm)),
                 use_biascorr = bias.correct, category_names = NULL, interval_width = 1)

#Direction of "geometric anisotropy"
SpatialDeltaGLMM::PlotAniso_Fn(FileName = paste0(DateFile,"Aniso.png"),
                               Report = Report, TmbData = TmbData)

# Plot Maps ======================
# plot_maps(plot_set = 1, Report=Report, PlotDF, Sdreport = Opt[["SD"]],
#           TmbData = TmbData, projargs = "+proj=longlat", Panel = "Category",
#           Year_Set = Year_Set, Years2Include = Years2Include, category_names = NULL,
#           quiet = FALSE, working_dir = DateFile, MapSizeRatio=c(2,4),
#           n_cells=10)

# Plot Results ===================
# Only works when use fit_model
# plot_results(fit=Opt, settings = settings, plot_set = 3,
#              working_dir = Date_File, year_labels = Year_Set,
#              years_to_plot = Years2Include, 
#              use_biascorr = bias.correct, map_list=MapDetails_List,
#              category_names, check_residuals = TRUE, projargs = "+proj=longlat",
#              zrange, n_samples = 100)


# plot_maps(plot_set = 3, MappingDetails, Report, #PlotDF,
#           Sdreport = NULL, Xlim, Ylim, TmbData, Nknots = n_x,
#           Panel = "Category", MapSizeRatio = c(`Width(in)` = 4, `Height(in)` = 4),
#           Res = 200, FileName = paste0(DateFile, "/"), Year_Set = NULL,
#           Years2Include = NULL, Rescale = FALSE, Rotate = 0,
#           Format = "png", zone = NA, Cex = 0.01, add = FALSE,
#           category_names = NULL, textmargin = NULL, pch = NULL,
#           Legend = list(use = FALSE, x = c(10, 30), y = c(10, 30)),
#           mfrow = NULL, plot_legend_fig = TRUE)
#========================================================================
##### CLEAN UP MODEL FILES #####
# cleanup_VAST_file(DateFile, Version=Version)

print(paste('bias.correct:',bias.correct))
print(paste('n_x:',n_x))
print(paste('START:',start.time))
print(paste('END:',end.time))


#========================================================================
##### APPORTIONMENT #####












#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Gulf of Alaska Dusky Rockfish for Comparison to spatialDeltaGLMM()
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 5.22.17
#
#Purpose: Example implementation of VAST model for GOA Dusky rockfish
#
#
#==================================================================================================
#NOTES:
#
#==================================================================================================
source("R/create-VAST-input.r")
source("R/create-Data-Geostat.r")
source("R/load-RACE-data.r")
source("R/plot-VAST-output.r")
source("R/cleanup-VAST-file.r")
source("R/run-RE-model.r") 

require(VAST)
require(TMB)

#=======================================================================
##### SETUP INPUT DATA #####

#Generate a dataset
species.codes <- c(30150,30152)
combineSpecies <- TRUE

lat_lon.def <- "start"

survey <- "GOA"
Region <- 'Gulf_of_Alaska'

bias.correct <- FALSE

#SPATIAL SETTINGS
Method <- c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km <- 25
n_x <- 500 #c(100, 250, 500, 1000, 2000)[1] # Number of stations
Kmeans_Config <- list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("All_areas"))


#DERIVED OBJECTS
Version <-  "VAST_v4_0_0"
###########################
trial.file <- paste0(getwd(),"/examples/Species_Specific_Case_Studies/Test_GOA_Dusky_rockfish/")

#MODEL SETTINGS
FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

ObsModel = c(1, 0) #Lognormal
# ObsModel = c(2, 0) #Gamma
# ObsModel = c(1, 1) #Poisson-Process Link function approximating Tweedie distribution

#SPECIFY OUTPUTS
Options = c(SD_site_density = 0, SD_site_logdensity = 0,
            Calculate_Range = 1, Calculate_evenness = 0, Calculate_effective_area = 1,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)


DateFile <- paste0(trial.file,"GOA Dusky rockfish knots_",n_x," bias.correct_", bias.correct, " Rho_",RhoConfig[1],RhoConfig[2],RhoConfig[3],RhoConfig[4],"/")
#=======================================================================
##### READ IN DATA AND BUILD vAST INPUT #####


#Extract Dusy data for comparison with Dana's version 10.12.17
temp.data <- load_RACE_data(species.codes=species.codes, combineSpecies=combineSpecies, survey=survey, writeCSV=FALSE)
write.csv(temp.data, file=paste0(trial.file,"/Dusky rockfish Input Data Curry.csv"))


VAST_input <- create_VAST_input_new(species.codes=species.codes, combineSpecies=combineSpecies,
                                    lat_lon.def=lat_lon.def, save.Record=FALSE,
                                    Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                    Kmeans_Config=Kmeans_Config,
                                    strata.limits=strata.limits, survey=survey,
                                    DateFile=DateFile,
                                    FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                    OverdispersionConfig=OverdispersionConfig,
                                    ObsModel=ObsModel, Options=Options, Version=Version)

#Unpack
TmbData <- VAST_input$TmbData
Data_Geostat <- VAST_input$Data_Geostat
Spatial_List <- VAST_input$Spatial_List
Extrapolation_List <- VAST_input$Extrapolation_List #Becomes zeros for non-GOA
# head(Extrapolation_List$a_el)
# head(Extrapolation_List$Area_km2_x)
# head(Extrapolation_List$Data_Extrap)

#=======================================================================
##### RUN VAST #####



#Build TMB Object
#  Compilation may take some time
TmbList <- VAST::Build_TMB_Fn(TmbData = TmbData, RunDir = DateFile,
                                Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                                Method = Method)
Obj <- TmbList[["Obj"]]


# Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                           upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                           bias.correct = bias.correct,
#                           bias.correct.control=list(nsplit=200, split=NULL, sd=FALSE))
if(bias.correct==FALSE) {
  Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
                             upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
                             bias.correct = bias.correct, newtonsteps=2)
}else {
  #NEW: Only Bias Correct Index
  Opt <- TMBhelper::Optimize(obj=Obj, lower=TmbList[["Lower"]], 
                             upper=TmbList[["Upper"]], getsd=TRUE, savedir=DateFile, 
                             bias.correct=bias.correct, newtonsteps=2,
                             bias.correct.control=list(sd=TRUE, nsplit=NULL, split=NULL,
                                                       vars_to_correct="Index_cyl"))
}
#Save output
Report = Obj$report()
Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
save(Save, file=paste0(DateFile,"Save.RData"))

#========================================================================
##### DIAGNOSTIC AND PREDICTION PLOTS #####
# plot_VAST_output(Opt, Report, DateFile, survey, TmbData, Data_Geostat, Extrapolation_List, Spatial_List)

#========================================================================
##### CLEAN UP MODEL FILES #####
# cleanup_VAST_file(DateFile, Version=Version)


# #========================================================================
# ##### DIAGNOSTIC PLOTS #####
# 
#Plot spatial distribution of data
SpatialDeltaGLMM::Plot_data_and_knots(Extrapolation_List = Extrapolation_List,
                                      Spatial_List = Spatial_List, Data_Geostat = Data_Geostat,
                                      PlotDir = DateFile)

#Diagnostics for Encounter Probability
#  "Diag--Encounter_prob"
Enc_prob = SpatialDeltaGLMM::Check_encounter_prob(Report = Report,
                                                  Data_Geostat = Data_Geostat,
                                                  DirName = DateFile)

#Diagnostics for positive-catch-rate component - WARNINGS
Q = SpatialDeltaGLMM::QQ_Fn(TmbData = TmbData, Report = Report,
                            FileName_PP = paste0(DateFile, "Posterior_Predictive.jpg"),
                            FileName_Phist = paste0(DateFile, "Posterior_Predictive-Histogram.jpg"),
                            FileName_QQ = paste0(DateFile, "Q-Q_plot.jpg"),
                            FileName_Qhist = paste0(DateFile, "Q-Q_hist.jpg"))


#Diagnostics for plotting residuals on a map


MapDetails_List = SpatialDeltaGLMM::MapDetails_Fn( "Region"=Region,
                                                   "NN_Extrap"=Spatial_List$PolygonList$NN_Extrap,
                                                   "Extrapolation_List"=Extrapolation_List )

#Which Years to Include
Year_Set = seq(min(Data_Geostat[,'Year']),max(Data_Geostat[,'Year']))
Years2Include = which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))

#Or just include years with observations

#Plot Pearson Residuals - NOT WORKING
#  Look for spatial patterns-- indication of "overshrinking"
#  Creates "maps--" files
SpatialDeltaGLMM:::plot_residuals(Lat_i = Data_Geostat[,"Lat"], Lon_i = Data_Geostat[, "Lon"], TmbData = TmbData,
                                  Report = Report, Q = Q, savedir = DateFile, MappingDetails = MapDetails_List[["MappingDetails"]],
                                  PlotDF = MapDetails_List[["PlotDF"]], MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
                                  Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
                                  FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
                                  Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
                                  Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
                                  mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8)




#========================================================================
##### MODEL OUTPUT PLOTS #####

#Direction of "geometric anisotropy"
SpatialDeltaGLMM::PlotAniso_Fn(FileName = paste0(DateFile,"Aniso.png"),
                               Report = Report, TmbData = TmbData)

#Density Surface for Each Year -- "Dens"
SpatialDeltaGLMM::PlotResultsOnMap_Fn(plot_set = c(3),
                                      MappingDetails = MapDetails_List[["MappingDetails"]],
                                      Report = Report, Sdreport = Opt$SD, PlotDF = MapDetails_List[["PlotDF"]],
                                      MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
                                      Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
                                      FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
                                      Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
                                      Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
                                      mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8,
                                      plot_legend_fig = TRUE)



#Generate Index of Abundance

Index = SpatialDeltaGLMM::PlotIndex_Fn(DirName = DateFile,
                                       TmbData = TmbData, Sdreport = Opt[["SD"]],
                                       Year_Set = Year_Set,
                                       Years2Include = Years2Include,
                                       use_biascorr = TRUE)

idx <- Index$Table


dev.off()
#Plotting

yrs.surv <- Year_Set[Years2Include]
x.lim <- c(min(yrs.surv), max(yrs.surv))
up.sd <- idx$Estimate_metric_tons + idx$SD_mt
low.sd <- idx$Estimate_metric_tons - idx$SD_mt
y.lim <- c(min(low.sd), max(up.sd))

loc.yrs <- which(idx$Year %in% yrs.surv)


plot(x=NULL, y=NULL, xlim=x.lim, ylim=y.lim, ylab='Survey Estimate (metric Tons)', xlab='Year',
     main='Gulf of Alaska\nDusky Rockfish Survey Index')

polygon(x=c(yrs.surv, rev(yrs.surv)), y=c(low.sd[loc.yrs],rev(up.sd[loc.yrs])), col='lightblue', border=FALSE)
lines(x=yrs.surv, y=idx$Estimate_metric_tons[loc.yrs], col='red')
points(x=yrs.surv, y=idx$Estimate_metric_tons[loc.yrs], pch=21, bg='red')
grid(col='black')

# 
# #Center of gravity and range expansion/contraction
# #  For some reason I can't actually change the years to plot 
# SpatialDeltaGLMM::Plot_range_shifts(Report = Report,
#                                     TmbData = TmbData, Sdreport = Opt[["SD"]], Znames = colnames(TmbData$Z_xm),
#                                     PlotDir = DateFile, Year_Set = Year_Set)

#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IVECTOR(ObsModel);    // Observation model
  // Slot 0: Distribution for positive catches
  // Slot 1: Link function for encounter probabilities
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IVECTOR(c_i);         // Category for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  //  -- presence/absence
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation
  //  -- positive catch rates
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz
  PARAMETER_ARRAY(logSigmaM);   // Slots: 0=mix1 CV, 1=prob-of-mix1, 2=
  PARAMETER_VECTOR(delta_i);
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_c, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  if( ObsModel(0)==11 ){
    for(i=0; i<delta_i.size(); i++){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  // Linear predictor (pre-link) for presence/absence component
  vector<Type> P1_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi 
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  vector<Type> P2_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      P1_i(i) = Omega1_sc(s_i(i),c_i(i)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_i(i));
      P2_i(i) = Omega2_sc(s_i(i),c_i(i)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_i(i));
      for( int z=0; z<t_iz.row(0).size(); z++ ){
        if( t_iz(i,z)>=0 & t_iz(i,z)<n_t ){  // isNA doesn't seem to work for IMATRIX type
          P1_i(i) += beta1_ct(c_i(i),t_iz(i,z)) + Epsilon1_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(s_i(i),c_i(i),t_iz(i,z));
          P2_i(i) += beta2_ct(c_i(i),t_iz(i,z)) + Epsilon2_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(s_i(i),c_i(i),t_iz(i,z));
        }
      }
      // Responses
      if( ObsModel(1)==0 | ObsModel(1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_i(i) );
        R2_i(i) = a_i(i) * exp( P2_i(i) );
      }
      if( ObsModel(1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        R1_i(i) = Type(1.0) - exp( -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i)) );
        R2_i(i) = a_i(i)*exp(P1_i(i)) / R1_i(i) * exp( P2_i(i) );
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i));
      }
      if( ObsModel(1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_i(i) );
        R2_i(i) = exp( P2_i(i) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel(0)==0 | ObsModel(0)==1 | ObsModel(0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel(1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel(0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(c_i(i),0), true);
          if(ObsModel(0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(c_i(i),0),2)/2, SigmaM(c_i(i),0), true); // log-space
          if(ObsModel(0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(c_i(i),0),2), R2_i(i)*pow(SigmaM(c_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel(0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(c_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel(0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(c_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel(0)==4 | ObsModel(0)==5 | ObsModel(0)==6 | ObsModel(0)==7 | ObsModel(0)==9 | ObsModel(0)==11){
        if(ObsModel(0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(c_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_i(i),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel(0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_i(i)), true, Options_vec(5));
        }
        if(ObsModel(0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel(0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel(0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(c_i(i),0)*delta_i(i)-pow(SigmaM(c_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(c_i(i),0)*delta_i(i)-pow(SigmaM(c_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel(4) isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel(1)==0 | ObsModel(1)==3 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel(1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -SigmaM(c,2)*exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel(1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel[1]=1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_i );
  REPORT( P2_i );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IVECTOR(ObsModel);    // Observation model
  // Slot 0: Distribution for positive catches
  // Slot 1: Link function for encounter probabilities
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IVECTOR(c_i);         // Category for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  //  -- presence/absence
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation
  //  -- positive catch rates
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz
  PARAMETER_ARRAY(logSigmaM);   // Slots: 0=mix1 CV, 1=prob-of-mix1, 2=
  PARAMETER_VECTOR(delta_i);
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_c, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  if( ObsModel(0)==11 ){
    for(i=0; i<delta_i.size(); i++){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  // Linear predictor (pre-link) for presence/absence component
  vector<Type> P1_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi 
  vector<Type> R1_i(n_i);   
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  vector<Type> P2_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      P1_i(i) = Omega1_sc(s_i(i),c_i(i)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_i(i));
      P2_i(i) = Omega2_sc(s_i(i),c_i(i)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_i(i));
      for( int z=0; z<t_iz.row(0).size(); z++ ){
        if( t_iz(i,z)>=0 & t_iz(i,z)<n_t ){  // isNA doesn't seem to work for IMATRIX type
          P1_i(i) += beta1_ct(c_i(i),t_iz(i,z)) + Epsilon1_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(s_i(i),c_i(i),t_iz(i,z));
          P2_i(i) += beta2_ct(c_i(i),t_iz(i,z)) + Epsilon2_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(s_i(i),c_i(i),t_iz(i,z));
        }
      }
      // Responses
      if( ObsModel(1)==0 | ObsModel(1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_i(i) );
        R2_i(i) = a_i(i) * exp( P2_i(i) );
      }
      if( ObsModel(1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        R1_i(i) = Type(1.0) - exp( -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i)) );
        R2_i(i) = a_i(i)*exp(P1_i(i)) / R1_i(i) * exp( P2_i(i) );
      }
      if( ObsModel(1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_i(i) );
        R2_i(i) = exp( P2_i(i) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel(0)==0 | ObsModel(0)==1 | ObsModel(0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          LogProb1_i(i) = log( 1-R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel(0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(c_i(i),0), true);
          if(ObsModel(0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(c_i(i),0),2)/2, SigmaM(c_i(i),0), true); // log-space
          if(ObsModel(0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(c_i(i),0),2), R2_i(i)*pow(SigmaM(c_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel(0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(c_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel(0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(c_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel(0)==4 | ObsModel(0)==5 | ObsModel(0)==6 | ObsModel(0)==7 | ObsModel(0)==9 | ObsModel(0)==11){
        if(ObsModel(0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(c_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_i(i),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel(0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_i(i)), true, Options_vec(5));
        }
        if(ObsModel(0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel(0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel(0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(c_i(i),0)*delta_i(i)-pow(SigmaM(c_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(c_i(i),0)*delta_i(i)-pow(SigmaM(c_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel(4) isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel(1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel(1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -SigmaM(c,2)*exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel(1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel[1]=1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_i );
  REPORT( P2_i );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IVECTOR(ObsModel);    // Observation model
  // Slot 0: Distribution for positive catches
  // Slot 1: Link function for encounter probabilities
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IVECTOR(c_i);         // Category for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  //  -- presence/absence
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation
  //  -- positive catch rates
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz
  PARAMETER_ARRAY(logSigmaM);   // Slots: 0=mix1 CV, 1=prob-of-mix1, 2=
  PARAMETER_VECTOR(delta_i);
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_c, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  if( ObsModel(0)==11 ){
    for(i=0; i<delta_i.size(); i++){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  // Linear predictor (pre-link) for presence/absence component
  vector<Type> P1_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi 
  vector<Type> R1_i(n_i);   
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  vector<Type> P2_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      P1_i(i) = Omega1_sc(s_i(i),c_i(i)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_i(i));
      P2_i(i) = Omega2_sc(s_i(i),c_i(i)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_i(i));
      for( int z=0; z<t_iz.row(0).size(); z++ ){
        if( t_iz(i,z)>=0 & t_iz(i,z)<n_t ){  // isNA doesn't seem to work for IMATRIX type
          P1_i(i) += beta1_ct(c_i(i),t_iz(i,z)) + Epsilon1_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(s_i(i),c_i(i),t_iz(i,z));
          P2_i(i) += beta2_ct(c_i(i),t_iz(i,z)) + Epsilon2_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(s_i(i),c_i(i),t_iz(i,z));
        }
      }
      // Responses
      if( ObsModel(1)==0 | ObsModel(1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_i(i) );
        R2_i(i) = a_i(i) * exp( P2_i(i) );
      }
      if( ObsModel(1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        R1_i(i) = Type(1.0) - exp( -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i)) );
        R2_i(i) = a_i(i)*exp(P1_i(i)) / R1_i(i) * exp( P2_i(i) );
      }
      if( ObsModel(1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_i(i) );
        R2_i(i) = exp( P2_i(i) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel(0)==0 | ObsModel(0)==1 | ObsModel(0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          LogProb1_i(i) = log( 1-R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel(0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(c_i(i),0), true);
          if(ObsModel(0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(c_i(i),0),2)/2, SigmaM(c_i(i),0), true); // log-space
          if(ObsModel(0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(c_i(i),0),2), R2_i(i)*pow(SigmaM(c_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel(0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(c_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel(0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(c_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel(0)==4 | ObsModel(0)==5 | ObsModel(0)==6 | ObsModel(0)==7 | ObsModel(0)==9 | ObsModel(0)==11){
        if(ObsModel(0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(c_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_i(i),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel(0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_i(i)), true, Options_vec(5));
        }
        if(ObsModel(0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel(0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel(0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(c_i(i),0)*delta_i(i)-pow(SigmaM(c_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(c_i(i),0)*delta_i(i)-pow(SigmaM(c_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel(4) isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel(1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel(1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -SigmaM(c,2)*exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel(1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel[1]=1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_i );
  REPORT( P2_i );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IVECTOR(ObsModel);    // Observation model
  // Slot 0: Distribution for positive catches
  // Slot 1: Link function for encounter probabilities
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IVECTOR(c_i);         // Category for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  //  -- presence/absence
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation
  //  -- positive catch rates
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz
  PARAMETER_ARRAY(logSigmaM);   // Slots: 0=mix1 CV, 1=prob-of-mix1, 2=
  PARAMETER_VECTOR(delta_i);
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_c, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  if( ObsModel(0)==11 ){
    for(i=0; i<delta_i.size(); i++){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  // Linear predictor (pre-link) for presence/absence component
  vector<Type> P1_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi 
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  vector<Type> P2_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      P1_i(i) = Omega1_sc(s_i(i),c_i(i)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_i(i));
      P2_i(i) = Omega2_sc(s_i(i),c_i(i)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_i(i));
      for( int z=0; z<t_iz.row(0).size(); z++ ){
        if( t_iz(i,z)>=0 & t_iz(i,z)<n_t ){  // isNA doesn't seem to work for IMATRIX type
          P1_i(i) += beta1_ct(c_i(i),t_iz(i,z)) + Epsilon1_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(s_i(i),c_i(i),t_iz(i,z));
          P2_i(i) += beta2_ct(c_i(i),t_iz(i,z)) + Epsilon2_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(s_i(i),c_i(i),t_iz(i,z));
        }
      }
      // Responses
      if( ObsModel(1)==0 | ObsModel(1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_i(i) );
        R2_i(i) = a_i(i) * exp( P2_i(i) );
      }
      if( ObsModel(1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        R1_i(i) = Type(1.0) - exp( -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i)) );
        R2_i(i) = a_i(i)*exp(P1_i(i)) / R1_i(i) * exp( P2_i(i) );
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i));
      }
      if( ObsModel(1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_i(i) );
        R2_i(i) = exp( P2_i(i) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel(0)==0 | ObsModel(0)==1 | ObsModel(0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel(1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel(0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(c_i(i),0), true);
          if(ObsModel(0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(c_i(i),0),2)/2, SigmaM(c_i(i),0), true); // log-space
          if(ObsModel(0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(c_i(i),0),2), R2_i(i)*pow(SigmaM(c_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel(0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(c_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel(0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(c_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel(0)==4 | ObsModel(0)==5 | ObsModel(0)==6 | ObsModel(0)==7 | ObsModel(0)==9 | ObsModel(0)==11){
        if(ObsModel(0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(c_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_i(i),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel(0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_i(i)), true, Options_vec(5));
        }
        if(ObsModel(0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel(0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel(0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(c_i(i),0)*delta_i(i)-pow(SigmaM(c_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(c_i(i),0)*delta_i(i)-pow(SigmaM(c_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel(4) isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel(1)==0 | ObsModel(1)==3 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel(1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -SigmaM(c,2)*exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel(1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel[1]=1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_i );
  REPORT( P2_i );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( ObsModel_ez(e_i(i),0)==11 ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( t_iz(i,zt)>=0 & t_iz(i,zt)<n_t ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Responses
      if( ObsModel_ez(c_iz(i,0),1)==0 | ObsModel_ez(c_iz(i,0),1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
          //if( !isNA(c_iz(i,zc)) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel_ez(e_i(i),0)==0 | ObsModel_ez(e_i(i),0)==1 | ObsModel_ez(e_i(i),0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
          if(ObsModel_ez(e_i(i),0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
          if(ObsModel_ez(e_i(i),0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel_ez(e_i(i),0)==4 | ObsModel_ez(e_i(i),0)==5 | ObsModel_ez(e_i(i),0)==6 | ObsModel_ez(e_i(i),0)==7 | ObsModel_ez(e_i(i),0)==9 | ObsModel_ez(e_i(i),0)==11){
        if(ObsModel_ez(e_i(i),0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        }
        if(ObsModel_ez(e_i(i),0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel_ez(e_i(i),0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel_ez(c,1)==0 | ObsModel_ez(c,1)==3 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( ObsModel_ez(e_i(i),0)==11 ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( t_iz(i,zt)>=0 & t_iz(i,zt)<n_t ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Responses
      if( ObsModel_ez(c_iz(i,0),1)==0 | ObsModel_ez(c_iz(i,0),1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
          //if( !isNA(c_iz(i,zc)) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel_ez(e_i(i),0)==0 | ObsModel_ez(e_i(i),0)==1 | ObsModel_ez(e_i(i),0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
          if(ObsModel_ez(e_i(i),0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
          if(ObsModel_ez(e_i(i),0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel_ez(e_i(i),0)==4 | ObsModel_ez(e_i(i),0)==5 | ObsModel_ez(e_i(i),0)==6 | ObsModel_ez(e_i(i),0)==7 | ObsModel_ez(e_i(i),0)==9 | ObsModel_ez(e_i(i),0)==11){
        if(ObsModel_ez(e_i(i),0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        }
        if(ObsModel_ez(e_i(i),0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel_ez(e_i(i),0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel_ez(c,1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Gulf of Alaska Northern Rockfish for 2019 SAFE
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 10.22.19
#
#Purpose: Implementation of VAST for GOA Dusky Northern
#
#
#==================================================================================================
#NOTES:
#Memory ProfilingJust


# [1] "bias.correct: FALSE"
# [1] "n_x: 100"
# [1] "START: Tue Oct 22 13:28:36 2019"
# [1] "END: Tue Oct 22 13:31:57 2019"

#==================================================================================================
source("R/create-VAST-input-new.r")
source("R/create-Data-Geostat.r")
source("R/load-RACE-data.r")
source("R/plot-VAST-output.r")
source("R/cleanup-VAST-file.r")
source("R/run-RE-model.r") 

require(VAST)
require(TMB)
require(tidyverse)
require(FishStatsUtils)

#=======================================================================
##### SETUP INPUT DATA #####

#Generate a dataset
species.codes <- c(30420)
combineSpecies <- FALSE

lat_lon.def <- "start"

survey <- "GOA"
Region <- "Gulf_of_Alaska"

bias.correct <- TRUE #TARGET: TRUE

#SPATIAL SETTINGS
Method <- c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km <- 25
n_x <- c(100, 250, 500, 1000, 2000)[3] #TARGET: 3 (500)
Kmeans_Config <- list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("All_areas"))


#DERIVED OBJECTS
Version <-  "VAST_v4_0_0"
###########################
trial.file <- paste0(getwd(),"/examples/Species_Specific_Case_Studies/Test_GOA_Northern_rockfish/")

#MODEL SETTINGS
FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

ObsModel = c(1, 0) #Lognormal
# ObsModel = c(2, 0) #Gamma
# ObsModel = c(1, 1) #Poisson-Process Link function approximating Tweedie distribution

#SPECIFY OUTPUTS
Options = c(SD_site_density = 1, SD_site_logdensity = 1,
            Calculate_Range = 0, Calculate_evenness = 0, Calculate_effective_area = 0,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)

# DEFINE SETTINGS
# settings <- make_settings(n_x=n_x, Region=Region, purpose = "index", fine_scale = FALSE,
#                           strata.limits = data.frame(STRATA = "All_areas"), zone = NA,
#                           FieldConfig=FieldConfig, RhoConfig=RhoConfig,
#                           OverdispersionConfig=OverdispersionConfig, ObsModel=ObsModel,
#                           bias.correct=bias.correct,
#                           Options=Options, use_anisotropy=TRUE, 
#                           vars_to_correct="Index_cyl", Version=Version,
#                           treat_nonencounter_as_zero=FALSE)#, n_categories, VamConfig)

# DateFile <- paste0(trial.file,"GOA Northern rockfish knots_",n_x," bias.correct_", bias.correct, " Rho_",RhoConfig[1],RhoConfig[2],RhoConfig[3],RhoConfig[4],"/")

DateFile <- paste0(trial.file,"GOA Northern rockfish knots_",n_x," bias.correct_", bias.correct, 
                   " Rho_",RhoConfig[1],RhoConfig[2],RhoConfig[3],RhoConfig[4],
                   " ObsModel_",ObsModel[1],ObsModel[2],"/")

# Save options for future records
# Record <- list("Version"=Version,"Method"=Method,"grid_size_km"=grid_size_km,"n_x"=n_x,"FieldConfig"=FieldConfig,
#                "RhoConfig"=RhoConfig,"OverdispersionConfig"=OverdispersionConfig,"ObsModel"=ObsModel,"Region"=Region,
#                "Species_set"=Species_set,"strata.limits"=strata.limits)
# save( Record, file=file.path(DateFile,"Record.RData"))
# capture.output( Record, file=file.path(DateFile,"Record.txt"))

#=======================================================================
##### READ IN DATA AND BUILD vAST INPUT #####
VAST_input <- create_VAST_input_new(species.codes=species.codes, combineSpecies=combineSpecies,
                                    lat_lon.def=lat_lon.def, save.Record=FALSE,
                                    Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                    Kmeans_Config=Kmeans_Config,
                                    strata.limits=strata.limits, survey=survey,
                                    DateFile=DateFile,
                                    FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                    OverdispersionConfig=OverdispersionConfig,
                                    ObsModel=ObsModel, Options=Options, Version=Version)
#Unpack
TmbData <- VAST_input$TmbData
Data_Geostat <- VAST_input$Data_Geostat
Spatial_List <- VAST_input$Spatial_List
Extrapolation_List <- VAST_input$Extrapolation_List #Becomes zeros for non-GOA
settings <- VAST_input$settings
MapDetails_List <- VAST_input$MapDetails_List

# head(Extrapolation_List$a_el)
# head(Extrapolation_List$Area_km2_x)
# head(Extrapolation_List$Data_Extrap)

#=======================================================================
##### RUN VAST #####



#Build TMB Object
#  Compilation may take some time
# ERROR HERE
TmbList <- VAST::make_model(TmbData = TmbData, RunDir = DateFile,
                            Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                            Method = Method)
Obj <- TmbList[["Obj"]]

start.time <- date()

#================================================
#TESTING OPTIMIZATION: Original Call

# Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                           upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                           bias.correct = bias.correct)


#================================================
#TESTING OPTIMIZATION: Updated call with nsplit to reduce memory load and 
#                        allow running bias.cor with kt > ~300
# Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                            upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                            bias.correct = bias.correct,
#                            bias.correct.control=list(nsplit=200, split=NULL, sd=FALSE))

#================================================
#TESTING OPTIMIZATION: New Alternative Following Jim's Suggestion
#  Should limit bias correction to single vector of interst: index

# nsplit <- 200
# 
# Opt = TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                            upper = TmbList[["Upper"]], getsd = TRUE, 
#                            savedir = DateFile, bias.correct=bias.correct )

# if(bias.correct==FALSE) {
#   Opt <- TMBhelper::fit_tmb(obj = Obj, lower = TmbList[["Lower"]],
#                             upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                             bias.correct = bias.correct)#, newtonsteps=1)
# }else {
#   #NEW: Only Bias Correct Index
#   Opt <- TMBhelper::fit_tmb(obj=Obj, lower=TmbList[["Lower"]],
#                             upper=TmbList[["Upper"]], getsd=TRUE, savedir=DateFile,
#                             bias.correct=bias.correct, #newtonsteps=1,
#                             bias.correct.control=list(sd=TRUE, #nsplit=200, split=NULL,
#                                                       vars_to_correct="Index_cyl"))
# }

# New Alternative ===========
fit <- fit_model(settings, Lat_i=Data_Geostat[["Lat"]], Lon_i=Data_Geostat[["Lon"]], 
                 t_iz=Data_Geostat[["Year"]], 
                 b_i=Data_Geostat[["Catch_KG"]],
                 a_i=Data_Geostat[["AreaSwept_km2"]], 
                 c_iz = rep(0, nrow(Data_Geostat)),#rep(0,length(b_i)), 
                 v_i = rep(0, nrow(Data_Geostat)),#Data_Geostat[["Vessel"]],
                 working_dir = DateFile, Xconfig_zcp = NULL,
                 covariate_data=NULL, formula = ~0, Q_ik = NULL, newtonsteps = 1,
                 silent = TRUE, run_model = TRUE, test_fit = FALSE)
# Opt <- fit

# Get sdreport for Plotting ======================
# Sdreport <- TMB::sdreport( obj=Obj, par.fixed=Opt$par) #No need to run as 
Sdreport <- Opt$SD

# First SD run 
# h <- optimHess(Opt$par, Obj$fn, Obj$gr)
# SD = sdreport( obj=Obj, par.fixed=Opt$par, hessian.fixed=h )

# Determine indices
# BiasCorrNames = c("Index_cyl")
# Which = which( rownames(summary(SD,"report")) %in% BiasCorrNames )
# Which = split( Which, cut(seq_along(Which), nsplit) )
# Which = Which[sapply(Which,FUN=length)>0]


# Repeat SD with indexing
# SD = sdreport( obj=Obj, par.fixed=Opt$par, hessian.fixed=h, bias.correct=TRUE, bias.correct.control=list(sd=FALSE, split=Which, nsplit=NULL) )

#================================================


end.time <- date()
#Save output
Report = Obj$report()
Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
save(Save, file=paste0(DateFile,"Save.RData"))

#========================================================================
##### DIAGNOSTIC AND PREDICTION PLOTS #####
# Get Index
Year_Set = seq(min(Data_Geostat[,'Year']),max(Data_Geostat[,'Year']))
Years2Include = which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))


# Only works when use fit_model
plot_results(fit=fit, settings = settings, plot_set = 3,
             working_dir = DateFile, year_labels = Year_Set,
             years_to_plot = Years2Include,
             use_biascorr = bias.correct, map_list=MapDetails_List,
             category_names="GOA NR", check_residuals = TRUE, projargs = "+proj=longlat",
             n_samples = 100)


# Error in as_mapper(.f, ...) : argument ".f" is missing, with no default
plot_data(Extrapolation_List=Extrapolation_List, Spatial_List=Spatial_List, Data_Geostat=Data_Geostat,
          PlotDir = paste0(DateFile, "/"), Plot1_name = "Data_and_knots.png",
          Plot2_name = "Data_by_year.png", col = rep("red",nrow(Data_Geostat)), cex = 0.01)


# Diagnostics =====================================
plot_encounter_diagnostic(Report, Data_Geostat, cutpoints_z = seq(0, 1,length = 21), 
                          interval_width = 1.96, DirName = paste0(DateFile, "/"),
                          PlotName = "Diag--Encounter_prob.png")

Q <- plot_quantile_diagnostic(TmbData=TmbData, Report=Report, DateFile=DateFile, 
                              save_dir = paste0(DateFile, "/QQ_Fn/"),
                              FileName_PP = "Posterior_Predictive",
                              FileName_Phist = "Posterior_Predictive-Histogram",
                              FileName_QQ = "Q-Q_plot", FileName_Qhist = "Q-Q_hist")

#Plot Pearson Residuals
#  Look for spatial patterns-- indication of "overshrinking"
#  Creates "maps--" files - ERROR: Error in as_mapper(.f, ...) : argument ".f" is missing, with no default

plot_residuals(Lat_i = Data_Geostat[,"Lat"], Lon_i = Data_Geostat[, "Lon"], TmbData = TmbData,
               Report = Report, Q = Q, savedir = DateFile, MappingDetails = MapDetails_List[["MappingDetails"]],
               PlotDF = MapDetails_List[["PlotDF"]], MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
               Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
               FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
               Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
               Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
               mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8)





# Predictions =====================================

Index = SpatialDeltaGLMM::PlotIndex_Fn(DirName = DateFile,
                                       TmbData = TmbData, Sdreport = Opt[["SD"]],
                                       Year_Set = Year_Set,
                                       Years2Include = Years2Include, 
                                       use_biascorr = bias.correct)

# Plot shifts in distribution and area occupied
plot_range_index(Sdreport, Report, TmbData, Year_Set = NULL,
                 PlotDir = paste0(DateFile, "/"), 
                 FileName_COG = paste0(DateFile,"/center_of_gravity.png"), 
                 FileName_Area = paste0(DateFile,"/Area.png"), 
                 FileName_EffArea = paste0(DateFile,"/Effective_Area.png"), 
                 Znames = rep("", ncol(TmbData$Z_xm)),
                 use_biascorr = bias.correct, category_names = NULL, interval_width = 1)

#Direction of "geometric anisotropy"
SpatialDeltaGLMM::PlotAniso_Fn(FileName = paste0(DateFile,"Aniso.png"),
                               Report = Report, TmbData = TmbData)

# plot_maps(plot_set = 3, MappingDetails, Report, #PlotDF,
#           Sdreport = NULL, Xlim, Ylim, TmbData, Nknots = n_x,
#           Panel = "Category", MapSizeRatio = c(`Width(in)` = 4, `Height(in)` = 4),
#           Res = 200, FileName = paste0(DateFile, "/"), Year_Set = NULL,
#           Years2Include = NULL, Rescale = FALSE, Rotate = 0,
#           Format = "png", zone = NA, Cex = 0.01, add = FALSE,
#           category_names = NULL, textmargin = NULL, pch = NULL,
#           Legend = list(use = FALSE, x = c(10, 30), y = c(10, 30)),
#           mfrow = NULL, plot_legend_fig = TRUE)
#========================================================================
##### CLEAN UP MODEL FILES #####
# cleanup_VAST_file(DateFile, Version=Version)

print(paste('bias.correct:',bias.correct))
print(paste('n_x:',n_x))
print(paste('START:',start.time))
print(paste('END:',end.time))


#========================================================================
##### APPORTIONMENT #####














# 
# 
# #========================================================================
# ##### DIAGNOSTIC PLOTS #####
# 
# #Plot spatial distribution of data
# SpatialDeltaGLMM::Plot_data_and_knots(Extrapolation_List = Extrapolation_List,
#                                       Spatial_List = Spatial_List, Data_Geostat = Data_Geostat,
#                                       PlotDir = DateFile)
# 
# #Diagnostics for Encounter Probability
# #  "Diag--Encounter_prob"
# Enc_prob = SpatialDeltaGLMM::Check_encounter_prob(Report = Report,
#                                                   Data_Geostat = Data_Geostat,
#                                                   DirName = DateFile)
# 
# #Diagnostics for positive-catch-rate component
# Q = SpatialDeltaGLMM::QQ_Fn(TmbData = TmbData, Report = Report,
#                             FileName_PP = paste0(DateFile, "Posterior_Predictive.jpg"),
#                             FileName_Phist = paste0(DateFile, "Posterior_Predictive-Histogram.jpg"),
#                             FileName_QQ = paste0(DateFile, "Q-Q_plot.jpg"),
#                             FileName_Qhist = paste0(DateFile, "Q-Q_hist.jpg"))
# 
# 
# #Diagnostics for plotting residuals on a map
# 
# 
# MapDetails_List = SpatialDeltaGLMM::MapDetails_Fn( "Region"=Region,
#                                                    "NN_Extrap"=Spatial_List$PolygonList$NN_Extrap,
#                                                    "Extrapolation_List"=Extrapolation_List )
# 
# #Which Years to Include
# Year_Set = seq(min(Data_Geostat[,'Year']),max(Data_Geostat[,'Year']))
# Years2Include = which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))
# 
# #Or just include years with observations
# 
# #Plot Pearson Residuals
# #  Look for spatial patterns-- indication of "overshrinking"
# #  Creates "maps--" files
# SpatialDeltaGLMM:::plot_residuals(Lat_i = Data_Geostat[,"Lat"], Lon_i = Data_Geostat[, "Lon"], TmbData = TmbData,
#                                   Report = Report, Q = Q, savedir = DateFile, MappingDetails = MapDetails_List[["MappingDetails"]],
#                                   PlotDF = MapDetails_List[["PlotDF"]], MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
#                                   Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
#                                   FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
#                                   Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
#                                   Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
#                                   mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8)
# 
# 
# 
# 
# #========================================================================
# ##### MODEL OUTPUT PLOTS #####
# 
# #Direction of "geometric anisotropy"
# SpatialDeltaGLMM::PlotAniso_Fn(FileName = paste0(DateFile,"Aniso.png"),
#                                Report = Report, TmbData = TmbData)
# 
# #Density Surface for Each Year -- "Dens"
# SpatialDeltaGLMM::PlotResultsOnMap_Fn(plot_set = c(3),
#                                       MappingDetails = MapDetails_List[["MappingDetails"]],
#                                       Report = Report, Sdreport = Opt$SD, PlotDF = MapDetails_List[["PlotDF"]],
#                                       MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
#                                       Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
#                                       FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
#                                       Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
#                                       Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
#                                       mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8,
#                                       plot_legend_fig = TRUE)
# 
# 
# 
# #Generate Index of Abundance
# 
# Index = SpatialDeltaGLMM::PlotIndex_Fn(DirName = DateFile,
#                                        TmbData = TmbData, Sdreport = Opt[["SD"]],
#                                        Year_Set = Year_Set,
#                                        Years2Include = Years2Include, 
#                                        use_biascorr = TRUE)
# 
# idx <- Index$Table
# 
# 
# dev.off()
# #Plotting 
# 
# yrs.surv <- Year_Set[Years2Include]
# x.lim <- c(min(yrs.surv), max(yrs.surv))
# up.sd <- idx$Estimate_metric_tons + idx$SD_mt
# low.sd <- idx$Estimate_metric_tons - idx$SD_mt
# y.lim <- c(min(low.sd), max(up.sd))
# 
# loc.yrs <- which(idx$Year %in% yrs.surv)
# 
# 
# plot(x=NULL, y=NULL, xlim=x.lim, ylim=y.lim, ylab='Survey Estimate (metric Tons)', xlab='Year',
#      main='Gulf of Alaska\nNorthern Rockfish Survey Index')
# 
# polygon(x=c(yrs.surv, rev(yrs.surv)), y=c(low.sd[loc.yrs],rev(up.sd[loc.yrs])), col='lightblue', border=FALSE)
# lines(x=yrs.surv, y=idx$Estimate_metric_tons[loc.yrs], col='red')
# points(x=yrs.surv, y=idx$Estimate_metric_tons[loc.yrs], pch=21, bg='red')
# grid(col='black')
# 
# 
# #Center of gravity and range expansion/contraction
# #  For some reason I can't actually change the years to plot 
# SpatialDeltaGLMM::Plot_range_shifts(Report = Report,
#                                     TmbData = TmbData, Sdreport = Opt[["SD"]], Znames = colnames(TmbData$Z_xm),
#                                     PlotDir = DateFile, Year_Set = Year_Set)

#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Gulf of Alaska Northern Rockfish for Comparison to spatialDeltaGLMM()
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 5.22.17
#
#Purpose: Example implementation of VAST model for GOA Northern rockfish, 
#           to be proposed for 2018 stock assessment
#
#
#==================================================================================================
#NOTES:
#
#==================================================================================================
source("R/create-VAST-input.r")
source("R/create-Data-Geostat.r")
source("R/load-RACE-data.r")
source("R/plot-VAST-output.r")
source("R/cleanup-VAST-file.r")
source("R/run-RE-model.r") 

require(VAST)
require(TMB)

#=======================================================================
##### SETUP INPUT DATA #####

#Generate a dataset
species.codes <- c(30420)
combineSpecies <- FALSE

lat_lon.def <- "start"

survey <- "GOA"
Region <- 'Gulf_of_Alaska'

bias.correct <- TRUE

#SPATIAL SETTINGS
Method <- c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km <- 25
n_x <- 500 #c(100, 250, 500, 1000, 2000)[1] # Number of stations
Kmeans_Config <- list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("All_areas"))


#DERIVED OBJECTS
Version <-  "VAST_v4_0_0"
###########################
trial.file <- paste0(getwd(),"/examples/Species_Specific_Case_Studies/Test_GOA_Northern_rockfish/")

#MODEL SETTINGS
FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

ObsModel = c(1, 0) #Lognormal
# ObsModel = c(2, 0) #Gamma
# ObsModel = c(1, 1) #Poisson-Process Link function approximating Tweedie distribution

#SPECIFY OUTPUTS
Options = c(SD_site_density = 0, SD_site_logdensity = 0,
            Calculate_Range = 1, Calculate_evenness = 0, Calculate_effective_area = 1,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)


DateFile <- paste0(trial.file,"GOA Northern rockfish knots_",n_x," bias.correct_", bias.correct, " Rho_",RhoConfig[1],RhoConfig[2],RhoConfig[3],RhoConfig[4],"/")
#=======================================================================
##### READ IN DATA AND BUILD vAST INPUT #####


#Extract Dusy data for comparison with Dana's version 10.12.17
temp.data <- load_RACE_data(species.codes=species.codes, combineSpecies=combineSpecies, survey=survey, writeCSV=FALSE)
write.csv(temp.data, file=paste0(trial.file,"/Northern rockfish Input Data Curry.csv"))


VAST_input <- create_VAST_input(species.codes=species.codes, combineSpecies=combineSpecies,
                                     lat_lon.def=lat_lon.def, save.Record=TRUE,
                                     Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                     Kmeans_Config=Kmeans_Config,
                                     strata.limits=strata.limits, survey=survey,
                                     DateFile=DateFile,
                                     FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                     OverdispersionConfig=OverdispersionConfig,
                                     ObsModel=ObsModel, Options=Options, Version=Version)

#Unpack
TmbData <- VAST_input$TmbData
Data_Geostat <- VAST_input$Data_Geostat
Spatial_List <- VAST_input$Spatial_List
Extrapolation_List <- VAST_input$Extrapolation_List #Becomes zeros for non-GOA
# head(Extrapolation_List$a_el)
# head(Extrapolation_List$Area_km2_x)
# head(Extrapolation_List$Data_Extrap)

#=======================================================================
##### RUN VAST #####



#Build TMB Object
#  Compilation may take some time
TmbList <- VAST::Build_TMB_Fn(TmbData = TmbData, RunDir = DateFile,
                                Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                                Method = Method)
Obj <- TmbList[["Obj"]]


# Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                           upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                           bias.correct = bias.correct,
#                           bias.correct.control=list(sd=FALSE, nsplit=200, split=NULL,
#                                vars_to_correct="Index_cyl"),
#                           newtonsteps = 2)
#                           # bias.correct.control=list(nsplit=200, split=NULL, sd=FALSE))

if(bias.correct==FALSE) {
  Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
                             upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
                             bias.correct = bias.correct, newtonsteps=2)
}else {
  #NEW: Only Bias Correct Index
  Opt <- TMBhelper::Optimize(obj=Obj, lower=TmbList[["Lower"]], 
                             upper=TmbList[["Upper"]], getsd=TRUE, savedir=DateFile, 
                             bias.correct=bias.correct, newtonsteps=2,
                             bias.correct.control=list(sd=TRUE, nsplit=NULL, split=NULL,
                                                       vars_to_correct="Index_cyl"))
}
#Save output
Report = Obj$report()
Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
save(Save, file=paste0(DateFile,"Save.RData"))

#Load Data
load(paste0(DateFile,"Save.RData"))
names(Save)
Opt <- Save$Opt
Report <- Save$Report
ParHat <- Save$ParHat
TmbData <- Save$TmbData

#========================================================================
##### DIAGNOSTIC AND PREDICTION PLOTS #####
# plot_VAST_output(Opt, Report, DateFile, survey, TmbData, Data_Geostat, Extrapolation_List, Spatial_List)

#========================================================================
##### CLEAN UP MODEL FILES #####
# cleanup_VAST_file(DateFile, Version=Version)


# #========================================================================
# ##### DIAGNOSTIC PLOTS #####
# 
#Plot spatial distribution of data
SpatialDeltaGLMM::Plot_data_and_knots(Extrapolation_List = Extrapolation_List,
                                      Spatial_List = Spatial_List, Data_Geostat = Data_Geostat,
                                      PlotDir = DateFile)

#Diagnostics for Encounter Probability
#  "Diag--Encounter_prob"
Enc_prob = SpatialDeltaGLMM::Check_encounter_prob(Report = Report,
                                                  Data_Geostat = Data_Geostat,
                                                  DirName = DateFile)

#Diagnostics for positive-catch-rate component - WARNINGS
Q = SpatialDeltaGLMM::QQ_Fn(TmbData = TmbData, Report = Report,
                            FileName_PP = paste0(DateFile, "Posterior_Predictive.jpg"),
                            FileName_Phist = paste0(DateFile, "Posterior_Predictive-Histogram.jpg"),
                            FileName_QQ = paste0(DateFile, "Q-Q_plot.jpg"),
                            FileName_Qhist = paste0(DateFile, "Q-Q_hist.jpg"))

# Q = SpatialDeltaGLMM::QQ_Fn(TmbData = TmbData, Report = Report,
#                             FileName_PP = NULL,
#                             FileName_Phist = NULL,
#                             FileName_QQ = NULL,
#                             FileName_Qhist = NULL)

# dev.off()
#Diagnostics for plotting residuals on a map


MapDetails_List = SpatialDeltaGLMM::MapDetails_Fn( "Region"=Region,
                                                   "NN_Extrap"=Spatial_List$PolygonList$NN_Extrap,
                                                   "Extrapolation_List"=Extrapolation_List )

#Which Years to Include
Year_Set = seq(min(Data_Geostat[,'Year']),max(Data_Geostat[,'Year']))
Years2Include = which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))
Years2Include = c(1: length(sort(unique(Data_Geostat[,'Year']))))
#Or just include years with observations

#Plot Pearson Residuals - NOT WORKING
#  Look for spatial patterns-- indication of "overshrinking"
#  Creates "maps--" files
# SpatialDeltaGLMM:::plot_residuals(Lat_i = Data_Geostat[,"Lat"], Lon_i = Data_Geostat[, "Lon"], TmbData = TmbData,
#                                   Report = Report, Q = Q, savedir = DateFile, MappingDetails = MapDetails_List[["MappingDetails"]],
#                                   PlotDF = MapDetails_List[["PlotDF"]], MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
#                                   Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
#                                   FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
#                                   Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
#                                   Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
#                                   mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8)




SpatialDeltaGLMM:::plot_residuals(Lat_i = Data_Geostat[,"Lat"], Lon_i = Data_Geostat[, "Lon"], TmbData = TmbData, 
                                  Report = Report, Q = Q, savedir = DateFile, MappingDetails = MapDetails_List[["MappingDetails"]], 
                                  PlotDF = MapDetails_List[["PlotDF"]], MapSizeRatio = MapDetails_List[["MapSizeRatio"]], 
                                  Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]], 
                                  FileName = DateFile, Year_Set = Year_Set, Rotate = MapDetails_List[["Rotate"]], 
                                  Cex = MapDetails_List[["Cex"]], Legend = MapDetails_List[["Legend"]], 
                                  zone = MapDetails_List[["Zone"]], mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8)

#========================================================================
##### MODEL OUTPUT PLOTS #####

#Direction of "geometric anisotropy"
SpatialDeltaGLMM::PlotAniso_Fn(FileName = paste0(DateFile,"Aniso.png"),
                               Report = Report, TmbData = TmbData)

#Density Surface for Each Year -- "Dens"
SpatialDeltaGLMM::PlotResultsOnMap_Fn(plot_set = c(3),
                                      MappingDetails = MapDetails_List[["MappingDetails"]],
                                      Report = Report, Sdreport = Opt$SD, PlotDF = MapDetails_List[["PlotDF"]],
                                      MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
                                      Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
                                      FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
                                      Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
                                      Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
                                      mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8,
                                      plot_legend_fig = TRUE)



#Generate Index of Abundance

Index = SpatialDeltaGLMM::PlotIndex_Fn(DirName = DateFile,
                                       TmbData = TmbData, Sdreport = Opt[["SD"]],
                                       Year_Set = Year_Set,
                                       Years2Include = Years2Include,
                                       use_biascorr = TRUE)

idx <- Index$Table


dev.off()
#Plotting

# Extract VAST Index =======================================================
source("R/get-VAST-index.r")
vast_est = get_VAST_index(TmbData=TmbData, Sdreport=Opt[["SD"]], bias.correct=bias.correct, Data_Geostat=Data_Geostat)
#Limit to years with observations
vast_est = vast_est[Years2Include,]

# Compare VAST and Design-based ============================================
#Calculate Design-Based
source("R/load-RACE-data.R")
source("R/calc-design-based-index.R")
db_est = calc_design_based_index(species.codes=species.codes, survey=survey)

# Plot 

png(paste0(DateFile,"/VAST DB Index Comparison.png"), height=8, width=9, units='in', res=500)  
par(mfrow=c(1,1), oma=c(0,0,0,0), mar=c(4,4,3,1))

y.lim = c(0, max(vast_est$Estimate_metric_tons+2*vast_est$SD_mt,
                 db_est$Biomass+2*db_est$SD, na.rm=TRUE))
x.lim = c(min(Year_Set), max(Year_Set))

#Survey years to plot
years = Year_Set[Years2Include]

#Plot it out
plot(x=NULL, y=NULL, xlim=x.lim, ylim=y.lim, xlab='Year', ylab='Abundance (metric tonnes)',
     main=paste0(' n=', TmbData$n_x, ' knots'))
grid(col='black')

legend('top', legend=c('Design-based', 'VAST'), fill=c('blue', 'red'), ncol=2, bg='white')

#Design-based
polygon(x=c(years, rev(years)), y=c(db_est$Biomass+2*db_est$SD, rev(db_est$Biomass-2*db_est$SD)),
        border=FALSE, col=rgb(0,0,1, alpha=0.25))

polygon(x=c(years, rev(years)), y=c(db_est$Biomass+1*db_est$SD, rev(db_est$Biomass-1*db_est$SD)),
        border=FALSE, col=rgb(0,0,1, alpha=0.25))

lines(x=years, y=db_est$Biomass, lwd=2, col='blue')
points(x=years, y=db_est$Biomass, pch=21, bg='blue')

#VAST
polygon(x=c(years, rev(years)), y=c(vast_est$Estimate_metric_tons+2*vast_est$SD_mt,
                                    rev(vast_est$Estimate_metric_tons-2*vast_est$SD_mt)),
        border=FALSE, col=rgb(1,0,0, alpha=0.25))

polygon(x=c(years, rev(years)), y=c(vast_est$Estimate_metric_tons+1*vast_est$SD_mt, 
                                    rev(vast_est$Estimate_metric_tons-1*vast_est$SD_mt)),
        border=FALSE, col=rgb(1,0,0, alpha=0.25))

lines(x=years, y=vast_est$Estimate_metric_tons, lwd=2, col='red')
points(x=years, y=vast_est$Estimate_metric_tons, pch=21, bg='red')
dev.off()



#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( ObsModel_ez(e_i(i),0)==11 ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( t_iz(i,zt)>=0 & t_iz(i,zt)<n_t ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Responses
      if( ObsModel_ez(c_iz(i,0),1)==0 | ObsModel_ez(c_iz(i,0),1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
          //if( !isNA(c_iz(i,zc)) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel_ez(e_i(i),0)==0 | ObsModel_ez(e_i(i),0)==1 | ObsModel_ez(e_i(i),0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
          if(ObsModel_ez(e_i(i),0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
          if(ObsModel_ez(e_i(i),0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel_ez(e_i(i),0)==4 | ObsModel_ez(e_i(i),0)==5 | ObsModel_ez(e_i(i),0)==6 | ObsModel_ez(e_i(i),0)==7 | ObsModel_ez(e_i(i),0)==9 | ObsModel_ez(e_i(i),0)==11){
        if(ObsModel_ez(e_i(i),0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        }
        if(ObsModel_ez(e_i(i),0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel_ez(e_i(i),0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel_ez(c,1)==0 | ObsModel_ez(c,1)==3 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( ObsModel_ez(e_i(i),0)==11 ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( t_iz(i,zt)>=0 & t_iz(i,zt)<n_t ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Responses
      if( ObsModel_ez(c_iz(i,0),1)==0 | ObsModel_ez(c_iz(i,0),1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
          //if( !isNA(c_iz(i,zc)) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel_ez(e_i(i),0)==0 | ObsModel_ez(e_i(i),0)==1 | ObsModel_ez(e_i(i),0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
          if(ObsModel_ez(e_i(i),0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
          if(ObsModel_ez(e_i(i),0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel_ez(e_i(i),0)==4 | ObsModel_ez(e_i(i),0)==5 | ObsModel_ez(e_i(i),0)==6 | ObsModel_ez(e_i(i),0)==7 | ObsModel_ez(e_i(i),0)==9 | ObsModel_ez(e_i(i),0)==11){
        if(ObsModel_ez(e_i(i),0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        }
        if(ObsModel_ez(e_i(i),0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel_ez(e_i(i),0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel_ez(c,1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( ObsModel_ez(e_i(i),0)==11 ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( t_iz(i,zt)>=0 & t_iz(i,zt)<n_t ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Responses
      if( ObsModel_ez(c_iz(i,0),1)==0 | ObsModel_ez(c_iz(i,0),1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
          //if( !isNA(c_iz(i,zc)) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel_ez(e_i(i),0)==0 | ObsModel_ez(e_i(i),0)==1 | ObsModel_ez(e_i(i),0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
          if(ObsModel_ez(e_i(i),0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
          if(ObsModel_ez(e_i(i),0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel_ez(e_i(i),0)==4 | ObsModel_ez(e_i(i),0)==5 | ObsModel_ez(e_i(i),0)==6 | ObsModel_ez(e_i(i),0)==7 | ObsModel_ez(e_i(i),0)==9 | ObsModel_ez(e_i(i),0)==11){
        if(ObsModel_ez(e_i(i),0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        }
        if(ObsModel_ez(e_i(i),0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel_ez(e_i(i),0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel_ez(c,1)==0 | ObsModel_ez(c,1)==3 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( ObsModel_ez(e_i(i),0)==11 ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( t_iz(i,zt)>=0 & t_iz(i,zt)<n_t ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Responses
      if( ObsModel_ez(c_iz(i,0),1)==0 | ObsModel_ez(c_iz(i,0),1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
          //if( !isNA(c_iz(i,zc)) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel_ez(e_i(i),0)==0 | ObsModel_ez(e_i(i),0)==1 | ObsModel_ez(e_i(i),0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
          if(ObsModel_ez(e_i(i),0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
          if(ObsModel_ez(e_i(i),0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel_ez(e_i(i),0)==4 | ObsModel_ez(e_i(i),0)==5 | ObsModel_ez(e_i(i),0)==6 | ObsModel_ez(e_i(i),0)==7 | ObsModel_ez(e_i(i),0)==9 | ObsModel_ez(e_i(i),0)==11){
        if(ObsModel_ez(e_i(i),0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        }
        if(ObsModel_ez(e_i(i),0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel_ez(e_i(i),0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel_ez(c,1)==0 | ObsModel_ez(c,1)==3 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Gulf of Alaska Pollock for CIE Review
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 5.22.17
#
#Purpose: Example implementation of VAST model for GOA Pollock
#
#
#==================================================================================================
#NOTES:
#Memory ProfilingJust


# Using nsplit=200
# [1] "bias.correct: TRUE"
# [1] "n_x: 1000"
# [1] "START: Mon Mar 05 10:18:23 2018"
# [1] "END: Mon Mar 05 17:28:07 2018"

#Using Jim's new method
# [1] "bias.correct: TRUE"
# [1] "n_x: 1000"
# [1] "START: Tue Mar 06 10:02:39 2018"
# [1] "END: Tue Mar 06 11:24:57 2018"

#==================================================================================================
 source("R/create-VAST-input.r")
 source("R/create-Data-Geostat.r")
 source("R/load-RACE-data.r")
 source("R/plot-VAST-output.r")
 source("R/cleanup-VAST-file.r")
 source("R/run-RE-model.r") 

require(VAST)
require(TMB)
require(tidyverse)

#=======================================================================
##### SETUP INPUT DATA #####

#Generate a dataset
species.codes <- 21740
lat_lon.def <- "start"

survey <- "GOA"

bias.correct <- FALSE

#SPATIAL SETTINGS
Method <- c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km <- 25
n_x <- c(100, 250, 500, 1000, 2000)[1] # Number of stations
Kmeans_Config <- list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("All_areas"))


#DERIVED OBJECTS
Version <-  "VAST_v4_0_0"
###########################
trial.file <- paste0(getwd(),"/examples/Species_Specific_Case_Studies/Test_GOA_pollock")

#MODEL SETTINGS
FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

ObsModel = c(1, 0) #Lognormal
# ObsModel = c(2, 0) #Gamma
# ObsModel = c(1, 1) #Poisson-Process Link function approximating Tweedie distribution

#SPECIFY OUTPUTS
Options = c(SD_site_density = 0, SD_site_logdensity = 0,
            Calculate_Range = 1, Calculate_evenness = 0, Calculate_effective_area = 1,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)


DateFile <- paste0(trial.file,"/GOA Pollock knots_",n_x," bias.correct_", bias.correct, " Rho_",RhoConfig[1],RhoConfig[2],RhoConfig[3],RhoConfig[4],"/")
#=======================================================================
##### READ IN DATA AND BUILD vAST INPUT #####
VAST_input <- create_VAST_input(species.codes=species.codes, combineSpecies=FALSE,
                                lat_lon.def=lat_lon.def, save.Record=FALSE,
                                Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                Kmeans_Config=Kmeans_Config,
                                strata.limits=strata.limits, survey=survey,
                                DateFile=DateFile,
                                FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                OverdispersionConfig=OverdispersionConfig,
                                ObsModel=ObsModel, Options=Options, Version=Version)
#Unpack
TmbData <- VAST_input$TmbData
Data_Geostat <- VAST_input$Data_Geostat
Spatial_List <- VAST_input$Spatial_List
Extrapolation_List <- VAST_input$Extrapolation_List #Becomes zeros for non-GOA
# head(Extrapolation_List$a_el)
# head(Extrapolation_List$Area_km2_x)
# head(Extrapolation_List$Data_Extrap)

#=======================================================================
##### RUN VAST #####



#Build TMB Object
#  Compilation may take some time
TmbList <- VAST::Build_TMB_Fn(TmbData = TmbData, RunDir = DateFile,
                                Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                                Method = Method)
Obj <- TmbList[["Obj"]]

start.time <- date()

#================================================
#TESTING OPTIMIZATION: Original Call

# Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                           upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                           bias.correct = bias.correct)


#================================================
#TESTING OPTIMIZATION: Updated call with nsplit to reduce memory load and 
#                        allow running bias.cor with kt > ~300
# Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                            upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                            bias.correct = bias.correct,
#                            bias.correct.control=list(nsplit=200, split=NULL, sd=FALSE))

#================================================
#TESTING OPTIMIZATION: New Alternative Following Jim's Suggestion
#  Should limit bias correction to single vector of interst: index

nsplit <- 200

Opt = TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
                           upper = TmbList[["Upper"]], getsd = TRUE, 
                           savedir = DateFile, bias.correct=FALSE )

# First SD run 
h <- optimHess(Opt$par, Obj$fn, Obj$gr)
SD = sdreport( obj=Obj, par.fixed=Opt$par, hessian.fixed=h )

# Determine indices
BiasCorrNames = c("Index_cyl")
Which = which( rownames(summary(SD,"report")) %in% BiasCorrNames )
Which = split( Which, cut(seq_along(Which), nsplit) )
Which = Which[sapply(Which,FUN=length)>0]


# Repeat SD with indexing
SD = sdreport( obj=Obj, par.fixed=Opt$par, hessian.fixed=h, bias.correct=TRUE, bias.correct.control=list(sd=FALSE, split=Which, nsplit=NULL) )

#================================================


end.time <- date()
#Save output
Report = Obj$report()
Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
save(Save, file=paste0(DateFile,"Save.RData"))

#========================================================================
##### DIAGNOSTIC AND PREDICTION PLOTS #####
plot_VAST_output(Opt, Report, DateFile, survey, TmbData, Data_Geostat, Extrapolation_List, Spatial_List)

#========================================================================
##### CLEAN UP MODEL FILES #####
# cleanup_VAST_file(DateFile, Version=Version)

print(paste('bias.correct:',bias.correct))
print(paste('n_x:',n_x))
print(paste('START:',start.time))
print(paste('END:',end.time))


#========================================================================
##### APPORTIONMENT #####














# 
# 
# #========================================================================
# ##### DIAGNOSTIC PLOTS #####
# 
# #Plot spatial distribution of data
# SpatialDeltaGLMM::Plot_data_and_knots(Extrapolation_List = Extrapolation_List,
#                                       Spatial_List = Spatial_List, Data_Geostat = Data_Geostat,
#                                       PlotDir = DateFile)
# 
# #Diagnostics for Encounter Probability
# #  "Diag--Encounter_prob"
# Enc_prob = SpatialDeltaGLMM::Check_encounter_prob(Report = Report,
#                                                   Data_Geostat = Data_Geostat,
#                                                   DirName = DateFile)
# 
# #Diagnostics for positive-catch-rate component
# Q = SpatialDeltaGLMM::QQ_Fn(TmbData = TmbData, Report = Report,
#                             FileName_PP = paste0(DateFile, "Posterior_Predictive.jpg"),
#                             FileName_Phist = paste0(DateFile, "Posterior_Predictive-Histogram.jpg"),
#                             FileName_QQ = paste0(DateFile, "Q-Q_plot.jpg"),
#                             FileName_Qhist = paste0(DateFile, "Q-Q_hist.jpg"))
# 
# 
# #Diagnostics for plotting residuals on a map
# 
# 
# MapDetails_List = SpatialDeltaGLMM::MapDetails_Fn( "Region"=Region,
#                                                    "NN_Extrap"=Spatial_List$PolygonList$NN_Extrap,
#                                                    "Extrapolation_List"=Extrapolation_List )
# 
# #Which Years to Include
# Year_Set = seq(min(Data_Geostat[,'Year']),max(Data_Geostat[,'Year']))
# Years2Include = which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))
# 
# #Or just include years with observations
# 
# #Plot Pearson Residuals
# #  Look for spatial patterns-- indication of "overshrinking"
# #  Creates "maps--" files
# SpatialDeltaGLMM:::plot_residuals(Lat_i = Data_Geostat[,"Lat"], Lon_i = Data_Geostat[, "Lon"], TmbData = TmbData,
#                                   Report = Report, Q = Q, savedir = DateFile, MappingDetails = MapDetails_List[["MappingDetails"]],
#                                   PlotDF = MapDetails_List[["PlotDF"]], MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
#                                   Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
#                                   FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
#                                   Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
#                                   Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
#                                   mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8)
# 
# 
# 
# 
# #========================================================================
# ##### MODEL OUTPUT PLOTS #####
# 
# #Direction of "geometric anisotropy"
# SpatialDeltaGLMM::PlotAniso_Fn(FileName = paste0(DateFile,"Aniso.png"),
#                                Report = Report, TmbData = TmbData)
# 
# #Density Surface for Each Year -- "Dens"
# SpatialDeltaGLMM::PlotResultsOnMap_Fn(plot_set = c(3),
#                                       MappingDetails = MapDetails_List[["MappingDetails"]],
#                                       Report = Report, Sdreport = Opt$SD, PlotDF = MapDetails_List[["PlotDF"]],
#                                       MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
#                                       Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
#                                       FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
#                                       Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
#                                       Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
#                                       mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8,
#                                       plot_legend_fig = TRUE)
# 
# 
# 
# #Generate Index of Abundance
# 
# Index = SpatialDeltaGLMM::PlotIndex_Fn(DirName = DateFile,
#                                        TmbData = TmbData, Sdreport = Opt[["SD"]],
#                                        Year_Set = Year_Set,
#                                        Years2Include = Years2Include, 
#                                        use_biascorr = TRUE)
# 
# idx <- Index$Table
# 
# 
# dev.off()
# #Plotting 
# 
# yrs.surv <- Year_Set[Years2Include]
# x.lim <- c(min(yrs.surv), max(yrs.surv))
# up.sd <- idx$Estimate_metric_tons + idx$SD_mt
# low.sd <- idx$Estimate_metric_tons - idx$SD_mt
# y.lim <- c(min(low.sd), max(up.sd))
# 
# loc.yrs <- which(idx$Year %in% yrs.surv)
# 
# 
# plot(x=NULL, y=NULL, xlim=x.lim, ylim=y.lim, ylab='Survey Estimate (metric Tons)', xlab='Year',
#      main='Gulf of Alaska\nNorthern Rockfish Survey Index')
# 
# polygon(x=c(yrs.surv, rev(yrs.surv)), y=c(low.sd[loc.yrs],rev(up.sd[loc.yrs])), col='lightblue', border=FALSE)
# lines(x=yrs.surv, y=idx$Estimate_metric_tons[loc.yrs], col='red')
# points(x=yrs.surv, y=idx$Estimate_metric_tons[loc.yrs], pch=21, bg='red')
# grid(col='black')
# 
# 
# #Center of gravity and range expansion/contraction
# #  For some reason I can't actually change the years to plot 
# SpatialDeltaGLMM::Plot_range_shifts(Report = Report,
#                                     TmbData = TmbData, Sdreport = Opt[["SD"]], Znames = colnames(TmbData$Z_xm),
#                                     PlotDir = DateFile, Year_Set = Year_Set)

#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( ObsModel_ez(e_i(i),0)==11 ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( t_iz(i,zt)>=0 & t_iz(i,zt)<n_t ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Responses
      if( ObsModel_ez(c_iz(i,0),1)==0 | ObsModel_ez(c_iz(i,0),1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
          //if( !isNA(c_iz(i,zc)) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel_ez(e_i(i),0)==0 | ObsModel_ez(e_i(i),0)==1 | ObsModel_ez(e_i(i),0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
          if(ObsModel_ez(e_i(i),0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
          if(ObsModel_ez(e_i(i),0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel_ez(e_i(i),0)==4 | ObsModel_ez(e_i(i),0)==5 | ObsModel_ez(e_i(i),0)==6 | ObsModel_ez(e_i(i),0)==7 | ObsModel_ez(e_i(i),0)==9 | ObsModel_ez(e_i(i),0)==11){
        if(ObsModel_ez(e_i(i),0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        }
        if(ObsModel_ez(e_i(i),0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel_ez(e_i(i),0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel_ez(c,1)==0 | ObsModel_ez(c,1)==3 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( ObsModel_ez(e_i(i),0)==11 ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( t_iz(i,zt)>=0 & t_iz(i,zt)<n_t ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Responses
      if( ObsModel_ez(c_iz(i,0),1)==0 | ObsModel_ez(c_iz(i,0),1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
          //if( !isNA(c_iz(i,zc)) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel_ez(e_i(i),0)==0 | ObsModel_ez(e_i(i),0)==1 | ObsModel_ez(e_i(i),0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
          if(ObsModel_ez(e_i(i),0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
          if(ObsModel_ez(e_i(i),0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel_ez(e_i(i),0)==4 | ObsModel_ez(e_i(i),0)==5 | ObsModel_ez(e_i(i),0)==6 | ObsModel_ez(e_i(i),0)==7 | ObsModel_ez(e_i(i),0)==9 | ObsModel_ez(e_i(i),0)==11){
        if(ObsModel_ez(e_i(i),0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        }
        if(ObsModel_ez(e_i(i),0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel_ez(e_i(i),0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel_ez(c,1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IVECTOR(ObsModel);    // Observation model
  // Slot 0: Distribution for positive catches
  // Slot 1: Link function for encounter probabilities
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IVECTOR(c_i);         // Category for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  //  -- presence/absence
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation
  //  -- positive catch rates
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz
  PARAMETER_ARRAY(logSigmaM);   // Slots: 0=mix1 CV, 1=prob-of-mix1, 2=
  PARAMETER_VECTOR(delta_i);
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_c, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  if( ObsModel(0)==11 ){
    for(i=0; i<delta_i.size(); i++){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  // Linear predictor (pre-link) for presence/absence component
  vector<Type> P1_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi 
  vector<Type> R1_i(n_i);   
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  vector<Type> P2_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      P1_i(i) = Omega1_sc(s_i(i),c_i(i)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_i(i));
      P2_i(i) = Omega2_sc(s_i(i),c_i(i)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_i(i));
      for( int z=0; z<t_iz.row(0).size(); z++ ){
        if( t_iz(i,z)>=0 & t_iz(i,z)<n_t ){  // isNA doesn't seem to work for IMATRIX type
          P1_i(i) += beta1_ct(c_i(i),t_iz(i,z)) + Epsilon1_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(s_i(i),c_i(i),t_iz(i,z));
          P2_i(i) += beta2_ct(c_i(i),t_iz(i,z)) + Epsilon2_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(s_i(i),c_i(i),t_iz(i,z));
        }
      }
      // Responses
      if( ObsModel(1)==0 | ObsModel(1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_i(i) );
        R2_i(i) = a_i(i) * exp( P2_i(i) );
      }
      if( ObsModel(1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        R1_i(i) = Type(1.0) - exp( -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i)) );
        R2_i(i) = a_i(i)*exp(P1_i(i)) / R1_i(i) * exp( P2_i(i) );
      }
      if( ObsModel(1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_i(i) );
        R2_i(i) = exp( P2_i(i) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel(0)==0 | ObsModel(0)==1 | ObsModel(0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          LogProb1_i(i) = log( 1-R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel(0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(c_i(i),0), true);
          if(ObsModel(0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(c_i(i),0),2)/2, SigmaM(c_i(i),0), true); // log-space
          if(ObsModel(0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(c_i(i),0),2), R2_i(i)*pow(SigmaM(c_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel(0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(c_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel(0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(c_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel(0)==4 | ObsModel(0)==5 | ObsModel(0)==6 | ObsModel(0)==7 | ObsModel(0)==9 | ObsModel(0)==11){
        if(ObsModel(0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(c_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_i(i),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel(0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_i(i)), true, Options_vec(5));
        }
        if(ObsModel(0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel(0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel(0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(c_i(i),0)*delta_i(i)-pow(SigmaM(c_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(c_i(i),0)*delta_i(i)-pow(SigmaM(c_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel(4) isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel(1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel(1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -SigmaM(c,2)*exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel(1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel[1]=1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_i );
  REPORT( P2_i );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Gulf of Alaska POP for 2019 SAFE
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 10.23.19
#
#Purpose: Implementation of VAST for GOA Pacific Ocean Perch Assessment - APPORTIONED
#
#
#==================================================================================================
#NOTES:

# NEW FIT METHOD
# [1] "bias.correct: TRUE"
# [1] "n_x: 500"
# [1] "START: Wed Oct 23 23:05:39 2019"
# [1] "END: Thu Oct 24 09:13:40 2019"

#==================================================================================================
source("R/create-VAST-input-new.r")
source("R/create-Data-Geostat.r")
source("R/load-RACE-data.r")
source("R/plot-VAST-output.r")
source("R/cleanup-VAST-file.r")
source("R/run-RE-model.r") 

require(VAST)
require(TMB)
require(tidyverse)
require(FishStatsUtils)

#=======================================================================
##### SETUP INPUT DATA #####

#Generate a dataset
species.codes <- 30060
combineSpecies <- TRUE

lat_lon.def <- "start"

survey <- "GOA"
Region <- "Gulf_of_Alaska"

bias.correct <- FALSE #TARGET: TRUE

#SPATIAL SETTINGS
Method <- c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km <- 25
n_x <- c(100, 250, 500, 1000, 2000)[1] # Number of stations
Kmeans_Config <- list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
# strata.limits <- data.frame(STRATA = c("All_areas"))
# GOA Apportionment
strata.limits <- data.frame(STRATA = c("Western","Central",'Eastern'),
                            west_border = c(-Inf, -159, -147),
                            east_border = c(-159, -147, Inf))

#DERIVED OBJECTS
Version <-  "VAST_v4_4_0"
###########################
trial.file <- paste0(getwd(),"/examples/Species_Specific_Case_Studies/Test_GOA_POP/")

#MODEL SETTINGS
FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
RhoConfig = c(Beta1 = 2, Beta2 = 2, Epsilon1 = 2, Epsilon2 = 2)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

# ObsModel = c(1, 0) #Lognormal
ObsModel = c(2, 0) #Gamma
# ObsModel = c(1, 1) #Poisson-Process Link function approximating Tweedie distribution

#SPECIFY OUTPUTS
Options = c(SD_site_density = 1, SD_site_logdensity = 1,
            Calculate_Range = 1, Calculate_evenness = 1, Calculate_effective_area = 1,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)

# DEFINE SETTINGS
# settings <- make_settings(n_x=n_x, Region=Region, purpose = "index", fine_scale = FALSE,
#                           strata.limits = data.frame(STRATA = "All_areas"), zone = NA,
#                           FieldConfig=FieldConfig, RhoConfig=RhoConfig,
#                           OverdispersionConfig=OverdispersionConfig, ObsModel=ObsModel,
#                           bias.correct=bias.correct,
#                           Options=Options, use_anisotropy=TRUE, 
#                           vars_to_correct="Index_cyl", Version=Version)#,
# treat_nonencounter_as_zero, n_categories, VamConfig)

DateFile <- paste0(trial.file,"GOA POP apportioned knots_",n_x," bias.correct_", bias.correct, 
                   " Rho_",RhoConfig[1],RhoConfig[2],RhoConfig[3],RhoConfig[4],
                   " ObsModel_",ObsModel[1],ObsModel[2],"/")

# Save options for future records
# Record <- list("Version"=Version,"Method"=Method,"grid_size_km"=grid_size_km,"n_x"=n_x,"FieldConfig"=FieldConfig,
#                "RhoConfig"=RhoConfig,"OverdispersionConfig"=OverdispersionConfig,"ObsModel"=ObsModel,"Region"=Region,
#                "Species_set"=Species_set,"strata.limits"=strata.limits)
# save( Record, file=file.path(DateFile,"Record.RData"))
# capture.output( Record, file=file.path(DateFile,"Record.txt"))

#=======================================================================
##### READ IN DATA AND BUILD vAST INPUT #####
VAST_input <- create_VAST_input_new(species.codes=species.codes, combineSpecies=combineSpecies,
                                    lat_lon.def=lat_lon.def, save.Record=FALSE,
                                    Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                    Kmeans_Config=Kmeans_Config,
                                    strata.limits=strata.limits, survey=survey,
                                    DateFile=DateFile,
                                    FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                    OverdispersionConfig=OverdispersionConfig,
                                    ObsModel=ObsModel, Options=Options, Version=Version)
#Unpack
TmbData <- VAST_input$TmbData
Data_Geostat <- VAST_input$Data_Geostat
Spatial_List <- VAST_input$Spatial_List
Extrapolation_List <- VAST_input$Extrapolation_List #Becomes zeros for non-GOA
settings <- VAST_input$settings
MapDetails_List <- VAST_input$MapDetails_List

# head(Extrapolation_List$a_el)
# head(Extrapolation_List$Area_km2_x)
# head(Extrapolation_List$Data_Extrap)

#=======================================================================
##### RUN VAST #####



#Build TMB Object
#  Compilation may take some time
TmbList <- VAST::make_model(TmbData = TmbData, RunDir = DateFile,
                            Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                            Method = Method)
Obj <- TmbList[["Obj"]]

start.time <- date()

#================================================
#TESTING OPTIMIZATION: Original Call

# Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                           upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                           bias.correct = bias.correct)


#================================================
#TESTING OPTIMIZATION: Updated call with nsplit to reduce memory load and 
#                        allow running bias.cor with kt > ~300
# Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                            upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                            bias.correct = bias.correct,
#                            bias.correct.control=list(nsplit=200, split=NULL, sd=FALSE))

#================================================
#TESTING OPTIMIZATION: New Alternative Following Jim's Suggestion
#  Should limit bias correction to single vector of interst: index

# nsplit <- 200
# 
# Opt = TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                            upper = TmbList[["Upper"]], getsd = TRUE, 
#                            savedir = DateFile, bias.correct=bias.correct )

# if(bias.correct==FALSE) {
#   Opt <- TMBhelper::fit_tmb(obj = Obj, lower = TmbList[["Lower"]],
#                             upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                             bias.correct = bias.correct)#, newtonsteps=1)
# }else {
#   #NEW: Only Bias Correct Index
#   Opt <- TMBhelper::fit_tmb(obj=Obj, lower=TmbList[["Lower"]], 
#                             upper=TmbList[["Upper"]], getsd=TRUE, savedir=DateFile, 
#                             bias.correct=bias.correct, #newtonsteps=1,
#                             bias.correct.control=list(sd=TRUE, #nsplit=200, split=NULL,
#                                                       vars_to_correct="Index_cyl"))
# }

# Fitting with New Alternative ===========
fit <- fit_model(settings, Lat_i=Data_Geostat[["Lat"]], Lon_i=Data_Geostat[["Lon"]], 
                 t_iz=Data_Geostat[["Year"]], 
                 b_i=Data_Geostat[["Catch_KG"]],
                 a_i=Data_Geostat[["AreaSwept_km2"]], 
                 c_iz = rep(0, nrow(Data_Geostat)),#rep(0,length(b_i)), 
                 v_i = rep(0, nrow(Data_Geostat)),#Data_Geostat[["Vessel"]],
                 working_dir = DateFile, Xconfig_zcp = NULL,
                 covariate_data=NULL, formula = ~0, Q_ik = NULL, newtonsteps = 1,
                 silent = TRUE, run_model = TRUE, test_fit = FALSE)
Opt <- fit
# Get sdreport for Plotting ======================
# Sdreport <- TMB::sdreport( obj=Obj, par.fixed=Opt$par) #No need to run as 
Sdreport <- Opt$SD

# First SD run 
# h <- optimHess(Opt$par, Obj$fn, Obj$gr)
# SD = sdreport( obj=Obj, par.fixed=Opt$par, hessian.fixed=h )

# Determine indices
# BiasCorrNames = c("Index_cyl")
# Which = which( rownames(summary(SD,"report")) %in% BiasCorrNames )
# Which = split( Which, cut(seq_along(Which), nsplit) )
# Which = Which[sapply(Which,FUN=length)>0]


# Repeat SD with indexing
# SD = sdreport( obj=Obj, par.fixed=Opt$par, hessian.fixed=h, bias.correct=TRUE, bias.correct.control=list(sd=FALSE, split=Which, nsplit=NULL) )

#================================================


end.time <- date()
#Save output
Report = Obj$report()
Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
save(Save, file=paste0(DateFile,"Save.RData"))

#========================================================================
##### DIAGNOSTIC AND PREDICTION PLOTS #####
# Get Index
Year_Set = seq(min(Data_Geostat[,'Year']),max(Data_Geostat[,'Year']))
Years2Include = which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))


# Only works when use fit_model
plot_results(fit=fit, settings = settings, plot_set = 3,
             working_dir = DateFile, year_labels = Year_Set,
             years_to_plot = Years2Include,
             use_biascorr = bias.correct, map_list=MapDetails_List,
             category_names="GOA POP", check_residuals = TRUE, 
             projargs = "+proj=longlat", n_samples = 100)

# Plot Data and Knots
FishStatsUtils::plot_data(Extrapolation_List=Extrapolation_List, Spatial_List=Spatial_List, 
                          Data_Geostat=Data_Geostat,
                          PlotDir = paste0(DateFile, "/"), Plot1_name = "Data_and_knots.png",
                          Plot2_name = "Data_by_year.png", col = rep("red",nrow(Data_Geostat)), cex = 0.01)


# Diagnostics =====================================
# FishStatsUtils::map_hypervariance(report=TmbList[["Obj"]], Spatial_List=Spatial_List, 
#                                   method="anisotropic")


FishStatsUtils::plot_encounter_diagnostic(Report, Data_Geostat, 
                                          cutpoints_z = seq(0, 1,length = 21), 
                                          interval_width = 1.96, DirName = paste0(DateFile, "/"),
                                          PlotName = "Diag--Encounter_prob.png")

Q <- FishStatsUtils::plot_quantile_diagnostic(TmbData=TmbData, Report=Report, 
                                              DateFile=DateFile, 
                                              save_dir = paste0(DateFile, "/QQ_Fn/"),
                                              FileName_PP = "Posterior_Predictive",
                                              FileName_Phist = "Posterior_Predictive-Histogram",
                                              FileName_QQ = "Q-Q_plot", FileName_Qhist = "Q-Q_hist")

#Plot Pearson Residuals
#  Look for spatial patterns-- indication of "overshrinking"
#  Creates "maps--" files - ERROR: Error in as_mapper(.f, ...) : argument ".f" is missing, with no default

# FishStatsUtils::plot_residuals(Lat_i = Data_Geostat[,"Lat"], Lon_i = Data_Geostat[, "Lon"], TmbData = TmbData,
#                Report = Report, Q = Q, savedir = DateFile, MappingDetails = MapDetails_List[["MappingDetails"]],
#                PlotDF = MapDetails_List[["PlotDF"]], MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
#                Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
#                FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
#                Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
#                Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
#                mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8,
#                spatial_list=Spatial_List,
#                extrapolation_list=Extrapolation_List)

# Updated - NOT WORKING
FishStatsUtils::plot_residuals(Lat_i=Data_Geostat[,"Lat"], Lon_i=Data_Geostat[, "Lon"],
                               TmbData=TmbData, Report=Report, Q=Q,
                               projargs = "+proj=longlat", working_dir = DateFile,
                               spatial_list=Spatial_List, 
                               extrapolation_list=Extrapolation_List, Year_Set = Year_Set,
                               Years2Include = Years2Include)



# Predictions =====================================
# Plot Biomass Index and generate .xlsx
FishStatsUtils::plot_biomass_index(TmbData=TmbData, 
                                   Sdreport = Opt[["SD"]], 
                                   Year_Set = Year_Set, 
                                   Years2Include = Years2Include,
                                   use_biascorr=bias.correct,
                                   DirName = DateFile)

# Plot shifts in distribution and area occupied
FishStatsUtils::plot_range_index(Sdreport=Sdreport, Report=Report, 
                                 TmbData=TmbData, Year_Set = Year_Set,
                                 PlotDir = paste0(DateFile, "/"), 
                                 FileName_COG = paste0(DateFile,"/center_of_gravity.png"), 
                                 FileName_Area = paste0(DateFile,"/Area.png"), 
                                 FileName_EffArea = paste0(DateFile,"/Effective_Area.png"), 
                                 Znames = rep("", ncol(TmbData$Z_xm)),
                                 use_biascorr = bias.correct, category_names = NULL, interval_width = 1)

#Direction of "geometric anisotropy"
SpatialDeltaGLMM::PlotAniso_Fn(FileName = paste0(DateFile,"Aniso.png"),
                               Report = Report, TmbData = TmbData)

# Plot Maps ======================
# plot_maps(plot_set = 1, Report=Report, PlotDF, Sdreport = Opt[["SD"]],
#           TmbData = TmbData, projargs = "+proj=longlat", Panel = "Category",
#           Year_Set = Year_Set, Years2Include = Years2Include, category_names = NULL,
#           quiet = FALSE, working_dir = DateFile, MapSizeRatio=c(2,4),
#           n_cells=10)

# Plot Results ===================



# plot_maps(plot_set = 3, MappingDetails=MapDetails_List, Report=Report, #PlotDF,
#           Sdreport = Opt[["SD"]], TmbData=TmbData, 
#           # Xlim, Ylim, Nknots = n_x,
#           Panel = "Category", MapSizeRatio = c(`Width(in)` = 4, `Height(in)` = 4),
#           Res = 200, FileName = DateFile, Year_Set = Year_Set,
#           Years2Include = Years2Include, Rescale = FALSE, Rotate = 0,
#           Format = "png", zone = NA, Cex = 0.01, add = FALSE,
#           category_names = NULL, textmargin = NULL, pch = NULL,
#           Legend = list(use = FALSE, x = c(10, 30), y = c(10, 30)),
#           mfrow = NULL, plot_legend_fig = TRUE)
#========================================================================
##### CLEAN UP MODEL FILES #####
# cleanup_VAST_file(DateFile, Version=Version)

print(paste('bias.correct:',bias.correct))
print(paste('n_x:',n_x))
print(paste('START:',start.time))
print(paste('END:',end.time))


#========================================================================
##### APPORTIONMENT #####












#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Gulf of Alaska POP for 2019 SAFE
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 10.23.19
#
#Purpose: Implementation of VAST for GOA Pacific Ocean Perch Assessment
#
#
#==================================================================================================
#NOTES:

# NEW FIT METHOD
# [1] "bias.correct: TRUE"
# [1] "n_x: 500"
# [1] "START: Wed Oct 23 23:05:39 2019"
# [1] "END: Thu Oct 24 09:13:40 2019"

#==================================================================================================
source("R/create-VAST-input-new.r")
source("R/create-Data-Geostat.r")
source("R/load-RACE-data.r")
source("R/plot-VAST-output.r")
source("R/cleanup-VAST-file.r")
source("R/run-RE-model.r") 

require(VAST)
require(TMB)
require(tidyverse)
require(FishStatsUtils)

#=======================================================================
##### SETUP INPUT DATA #####

#Generate a dataset
species.codes <- 30060
combineSpecies <- TRUE

lat_lon.def <- "start"

survey <- "GOA"
Region <- "Gulf_of_Alaska"

bias.correct <- FALSE #TARGET TRUE

#SPATIAL SETTINGS
Method <- c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km <- 25
n_x <- c(100, 250, 500, 1000, 2000)[2] #TARGET: 3 (500)
Kmeans_Config <- list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("All_areas"))


#DERIVED OBJECTS
Version <-  "VAST_v4_4_0"
###########################
trial.file <- paste0(getwd(),"/examples/Species_Specific_Case_Studies/Test_GOA_POP/")

#MODEL SETTINGS
FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

# Standard Delta
# ObsModel = c(1, 0) #Lognormal
# ObsModel = c(2, 0) #Gamma
# Poisson-Link Delta
# ObsModel = c(1, 1) #Lornormal
ObsModel = c(2, 1) #Gamma

#SPECIFY OUTPUTS
Options = c(SD_site_density = 1, SD_site_logdensity = 1,
            Calculate_Range = 0, Calculate_evenness = 0, Calculate_effective_area = 0,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)

# DEFINE SETTINGS
# settings <- make_settings(n_x=n_x, Region=Region, purpose = "index", fine_scale = FALSE,
#                           strata.limits = data.frame(STRATA = "All_areas"), zone = NA,
#                           FieldConfig=FieldConfig, RhoConfig=RhoConfig,
#                           OverdispersionConfig=OverdispersionConfig, ObsModel=ObsModel,
#                           bias.correct=bias.correct,
#                           Options=Options, use_anisotropy=TRUE, 
#                           vars_to_correct="Index_cyl", Version=Version)#,
# treat_nonencounter_as_zero, n_categories, VamConfig)

DateFile <- paste0(trial.file,"GOA POP knots_",n_x," bias.correct_", bias.correct, 
                   " Rho_",RhoConfig[1],RhoConfig[2],RhoConfig[3],RhoConfig[4],
                   " ObsModel_",ObsModel[1],ObsModel[2],"/")

# Save options for future records
# Record <- list("Version"=Version,"Method"=Method,"grid_size_km"=grid_size_km,"n_x"=n_x,"FieldConfig"=FieldConfig,
#                "RhoConfig"=RhoConfig,"OverdispersionConfig"=OverdispersionConfig,"ObsModel"=ObsModel,"Region"=Region,
#                "Species_set"=Species_set,"strata.limits"=strata.limits)
# save( Record, file=file.path(DateFile,"Record.RData"))
# capture.output( Record, file=file.path(DateFile,"Record.txt"))

#=======================================================================
##### READ IN DATA AND BUILD vAST INPUT #####
VAST_input <- create_VAST_input_new(species.codes=species.codes, combineSpecies=combineSpecies,
                                    lat_lon.def=lat_lon.def, save.Record=FALSE,
                                    Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                    Kmeans_Config=Kmeans_Config,
                                    strata.limits=strata.limits, survey=survey,
                                    DateFile=DateFile,
                                    FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                    OverdispersionConfig=OverdispersionConfig,
                                    ObsModel=ObsModel, Options=Options, Version=Version)
#Unpack
TmbData <- VAST_input$TmbData
Data_Geostat <- VAST_input$Data_Geostat
Spatial_List <- VAST_input$Spatial_List
Extrapolation_List <- VAST_input$Extrapolation_List #Becomes zeros for non-GOA
settings <- VAST_input$settings
MapDetails_List <- VAST_input$MapDetails_List

# head(Extrapolation_List$a_el)
# head(Extrapolation_List$Area_km2_x)
# head(Extrapolation_List$Data_Extrap)

#=======================================================================
##### RUN VAST #####



#Build TMB Object
#  Compilation may take some time
TmbList <- VAST::make_model(TmbData = TmbData, RunDir = DateFile,
                            Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                            Method = Method)
Obj <- TmbList[["Obj"]]

start.time <- date()

#================================================
#TESTING OPTIMIZATION: Original Call

# Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                           upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                           bias.correct = bias.correct)


#================================================
#TESTING OPTIMIZATION: Updated call with nsplit to reduce memory load and 
#                        allow running bias.cor with kt > ~300
# Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                            upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                            bias.correct = bias.correct,
#                            bias.correct.control=list(nsplit=200, split=NULL, sd=FALSE))

#================================================
#TESTING OPTIMIZATION: New Alternative Following Jim's Suggestion
#  Should limit bias correction to single vector of interst: index

# nsplit <- 200
# 
# Opt = TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                            upper = TmbList[["Upper"]], getsd = TRUE, 
#                            savedir = DateFile, bias.correct=bias.correct )

# if(bias.correct==FALSE) {
#   Opt <- TMBhelper::fit_tmb(obj = Obj, lower = TmbList[["Lower"]],
#                             upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                             bias.correct = bias.correct)#, newtonsteps=1)
# }else {
#   #NEW: Only Bias Correct Index
#   Opt <- TMBhelper::fit_tmb(obj=Obj, lower=TmbList[["Lower"]], 
#                             upper=TmbList[["Upper"]], getsd=TRUE, savedir=DateFile, 
#                             bias.correct=bias.correct, #newtonsteps=1,
#                             bias.correct.control=list(sd=TRUE, #nsplit=200, split=NULL,
#                                                       vars_to_correct="Index_cyl"))
# }

# Fitting with New Alternative ===========
fit <- fit_model(settings, Lat_i=Data_Geostat[["Lat"]], Lon_i=Data_Geostat[["Lon"]], 
                 t_iz=Data_Geostat[["Year"]], 
                 b_i=Data_Geostat[["Catch_KG"]],
                 a_i=Data_Geostat[["AreaSwept_km2"]], 
                 c_iz = rep(0, nrow(Data_Geostat)),#rep(0,length(b_i)), 
                 v_i = rep(0, nrow(Data_Geostat)),#Data_Geostat[["Vessel"]],
                 working_dir = DateFile, Xconfig_zcp = NULL,
                 covariate_data=NULL, formula = ~0, Q_ik = NULL, newtonsteps = 1,
                 silent = TRUE, run_model = TRUE, test_fit = FALSE)
Opt <- fit

# Get sdreport for Plotting ======================
# Sdreport <- TMB::sdreport( obj=Obj, par.fixed=Opt$par) #No need to run as 
Sdreport <- Opt$SD

# First SD run 
# h <- optimHess(Opt$par, Obj$fn, Obj$gr)
# SD = sdreport( obj=Obj, par.fixed=Opt$par, hessian.fixed=h )

# Determine indices
# BiasCorrNames = c("Index_cyl")
# Which = which( rownames(summary(SD,"report")) %in% BiasCorrNames )
# Which = split( Which, cut(seq_along(Which), nsplit) )
# Which = Which[sapply(Which,FUN=length)>0]


# Repeat SD with indexing
# SD = sdreport( obj=Obj, par.fixed=Opt$par, hessian.fixed=h, bias.correct=TRUE, bias.correct.control=list(sd=FALSE, split=Which, nsplit=NULL) )

#================================================


end.time <- date()
#Save output
Report = Obj$report()
Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
save(Save, file=paste0(DateFile,"Save.RData"))

#========================================================================
##### DIAGNOSTIC AND PREDICTION PLOTS #####
# Get Index
Year_Set = seq(min(Data_Geostat[,'Year']),max(Data_Geostat[,'Year']))
Years2Include = which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))


# Only works when use fit_model
plot_results(fit=fit, settings = settings, plot_set = 3,
             working_dir = DateFile, year_labels = Year_Set,
             years_to_plot = Years2Include,
             use_biascorr = bias.correct, map_list=MapDetails_List,
             category_names="GOA POP", check_residuals = TRUE, projargs = "+proj=longlat",
             n_samples = 100)

# Plot Data and Knots
FishStatsUtils::plot_data(Extrapolation_List=Extrapolation_List, Spatial_List=Spatial_List, 
                          Data_Geostat=Data_Geostat,
                          PlotDir = paste0(DateFile, "/"), Plot1_name = "Data_and_knots.png",
                          Plot2_name = "Data_by_year.png", col = rep("red",nrow(Data_Geostat)), cex = 0.01)


# Diagnostics =====================================
# FishStatsUtils::map_hypervariance(report=TmbList[["Obj"]], Spatial_List=Spatial_List, 
#                                   method="anisotropic")


FishStatsUtils::plot_encounter_diagnostic(Report, Data_Geostat, 
                                          cutpoints_z = seq(0, 1,length = 21), 
                                          interval_width = 1.96, DirName = paste0(DateFile, "/"),
                                          PlotName = "Diag--Encounter_prob.png")

Q <- FishStatsUtils::plot_quantile_diagnostic(TmbData=TmbData, Report=Report, 
                                              DateFile=DateFile, 
                                              save_dir = paste0(DateFile, "/QQ_Fn/"),
                                              FileName_PP = "Posterior_Predictive",
                                              FileName_Phist = "Posterior_Predictive-Histogram",
                                              FileName_QQ = "Q-Q_plot", FileName_Qhist = "Q-Q_hist")

#Plot Pearson Residuals
#  Look for spatial patterns-- indication of "overshrinking"
#  Creates "maps--" files - ERROR: Error in as_mapper(.f, ...) : argument ".f" is missing, with no default

# FishStatsUtils::plot_residuals(Lat_i = Data_Geostat[,"Lat"], Lon_i = Data_Geostat[, "Lon"], TmbData = TmbData,
#                Report = Report, Q = Q, savedir = DateFile, MappingDetails = MapDetails_List[["MappingDetails"]],
#                PlotDF = MapDetails_List[["PlotDF"]], MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
#                Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
#                FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
#                Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
#                Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
#                mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8,
#                spatial_list=Spatial_List,
#                extrapolation_list=Extrapolation_List)

# Updated - NOT WORKING
FishStatsUtils::plot_residuals(Lat_i=Data_Geostat[,"Lat"], Lon_i=Data_Geostat[, "Lon"],
                               TmbData=TmbData, Report=Report, Q=Q,
                               projargs = "+proj=longlat", working_dir = DateFile,
                               spatial_list=Spatial_List, 
                               extrapolation_list=Extrapolation_List, Year_Set = Year_Set,
                               Years2Include = Years2Include)



# Predictions =====================================
# Plot Biomass Index and generate .xlsx
FishStatsUtils::plot_biomass_index(TmbData=TmbData, 
                                   Sdreport = Opt[["SD"]], 
                                   Year_Set = Year_Set, 
                                   Years2Include = Years2Include,
                                   use_biascorr=bias.correct,
                                   DirName = DateFile)

# Plot shifts in distribution and area occupied
FishStatsUtils::plot_range_index(Sdreport=Sdreport, Report=Report, 
                                 TmbData=TmbData, Year_Set = Year_Set,
                                 PlotDir = paste0(DateFile, "/"), 
                                 FileName_COG = paste0(DateFile,"/center_of_gravity.png"), 
                                 FileName_Area = paste0(DateFile,"/Area.png"), 
                                 FileName_EffArea = paste0(DateFile,"/Effective_Area.png"), 
                                 Znames = rep("", ncol(TmbData$Z_xm)),
                                 use_biascorr = bias.correct, category_names = NULL, interval_width = 1)

#Direction of "geometric anisotropy"
SpatialDeltaGLMM::PlotAniso_Fn(FileName = paste0(DateFile,"Aniso.png"),
                               Report = Report, TmbData = TmbData)

# Plot Maps ======================
# plot_maps(plot_set = 1, Report=Report, PlotDF, Sdreport = Opt[["SD"]],
#           TmbData = TmbData, projargs = "+proj=longlat", Panel = "Category",
#           Year_Set = Year_Set, Years2Include = Years2Include, category_names = NULL,
#           quiet = FALSE, working_dir = DateFile, MapSizeRatio=c(2,4),
#           n_cells=10)

# Plot Results ===================



# plot_maps(plot_set = 3, MappingDetails=MapDetails_List, Report=Report, #PlotDF,
#           Sdreport = Opt[["SD"]], TmbData=TmbData, 
#           # Xlim, Ylim, Nknots = n_x,
#           Panel = "Category", MapSizeRatio = c(`Width(in)` = 4, `Height(in)` = 4),
#           Res = 200, FileName = DateFile, Year_Set = Year_Set,
#           Years2Include = Years2Include, Rescale = FALSE, Rotate = 0,
#           Format = "png", zone = NA, Cex = 0.01, add = FALSE,
#           category_names = NULL, textmargin = NULL, pch = NULL,
#           Legend = list(use = FALSE, x = c(10, 30), y = c(10, 30)),
#           mfrow = NULL, plot_legend_fig = TRUE)
#========================================================================
##### CLEAN UP MODEL FILES #####
# cleanup_VAST_file(DateFile, Version=Version)

print(paste('bias.correct:',bias.correct))
print(paste('n_x:',n_x))
print(paste('START:',start.time))
print(paste('END:',end.time))


#========================================================================
##### APPORTIONMENT #####












#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Gulf of Alaska Pollock for CIE Review
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 5.22.17
#
#Purpose: Example implementation of VAST model for GOA Pollock
#
#
#==================================================================================================
#NOTES:
#Memory ProfilingJust


# Using nsplit=200
# [1] "bias.correct: TRUE"
# [1] "n_x: 1000"
# [1] "START: Mon Mar 05 10:18:23 2018"
# [1] "END: Mon Mar 05 17:28:07 2018"

#Using Jim's new method
# [1] "bias.correct: TRUE"
# [1] "n_x: 1000"
# [1] "START: Tue Mar 06 10:02:39 2018"
# [1] "END: Tue Mar 06 11:24:57 2018"

# NEW for POP
# [1] "bias.correct: TRUE"
# [1] "n_x: 250"
# [1] "START: Sun Sep 15 23:41:12 2019"
# [1] "END: Mon Sep 16 03:48:31 2019"

#==================================================================================================
 source("R/create-VAST-input-new.r")
 source("R/create-Data-Geostat.r")
 source("R/load-RACE-data.r")
 source("R/plot-VAST-output.r")
 source("R/cleanup-VAST-file.r")
 source("R/run-RE-model.r") 

require(VAST)
require(TMB)
require(tidyverse)
require(FishStatsUtils)

#=======================================================================
##### SETUP INPUT DATA #####

#Generate a dataset
species.codes <- 30060 #POP
lat_lon.def <- "start"

survey <- "GOA"
Region <- "Gulf_of_Alaska"

bias.correct <- TRUE

#SPATIAL SETTINGS
Method <- c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km <- 25
n_x <- c(100, 250, 500, 1000, 2000)[1] # Number of stations
Kmeans_Config <- list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("All_areas"))


#DERIVED OBJECTS
Version <-  "VAST_v4_0_0"
###########################
trial.file <- paste0(getwd(),"/examples/Species_Specific_Case_Studies/Test_GOA_POP")

#MODEL SETTINGS
FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

# ObsModel = c(1, 0) #Lognormal
ObsModel = c(2, 0) #Gamma
# ObsModel = c(1, 1) #Poisson-Process Link function approximating Tweedie distribution

#SPECIFY OUTPUTS
Options = c(SD_site_density = 1, SD_site_logdensity = 1,
            Calculate_Range = 1, Calculate_evenness = 1, Calculate_effective_area = 1,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)

# DEFINE SETTINGS
settings <- make_settings(n_x=n_x, Region=Region, purpose = "index", fine_scale = FALSE,
                          strata.limits = data.frame(STRATA = "All_areas"), zone = NA,
                          FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                          OverdispersionConfig=OverdispersionConfig, ObsModel=ObsModel,
                          bias.correct=bias.correct,
                          Options=Options, use_anisotropy=TRUE, 
                          vars_to_correct="Index_cyl", Version=Version)#,
                          # treat_nonencounter_as_zero, n_categories, VamConfig)

DateFile <- paste0(trial.file,"/GOA POP knots_",n_x," bias.correct_", bias.correct, " Rho_",RhoConfig[1],RhoConfig[2],RhoConfig[3],RhoConfig[4], " ObsModel", ObsModel[1], ObsModel[2], "/")

# Save options for future records
# Record <- list("Version"=Version,"Method"=Method,"grid_size_km"=grid_size_km,"n_x"=n_x,"FieldConfig"=FieldConfig,
#                "RhoConfig"=RhoConfig,"OverdispersionConfig"=OverdispersionConfig,"ObsModel"=ObsModel,"Region"=Region,
#                "Species_set"=Species_set,"strata.limits"=strata.limits)
# save( Record, file=file.path(DateFile,"Record.RData"))
# capture.output( Record, file=file.path(DateFile,"Record.txt"))

#=======================================================================
##### READ IN DATA AND BUILD vAST INPUT #####
VAST_input <- create_VAST_input_new(species.codes=species.codes, combineSpecies=FALSE,
                                lat_lon.def=lat_lon.def, save.Record=FALSE,
                                Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                Kmeans_Config=Kmeans_Config,
                                strata.limits=strata.limits, survey=survey,
                                DateFile=DateFile,
                                FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                OverdispersionConfig=OverdispersionConfig,
                                ObsModel=ObsModel, Options=Options, Version=Version)
#Unpack
TmbData <- VAST_input$TmbData
Data_Geostat <- VAST_input$Data_Geostat
Spatial_List <- VAST_input$Spatial_List
Extrapolation_List <- VAST_input$Extrapolation_List #Becomes zeros for non-GOA
settings <- VAST_input$settings
MapDetails_List <- VAST_input$MapDetails_List

# head(Extrapolation_List$a_el)
# head(Extrapolation_List$Area_km2_x)
# head(Extrapolation_List$Data_Extrap)

#=======================================================================
##### RUN VAST #####



#Build TMB Object
#  Compilation may take some time
TmbList <- VAST::make_model(TmbData = TmbData, RunDir = DateFile,
                                Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                                Method = Method)
Obj <- TmbList[["Obj"]]

start.time <- date()

#================================================
#TESTING OPTIMIZATION: Original Call

# Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                           upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                           bias.correct = bias.correct)


#================================================
#TESTING OPTIMIZATION: Updated call with nsplit to reduce memory load and 
#                        allow running bias.cor with kt > ~300
# Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                            upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
#                            bias.correct = bias.correct,
#                            bias.correct.control=list(nsplit=200, split=NULL, sd=FALSE))

#================================================
#TESTING OPTIMIZATION: New Alternative Following Jim's Suggestion
#  Should limit bias correction to single vector of interst: index

# nsplit <- 200
# 
# Opt = TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
#                            upper = TmbList[["Upper"]], getsd = TRUE, 
#                            savedir = DateFile, bias.correct=bias.correct )

if(bias.correct==FALSE) {
  Opt <- TMBhelper::fit_tmb(obj = Obj, lower = TmbList[["Lower"]],
                             upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
                             bias.correct = bias.correct)#, newtonsteps=1)
}else {
  #NEW: Only Bias Correct Index
  Opt <- TMBhelper::fit_tmb(obj=Obj, lower=TmbList[["Lower"]], 
                             upper=TmbList[["Upper"]], getsd=TRUE, savedir=DateFile, 
                             bias.correct=bias.correct,# newtonsteps=01,
                             bias.correct.control=list(sd=TRUE, #nsplit=200, split=NULL,
                                                       vars_to_correct="Index_cyl"))
}

# Get sdreport for Plotting ======================
# Sdreport <- TMB::sdreport( obj=Obj, par.fixed=Opt$par) #No need to run as 
Sdreport <- Opt$SD

# First SD run 
# h <- optimHess(Opt$par, Obj$fn, Obj$gr)
# SD = sdreport( obj=Obj, par.fixed=Opt$par, hessian.fixed=h )

# Determine indices
# BiasCorrNames = c("Index_cyl")
# Which = which( rownames(summary(SD,"report")) %in% BiasCorrNames )
# Which = split( Which, cut(seq_along(Which), nsplit) )
# Which = Which[sapply(Which,FUN=length)>0]


# Repeat SD with indexing
# SD = sdreport( obj=Obj, par.fixed=Opt$par, hessian.fixed=h, bias.correct=TRUE, bias.correct.control=list(sd=FALSE, split=Which, nsplit=NULL) )

#================================================


end.time <- date()
#Save output
Report = Obj$report()
Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
save(Save, file=paste0(DateFile,"Save.RData"))

#========================================================================
##### DIAGNOSTIC AND PREDICTION PLOTS #####
# plot_VAST_output(Opt, Report, DateFile, survey, TmbData, Data_Geostat, Extrapolation_List, Spatial_List)

# plot_results(Opt, settings, plot_set = 3, working_dir = paste0(DateFile,"/"), 
#              # year_labels = Opt$year_labels,
#              # years_to_plot = Opt$years_to_plot,
#              use_biascorr = bias.correct, map_list)

# 

# Error in as_mapper(.f, ...) : argument ".f" is missing, with no default
plot_data(Extrapolation_List=Extrapolation_List, Spatial_List=Spatial_List, Data_Geostat=Data_Geostat,
          PlotDir = paste0(DateFile, "/"), Plot1_name = "Data_and_knots.png",
          Plot2_name = "Data_by_year.png", col = rep("red",nrow(Data_Geostat)), cex = 0.01)


# Diagnostics =====================================
plot_encounter_diagnostic(Report, Data_Geostat, cutpoints_z = seq(0, 1,length = 21), 
                          interval_width = 1.96, DirName = paste0(DateFile, "/"),
                          PlotName = "Diag--Encounter_prob.png")

Q <- plot_quantile_diagnostic(TmbData=TmbData, Report=Report, DateFile=DateFile, 
                         save_dir = paste0(DateFile, "/QQ_Fn/"),
                         FileName_PP = "Posterior_Predictive",
                         FileName_Phist = "Posterior_Predictive-Histogram",
                         FileName_QQ = "Q-Q_plot", FileName_Qhist = "Q-Q_hist")

#Plot Pearson Residuals
#  Look for spatial patterns-- indication of "overshrinking"
#  Creates "maps--" files - ERROR: Error in as_mapper(.f, ...) : argument ".f" is missing, with no default

plot_residuals(Lat_i = Data_Geostat[,"Lat"], Lon_i = Data_Geostat[, "Lon"], TmbData = TmbData,
               Report = Report, Q = Q, savedir = DateFile, MappingDetails = MapDetails_List[["MappingDetails"]],
               PlotDF = MapDetails_List[["PlotDF"]], MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
               Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
               FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
               Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
               Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
               mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8)





# Predictions =====================================

# Get Index
Year_Set = seq(min(Data_Geostat[,'Year']),max(Data_Geostat[,'Year']))
Years2Include = which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))

Index = SpatialDeltaGLMM::PlotIndex_Fn(DirName = DateFile,
                                       TmbData = TmbData, Sdreport = Opt[["SD"]],
                                       Year_Set = Year_Set,
                                       Years2Include = Years2Include, 
                                       use_biascorr = bias.correct)

# Plot shifts in distribution and area occupied
plot_range_index(Sdreport, Report, TmbData, Year_Set = NULL,
                 PlotDir = paste0(DateFile, "/"), 
                 FileName_COG = paste0(DateFile,"/center_of_gravity.png"), 
                 FileName_Area = paste0(DateFile,"/Area.png"), 
                 FileName_EffArea = paste0(DateFile,"/Effective_Area.png"), 
                 Znames = rep("", ncol(TmbData$Z_xm)),
                 use_biascorr = bias.correct, category_names = NULL, interval_width = 1)

#Direction of "geometric anisotropy"
SpatialDeltaGLMM::PlotAniso_Fn(FileName = paste0(DateFile,"Aniso.png"),
                                 Report = Report, TmbData = TmbData)

# plot_maps(plot_set = 3, MappingDetails, Report, #PlotDF,
#           Sdreport = NULL, Xlim, Ylim, TmbData, Nknots = n_x,
#           Panel = "Category", MapSizeRatio = c(`Width(in)` = 4, `Height(in)` = 4),
#           Res = 200, FileName = paste0(DateFile, "/"), Year_Set = NULL,
#           Years2Include = NULL, Rescale = FALSE, Rotate = 0,
#           Format = "png", zone = NA, Cex = 0.01, add = FALSE,
#           category_names = NULL, textmargin = NULL, pch = NULL,
#           Legend = list(use = FALSE, x = c(10, 30), y = c(10, 30)),
#           mfrow = NULL, plot_legend_fig = TRUE)
#========================================================================
##### CLEAN UP MODEL FILES #####
# cleanup_VAST_file(DateFile, Version=Version)

print(paste('bias.correct:',bias.correct))
print(paste('n_x:',n_x))
print(paste('START:',start.time))
print(paste('END:',end.time))


#========================================================================
##### APPORTIONMENT #####














# 
# 
# #========================================================================
# ##### DIAGNOSTIC PLOTS #####
# 
# #Plot spatial distribution of data
# SpatialDeltaGLMM::Plot_data_and_knots(Extrapolation_List = Extrapolation_List,
#                                       Spatial_List = Spatial_List, Data_Geostat = Data_Geostat,
#                                       PlotDir = DateFile)
# 
# #Diagnostics for Encounter Probability
# #  "Diag--Encounter_prob"
# Enc_prob = SpatialDeltaGLMM::Check_encounter_prob(Report = Report,
#                                                   Data_Geostat = Data_Geostat,
#                                                   DirName = DateFile)
# 
# #Diagnostics for positive-catch-rate component
# Q = SpatialDeltaGLMM::QQ_Fn(TmbData = TmbData, Report = Report,
#                             FileName_PP = paste0(DateFile, "Posterior_Predictive.jpg"),
#                             FileName_Phist = paste0(DateFile, "Posterior_Predictive-Histogram.jpg"),
#                             FileName_QQ = paste0(DateFile, "Q-Q_plot.jpg"),
#                             FileName_Qhist = paste0(DateFile, "Q-Q_hist.jpg"))
# 
# 
# #Diagnostics for plotting residuals on a map
# 
# 
# MapDetails_List = SpatialDeltaGLMM::MapDetails_Fn( "Region"=Region,
#                                                    "NN_Extrap"=Spatial_List$PolygonList$NN_Extrap,
#                                                    "Extrapolation_List"=Extrapolation_List )
# 
# #Which Years to Include
# Year_Set = seq(min(Data_Geostat[,'Year']),max(Data_Geostat[,'Year']))
# Years2Include = which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))
# 
# #Or just include years with observations
# 
# #Plot Pearson Residuals
# #  Look for spatial patterns-- indication of "overshrinking"
# #  Creates "maps--" files
# SpatialDeltaGLMM:::plot_residuals(Lat_i = Data_Geostat[,"Lat"], Lon_i = Data_Geostat[, "Lon"], TmbData = TmbData,
#                                   Report = Report, Q = Q, savedir = DateFile, MappingDetails = MapDetails_List[["MappingDetails"]],
#                                   PlotDF = MapDetails_List[["PlotDF"]], MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
#                                   Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
#                                   FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
#                                   Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
#                                   Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
#                                   mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8)
# 
# 
# 
# 
# #========================================================================
# ##### MODEL OUTPUT PLOTS #####
# 
# #Direction of "geometric anisotropy"
# SpatialDeltaGLMM::PlotAniso_Fn(FileName = paste0(DateFile,"Aniso.png"),
#                                Report = Report, TmbData = TmbData)
# 
# #Density Surface for Each Year -- "Dens"
# SpatialDeltaGLMM::PlotResultsOnMap_Fn(plot_set = c(3),
#                                       MappingDetails = MapDetails_List[["MappingDetails"]],
#                                       Report = Report, Sdreport = Opt$SD, PlotDF = MapDetails_List[["PlotDF"]],
#                                       MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
#                                       Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
#                                       FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
#                                       Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
#                                       Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
#                                       mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8,
#                                       plot_legend_fig = TRUE)
# 
# 
# 
# #Generate Index of Abundance
# 
# Index = SpatialDeltaGLMM::PlotIndex_Fn(DirName = DateFile,
#                                        TmbData = TmbData, Sdreport = Opt[["SD"]],
#                                        Year_Set = Year_Set,
#                                        Years2Include = Years2Include, 
#                                        use_biascorr = TRUE)
# 
# idx <- Index$Table
# 
# 
# dev.off()
# #Plotting 
# 
# yrs.surv <- Year_Set[Years2Include]
# x.lim <- c(min(yrs.surv), max(yrs.surv))
# up.sd <- idx$Estimate_metric_tons + idx$SD_mt
# low.sd <- idx$Estimate_metric_tons - idx$SD_mt
# y.lim <- c(min(low.sd), max(up.sd))
# 
# loc.yrs <- which(idx$Year %in% yrs.surv)
# 
# 
# plot(x=NULL, y=NULL, xlim=x.lim, ylim=y.lim, ylab='Survey Estimate (metric Tons)', xlab='Year',
#      main='Gulf of Alaska\nNorthern Rockfish Survey Index')
# 
# polygon(x=c(yrs.surv, rev(yrs.surv)), y=c(low.sd[loc.yrs],rev(up.sd[loc.yrs])), col='lightblue', border=FALSE)
# lines(x=yrs.surv, y=idx$Estimate_metric_tons[loc.yrs], col='red')
# points(x=yrs.surv, y=idx$Estimate_metric_tons[loc.yrs], pch=21, bg='red')
# grid(col='black')
# 
# 
# #Center of gravity and range expansion/contraction
# #  For some reason I can't actually change the years to plot 
# SpatialDeltaGLMM::Plot_range_shifts(Report = Report,
#                                     TmbData = TmbData, Sdreport = Opt[["SD"]], Znames = colnames(TmbData$Z_xm),
#                                     PlotDir = DateFile, Year_Set = Year_Set)

#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vf.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        SCALE( AR1(L_z(1)), exp(L_z(0)) ).simulate(Tmp_c);
        eta_vf.row(v) = Tmp_c;
      }
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        eta_vf(v,f) = rnorm( Type(0.0), Type(1.0) );
      }
    }}
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  vector<Type> gmrf_s(n_s);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(c) = gmrf_s + gmrf_mean_sf.col(c);
      }
      // Rescale
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    // Simulate new values when using obj.simulate()
    if(isDouble<Type>::value && of->do_simulate) {
      SEPARABLE( AR1(L_z(1)), gmrf_Q ).simulate(gmrf_input_sf);
      gmrf_input_sf += gmrf_input_sf;
    }
    // Rescale
    logtau = L_z(0) - logkappa;  //
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    for( int f=0; f<n_f; f++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(f) = gmrf_s + gmrf_mean_sf.col(f);
      }
    }
    // Rescale
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j;
  //Type pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on beta1 (year intercepts for 1st linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- AR1 on beta2 (year intercepts for 2nd linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_INTEGER(include_data);   // Always use TRUE except for internal usage to extract GRMF normalization when turn off GMRF normalization in CPP
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SE for Index_xctl
  // Slot 1: Calculate SE for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: Calculate SE for D_i (expected density for every observation)
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  // Slot 8: Calculate proportions and SE
  // Slot 9: Include normalization in GMRF PDF
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,t,c;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( (Options_vec(7)==0) & (Options_vec(0)==0) ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( (Options_vec(7)==0) & (Options_vec(0)==1) ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF( Q1, bool(Options(9)) );
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0), this);
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF( Q2, bool(Options(9)) );
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2), this);
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
  }

  // Normalization of GMRFs to normalize during outer-optimization step in R
  Type jnll_GMRF = jnll_comp(0) + jnll_comp(1) + jnll_comp(2) + jnll_comp(3);
  if( include_data == 0 ){
    return( jnll_GMRF );
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4), this );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5), this );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta1_ct(c,t) = rnorm( Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1) );
      }
    }}
  }
  if( Options_vec(3)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta2_ct(c,t) = rnorm( Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2) );
      }
    }}
  }

  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( (ObsModel_ez(e_i(i),0)==11) | (ObsModel_ez(e_i(i),0)==14) ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        delta_i(i) = rnorm( Type(0.0), Type(1.0) );
      }
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  LogProb1_i.setZero();
  LogProb2_i.setZero();
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( (t_iz(i,zt)>=0) & (t_iz(i,zt)<n_t) ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Apply link function to calculate responses
      if( (ObsModel_ez(c_iz(i,0),1)==0) | (ObsModel_ez(c_iz(i,0),1)==3) ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if( (ObsModel_ez(e_i(i),0)==0) | (ObsModel_ez(e_i(i),0)==1) | (ObsModel_ez(e_i(i),0)==2) ){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0){
            LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rnorm( R2_i(i), SigmaM(e_i(i),0) );
            }
          }
          if(ObsModel_ez(e_i(i),0)==1){
            LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = exp(rnorm( log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0) ));
            }
          }
          if(ObsModel_ez(e_i(i),0)==2){
            LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rgamma( 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2) );
            }
          }
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Likelihood #2 for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      ///// Likelihood for models with discrete support
      // Zero-inflated negative binomial (not numerically stable!)
      if(ObsModel_ez(e_i(i),0)==5){
        var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rnbinom2( R2_i(i), var_i(i) );
          }
        }
      }
      // Conway-Maxwell-Poisson
      if(ObsModel_ez(e_i(i),0)==6){
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Poisson
      if(ObsModel_ez(e_i(i),0)==7){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i) );
          }
        }
      }
      // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
        /// Doesn't appear stable given spatial or spatio-temporal variation
      if(ObsModel_ez(e_i(i),0)==9){
        vector<Type> logdBinPois(4);
        logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
        logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
        for(int j=3; j<=10; j++){
          logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
        }
        logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
        if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
        if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
        if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
        if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Lognormal Poisson
      if(ObsModel_ez(e_i(i),0)==11){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
          }
        }
      }
      // Non-zero-inflated Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==12){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
        }
      }
      // Non-zero-inflated Bernoulli using cloglog link from 1st lilnear predict
      if(ObsModel_ez(e_i(i),0)==13){
        if( b_i(i)==0 ){
          LogProb2_i(i) = dpois(Type(0), R1_i(i), true);
        }else{
          LogProb2_i(i) = logspace_sub( log(Type(1.0)), dpois(Type(0), R1_i(i), true) );
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = 1;
          }
        }
      }
      // Non-zero-inflated Lognormal-Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==14){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
        }
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( (t_yz(y,z)>=0) & (t_yz(y,z)<n_t) ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( (ObsModel_ez(c,1)==0) | (ObsModel_ez(c,1)==3) ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );

  SIMULATE{
    REPORT( b_i );
  }

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }
  if( Options(3)==1 ){
    vector<Type> D_i( n_i );
    D_i = R1_i * R2_i;
    ADREPORT( D_i );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vf.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        SCALE( AR1(L_z(1)), exp(L_z(0)) ).simulate(Tmp_c);
        eta_vf.row(v) = Tmp_c;
      }
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        eta_vf(v,f) = rnorm( Type(0.0), Type(1.0) );
      }
    }}
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  vector<Type> gmrf_s(n_s);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(c) = gmrf_s + gmrf_mean_sf.col(c);
      }
      // Rescale
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    // Simulate new values when using obj.simulate()
    if(isDouble<Type>::value && of->do_simulate) {
      SEPARABLE( AR1(L_z(1)), gmrf_Q ).simulate(gmrf_input_sf);
      gmrf_input_sf += gmrf_input_sf;
    }
    // Rescale
    logtau = L_z(0) - logkappa;  //
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    for( int f=0; f<n_f; f++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(f) = gmrf_s + gmrf_mean_sf.col(f);
      }
    }
    // Rescale
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j;
  //Type pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on beta1 (year intercepts for 1st linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- AR1 on beta2 (year intercepts for 2nd linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_INTEGER(include_data);   // Always use TRUE except for internal usage to extract GRMF normalization when turn off GMRF normalization in CPP
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SE for Index_xctl
  // Slot 1: Calculate SE for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: Calculate SE for D_i (expected density for every observation)
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  // Slot 8: Calculate proportions and SE
  // Slot 9: Include normalization in GMRF PDF
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,t,c;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( (Options_vec(7)==0) & (Options_vec(0)==0) ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( (Options_vec(7)==0) & (Options_vec(0)==1) ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF( Q1, bool(Options(9)) );
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0), this);
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF( Q2, bool(Options(9)) );
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2), this);
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
  }

  // Normalization of GMRFs to normalize during outer-optimization step in R
  Type jnll_GMRF = jnll_comp(0) + jnll_comp(1) + jnll_comp(2) + jnll_comp(3);
  if( include_data == 0 ){
    return( jnll_GMRF );
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4), this );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5), this );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta1_ct(c,t) = rnorm( Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1) );
      }
    }}
  }
  if( Options_vec(3)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta2_ct(c,t) = rnorm( Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2) );
      }
    }}
  }

  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( (ObsModel_ez(e_i(i),0)==11) | (ObsModel_ez(e_i(i),0)==14) ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        delta_i(i) = rnorm( Type(0.0), Type(1.0) );
      }
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  LogProb1_i.setZero();
  LogProb2_i.setZero();
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( (t_iz(i,zt)>=0) & (t_iz(i,zt)<n_t) ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Apply link function to calculate responses
      if( (ObsModel_ez(c_iz(i,0),1)==0) | (ObsModel_ez(c_iz(i,0),1)==3) ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if( (ObsModel_ez(e_i(i),0)==0) | (ObsModel_ez(e_i(i),0)==1) | (ObsModel_ez(e_i(i),0)==2) ){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0){
            LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rnorm( R2_i(i), SigmaM(e_i(i),0) );
            }
          }
          if(ObsModel_ez(e_i(i),0)==1){
            LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = exp(rnorm( log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0) ));
            }
          }
          if(ObsModel_ez(e_i(i),0)==2){
            LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rgamma( 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2) );
            }
          }
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Likelihood #2 for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      ///// Likelihood for models with discrete support
      // Zero-inflated negative binomial (not numerically stable!)
      if(ObsModel_ez(e_i(i),0)==5){
        var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rnbinom2( R2_i(i), var_i(i) );
          }
        }
      }
      // Conway-Maxwell-Poisson
      if(ObsModel_ez(e_i(i),0)==6){
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Poisson
      if(ObsModel_ez(e_i(i),0)==7){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i) );
          }
        }
      }
      // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
        /// Doesn't appear stable given spatial or spatio-temporal variation
      if(ObsModel_ez(e_i(i),0)==9){
        vector<Type> logdBinPois(4);
        logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
        logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
        for(int j=3; j<=10; j++){
          logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
        }
        logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
        if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
        if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
        if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
        if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Lognormal Poisson
      if(ObsModel_ez(e_i(i),0)==11){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
          }
        }
      }
      // Non-zero-inflated Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==12){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
        }
      }
      // Non-zero-inflated Bernoulli using cloglog link from 1st lilnear predict
      if(ObsModel_ez(e_i(i),0)==13){
        if( b_i(i)==0 ){
          LogProb2_i(i) = dpois(Type(0), R1_i(i), true);
        }else{
          LogProb2_i(i) = logspace_sub( log(Type(1.0)), dpois(Type(0), R1_i(i), true) );
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = 1;
          }
        }
      }
      // Non-zero-inflated Lognormal-Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==14){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
        }
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( (t_yz(y,z)>=0) & (t_yz(y,z)<n_t) ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( (ObsModel_ez(c,1)==0) | (ObsModel_ez(c,1)==3) ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );

  SIMULATE{
    REPORT( b_i );
  }

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }
  if( Options(3)==1 ){
    vector<Type> D_i( n_i );
    D_i = R1_i * R2_i;
    ADREPORT( D_i );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vf.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        SCALE( AR1(L_z(1)), exp(L_z(0)) ).simulate(Tmp_c);
        eta_vf.row(v) = Tmp_c;
      }
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        eta_vf(v,f) = rnorm( Type(0.0), Type(1.0) );
      }
    }}
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  vector<Type> gmrf_s(n_s);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(c) = gmrf_s + gmrf_mean_sf.col(c);
      }
      // Rescale
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    // Simulate new values when using obj.simulate()
    if(isDouble<Type>::value && of->do_simulate) {
      SEPARABLE( AR1(L_z(1)), gmrf_Q ).simulate(gmrf_input_sf);
      gmrf_input_sf += gmrf_input_sf;
    }
    // Rescale
    logtau = L_z(0) - logkappa;  //
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    for( int f=0; f<n_f; f++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(f) = gmrf_s + gmrf_mean_sf.col(f);
      }
    }
    // Rescale
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j;
  //Type pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on beta1 (year intercepts for 1st linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- AR1 on beta2 (year intercepts for 2nd linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_INTEGER(include_data);   // Always use TRUE except for internal usage to extract GRMF normalization when turn off GMRF normalization in CPP
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SE for Index_xctl
  // Slot 1: Calculate SE for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: Calculate SE for D_i (expected density for every observation)
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  // Slot 8: Calculate proportions and SE
  // Slot 9: Include normalization in GMRF PDF
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,t,c;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( (Options_vec(7)==0) & (Options_vec(0)==0) ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( (Options_vec(7)==0) & (Options_vec(0)==1) ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF( Q1, bool(Options(9)) );
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0), this);
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF( Q2, bool(Options(9)) );
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2), this);
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
  }

  // Normalization of GMRFs to normalize during outer-optimization step in R
  Type jnll_GMRF = jnll_comp(0) + jnll_comp(1) + jnll_comp(2) + jnll_comp(3);
  if( include_data == 0 ){
    return( jnll_GMRF );
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4), this );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5), this );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta1_ct(c,t) = rnorm( Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1) );
      }
    }}
  }
  if( Options_vec(3)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta2_ct(c,t) = rnorm( Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2) );
      }
    }}
  }

  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( (ObsModel_ez(e_i(i),0)==11) | (ObsModel_ez(e_i(i),0)==14) ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        delta_i(i) = rnorm( Type(0.0), Type(1.0) );
      }
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  LogProb1_i.setZero();
  LogProb2_i.setZero();
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( (t_iz(i,zt)>=0) & (t_iz(i,zt)<n_t) ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Apply link function to calculate responses
      if( (ObsModel_ez(c_iz(i,0),1)==0) | (ObsModel_ez(c_iz(i,0),1)==3) ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if( (ObsModel_ez(e_i(i),0)==0) | (ObsModel_ez(e_i(i),0)==1) | (ObsModel_ez(e_i(i),0)==2) ){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0){
            LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rnorm( R2_i(i), SigmaM(e_i(i),0) );
            }
          }
          if(ObsModel_ez(e_i(i),0)==1){
            LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = exp(rnorm( log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0) ));
            }
          }
          if(ObsModel_ez(e_i(i),0)==2){
            LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rgamma( 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2) );
            }
          }
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Likelihood #2 for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      ///// Likelihood for models with discrete support
      // Zero-inflated negative binomial (not numerically stable!)
      if(ObsModel_ez(e_i(i),0)==5){
        var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rnbinom2( R2_i(i), var_i(i) );
          }
        }
      }
      // Conway-Maxwell-Poisson
      if(ObsModel_ez(e_i(i),0)==6){
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Poisson
      if(ObsModel_ez(e_i(i),0)==7){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i) );
          }
        }
      }
      // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
        /// Doesn't appear stable given spatial or spatio-temporal variation
      if(ObsModel_ez(e_i(i),0)==9){
        vector<Type> logdBinPois(4);
        logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
        logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
        for(int j=3; j<=10; j++){
          logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
        }
        logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
        if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
        if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
        if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
        if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Lognormal Poisson
      if(ObsModel_ez(e_i(i),0)==11){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
          }
        }
      }
      // Non-zero-inflated Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==12){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
        }
      }
      // Non-zero-inflated Bernoulli using cloglog link from 1st lilnear predict
      if(ObsModel_ez(e_i(i),0)==13){
        if( b_i(i)==0 ){
          LogProb2_i(i) = dpois(Type(0), R1_i(i), true);
        }else{
          LogProb2_i(i) = logspace_sub( log(Type(1.0)), dpois(Type(0), R1_i(i), true) );
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = 1;
          }
        }
      }
      // Non-zero-inflated Lognormal-Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==14){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
        }
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( (t_yz(y,z)>=0) & (t_yz(y,z)<n_t) ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( (ObsModel_ez(c,1)==0) | (ObsModel_ez(c,1)==3) ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );

  SIMULATE{
    REPORT( b_i );
  }

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }
  if( Options(3)==1 ){
    vector<Type> D_i( n_i );
    D_i = R1_i * R2_i;
    ADREPORT( D_i );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vf.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        SCALE( AR1(L_z(1)), exp(L_z(0)) ).simulate(Tmp_c);
        eta_vf.row(v) = Tmp_c;
      }
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        eta_vf(v,f) = rnorm( Type(0.0), Type(1.0) );
      }
    }}
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  vector<Type> gmrf_s(n_s);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(c) = gmrf_s + gmrf_mean_sf.col(c);
      }
      // Rescale
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    // Simulate new values when using obj.simulate()
    if(isDouble<Type>::value && of->do_simulate) {
      SEPARABLE( AR1(L_z(1)), gmrf_Q ).simulate(gmrf_input_sf);
      gmrf_input_sf += gmrf_input_sf;
    }
    // Rescale
    logtau = L_z(0) - logkappa;  //
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    for( int f=0; f<n_f; f++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(f) = gmrf_s + gmrf_mean_sf.col(f);
      }
    }
    // Rescale
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j;
  //Type pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on beta1 (year intercepts for 1st linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- AR1 on beta2 (year intercepts for 2nd linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_INTEGER(include_data);   // Always use TRUE except for internal usage to extract GRMF normalization when turn off GMRF normalization in CPP
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SE for Index_xctl
  // Slot 1: Calculate SE for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: Calculate SE for D_i (expected density for every observation)
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  // Slot 8: Calculate proportions and SE
  // Slot 9: Include normalization in GMRF PDF
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,t,c;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( (Options_vec(7)==0) & (Options_vec(0)==0) ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( (Options_vec(7)==0) & (Options_vec(0)==1) ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF( Q1, bool(Options(9)) );
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0), this);
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF( Q2, bool(Options(9)) );
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2), this);
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
  }

  // Normalization of GMRFs to normalize during outer-optimization step in R
  Type jnll_GMRF = jnll_comp(0) + jnll_comp(1) + jnll_comp(2) + jnll_comp(3);
  if( include_data == 0 ){
    return( jnll_GMRF );
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4), this );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5), this );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta1_ct(c,t) = rnorm( Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1) );
      }
    }}
  }
  if( Options_vec(3)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta2_ct(c,t) = rnorm( Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2) );
      }
    }}
  }

  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( (ObsModel_ez(e_i(i),0)==11) | (ObsModel_ez(e_i(i),0)==14) ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        delta_i(i) = rnorm( Type(0.0), Type(1.0) );
      }
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  LogProb1_i.setZero();
  LogProb2_i.setZero();
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( (t_iz(i,zt)>=0) & (t_iz(i,zt)<n_t) ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Apply link function to calculate responses
      if( (ObsModel_ez(c_iz(i,0),1)==0) | (ObsModel_ez(c_iz(i,0),1)==3) ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if( (ObsModel_ez(e_i(i),0)==0) | (ObsModel_ez(e_i(i),0)==1) | (ObsModel_ez(e_i(i),0)==2) ){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0){
            LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rnorm( R2_i(i), SigmaM(e_i(i),0) );
            }
          }
          if(ObsModel_ez(e_i(i),0)==1){
            LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = exp(rnorm( log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0) ));
            }
          }
          if(ObsModel_ez(e_i(i),0)==2){
            LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rgamma( 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2) );
            }
          }
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Likelihood #2 for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      ///// Likelihood for models with discrete support
      // Zero-inflated negative binomial (not numerically stable!)
      if(ObsModel_ez(e_i(i),0)==5){
        var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rnbinom2( R2_i(i), var_i(i) );
          }
        }
      }
      // Conway-Maxwell-Poisson
      if(ObsModel_ez(e_i(i),0)==6){
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Poisson
      if(ObsModel_ez(e_i(i),0)==7){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i) );
          }
        }
      }
      // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
        /// Doesn't appear stable given spatial or spatio-temporal variation
      if(ObsModel_ez(e_i(i),0)==9){
        vector<Type> logdBinPois(4);
        logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
        logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
        for(int j=3; j<=10; j++){
          logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
        }
        logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
        if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
        if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
        if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
        if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Lognormal Poisson
      if(ObsModel_ez(e_i(i),0)==11){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
          }
        }
      }
      // Non-zero-inflated Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==12){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
        }
      }
      // Non-zero-inflated Bernoulli using cloglog link from 1st lilnear predict
      if(ObsModel_ez(e_i(i),0)==13){
        if( b_i(i)==0 ){
          LogProb2_i(i) = dpois(Type(0), R1_i(i), true);
        }else{
          LogProb2_i(i) = logspace_sub( log(Type(1.0)), dpois(Type(0), R1_i(i), true) );
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = 1;
          }
        }
      }
      // Non-zero-inflated Lognormal-Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==14){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
        }
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( (t_yz(y,z)>=0) & (t_yz(y,z)<n_t) ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( (ObsModel_ez(c,1)==0) | (ObsModel_ez(c,1)==3) ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );

  SIMULATE{
    REPORT( b_i );
  }

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }
  if( Options(3)==1 ){
    vector<Type> D_i( n_i );
    D_i = R1_i * R2_i;
    ADREPORT( D_i );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vf.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        SCALE( AR1(L_z(1)), exp(L_z(0)) ).simulate(Tmp_c);
        eta_vf.row(v) = Tmp_c;
      }
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        eta_vf(v,f) = rnorm( Type(0.0), Type(1.0) );
      }
    }}
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  vector<Type> gmrf_s(n_s);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(c) = gmrf_s + gmrf_mean_sf.col(c);
      }
      // Rescale
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    // Simulate new values when using obj.simulate()
    if(isDouble<Type>::value && of->do_simulate) {
      SEPARABLE( AR1(L_z(1)), gmrf_Q ).simulate(gmrf_input_sf);
      gmrf_input_sf += gmrf_input_sf;
    }
    // Rescale
    logtau = L_z(0) - logkappa;  //
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    for( int f=0; f<n_f; f++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(f) = gmrf_s + gmrf_mean_sf.col(f);
      }
    }
    // Rescale
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j;
  //Type pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on beta1 (year intercepts for 1st linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- AR1 on beta2 (year intercepts for 2nd linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_INTEGER(include_data);   // Always use TRUE except for internal usage to extract GRMF normalization when turn off GMRF normalization in CPP
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SE for Index_xctl
  // Slot 1: Calculate SE for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: Calculate SE for D_i (expected density for every observation)
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  // Slot 8: Calculate proportions and SE
  // Slot 9: Include normalization in GMRF PDF
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,t,c;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( (Options_vec(7)==0) & (Options_vec(0)==0) ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( (Options_vec(7)==0) & (Options_vec(0)==1) ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF( Q1, bool(Options(9)) );
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0), this);
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF( Q2, bool(Options(9)) );
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2), this);
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
  }

  // Normalization of GMRFs to normalize during outer-optimization step in R
  Type jnll_GMRF = jnll_comp(0) + jnll_comp(1) + jnll_comp(2) + jnll_comp(3);
  if( include_data == 0 ){
    return( jnll_GMRF );
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4), this );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5), this );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta1_ct(c,t) = rnorm( Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1) );
      }
    }}
  }
  if( Options_vec(3)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta2_ct(c,t) = rnorm( Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2) );
      }
    }}
  }

  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( (ObsModel_ez(e_i(i),0)==11) | (ObsModel_ez(e_i(i),0)==14) ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        delta_i(i) = rnorm( Type(0.0), Type(1.0) );
      }
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  LogProb1_i.setZero();
  LogProb2_i.setZero();
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( (t_iz(i,zt)>=0) & (t_iz(i,zt)<n_t) ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Apply link function to calculate responses
      if( (ObsModel_ez(c_iz(i,0),1)==0) | (ObsModel_ez(c_iz(i,0),1)==3) ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if( (ObsModel_ez(e_i(i),0)==0) | (ObsModel_ez(e_i(i),0)==1) | (ObsModel_ez(e_i(i),0)==2) ){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0){
            LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rnorm( R2_i(i), SigmaM(e_i(i),0) );
            }
          }
          if(ObsModel_ez(e_i(i),0)==1){
            LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = exp(rnorm( log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0) ));
            }
          }
          if(ObsModel_ez(e_i(i),0)==2){
            LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rgamma( 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2) );
            }
          }
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Likelihood #2 for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      ///// Likelihood for models with discrete support
      // Zero-inflated negative binomial (not numerically stable!)
      if(ObsModel_ez(e_i(i),0)==5){
        var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rnbinom2( R2_i(i), var_i(i) );
          }
        }
      }
      // Conway-Maxwell-Poisson
      if(ObsModel_ez(e_i(i),0)==6){
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Poisson
      if(ObsModel_ez(e_i(i),0)==7){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i) );
          }
        }
      }
      // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
        /// Doesn't appear stable given spatial or spatio-temporal variation
      if(ObsModel_ez(e_i(i),0)==9){
        vector<Type> logdBinPois(4);
        logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
        logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
        for(int j=3; j<=10; j++){
          logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
        }
        logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
        if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
        if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
        if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
        if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Lognormal Poisson
      if(ObsModel_ez(e_i(i),0)==11){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
          }
        }
      }
      // Non-zero-inflated Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==12){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
        }
      }
      // Non-zero-inflated Bernoulli using cloglog link from 1st lilnear predict
      if(ObsModel_ez(e_i(i),0)==13){
        if( b_i(i)==0 ){
          LogProb2_i(i) = dpois(Type(0), R1_i(i), true);
        }else{
          LogProb2_i(i) = logspace_sub( log(Type(1.0)), dpois(Type(0), R1_i(i), true) );
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = 1;
          }
        }
      }
      // Non-zero-inflated Lognormal-Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==14){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
        }
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( (t_yz(y,z)>=0) & (t_yz(y,z)<n_t) ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( (ObsModel_ez(c,1)==0) | (ObsModel_ez(c,1)==3) ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );

  SIMULATE{
    REPORT( b_i );
  }

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }
  if( Options(3)==1 ){
    vector<Type> D_i( n_i );
    D_i = R1_i * R2_i;
    ADREPORT( D_i );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vf.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        SCALE( AR1(L_z(1)), exp(L_z(0)) ).simulate(Tmp_c);
        eta_vf.row(v) = Tmp_c;
      }
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        eta_vf(v,f) = rnorm( Type(0.0), Type(1.0) );
      }
    }}
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  vector<Type> gmrf_s(n_s);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(c) = gmrf_s + gmrf_mean_sf.col(c);
      }
      // Rescale
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    // Simulate new values when using obj.simulate()
    if(isDouble<Type>::value && of->do_simulate) {
      SEPARABLE( AR1(L_z(1)), gmrf_Q ).simulate(gmrf_input_sf);
      gmrf_input_sf += gmrf_input_sf;
    }
    // Rescale
    logtau = L_z(0) - logkappa;  //
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    for( int f=0; f<n_f; f++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(f) = gmrf_s + gmrf_mean_sf.col(f);
      }
    }
    // Rescale
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j;
  //Type pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on beta1 (year intercepts for 1st linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- AR1 on beta2 (year intercepts for 2nd linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_INTEGER(include_data);   // Always use TRUE except for internal usage to extract GRMF normalization when turn off GMRF normalization in CPP
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SE for Index_xctl
  // Slot 1: Calculate SE for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: Calculate SE for D_i (expected density for every observation)
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  // Slot 8: Calculate proportions and SE
  // Slot 9: Include normalization in GMRF PDF
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,t,c;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( (Options_vec(7)==0) & (Options_vec(0)==0) ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( (Options_vec(7)==0) & (Options_vec(0)==1) ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF( Q1, bool(Options(9)) );
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0), this);
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF( Q2, bool(Options(9)) );
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2), this);
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
  }

  // Normalization of GMRFs to normalize during outer-optimization step in R
  Type jnll_GMRF = jnll_comp(0) + jnll_comp(1) + jnll_comp(2) + jnll_comp(3);
  if( include_data == 0 ){
    return( jnll_GMRF );
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4), this );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5), this );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta1_ct(c,t) = rnorm( Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1) );
      }
    }}
  }
  if( Options_vec(3)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta2_ct(c,t) = rnorm( Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2) );
      }
    }}
  }

  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( (ObsModel_ez(e_i(i),0)==11) | (ObsModel_ez(e_i(i),0)==14) ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        delta_i(i) = rnorm( Type(0.0), Type(1.0) );
      }
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  LogProb1_i.setZero();
  LogProb2_i.setZero();
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( (t_iz(i,zt)>=0) & (t_iz(i,zt)<n_t) ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Apply link function to calculate responses
      if( (ObsModel_ez(c_iz(i,0),1)==0) | (ObsModel_ez(c_iz(i,0),1)==3) ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if( (ObsModel_ez(e_i(i),0)==0) | (ObsModel_ez(e_i(i),0)==1) | (ObsModel_ez(e_i(i),0)==2) ){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0){
            LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rnorm( R2_i(i), SigmaM(e_i(i),0) );
            }
          }
          if(ObsModel_ez(e_i(i),0)==1){
            LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = exp(rnorm( log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0) ));
            }
          }
          if(ObsModel_ez(e_i(i),0)==2){
            LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rgamma( 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2) );
            }
          }
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Likelihood #2 for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      ///// Likelihood for models with discrete support
      // Zero-inflated negative binomial (not numerically stable!)
      if(ObsModel_ez(e_i(i),0)==5){
        var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rnbinom2( R2_i(i), var_i(i) );
          }
        }
      }
      // Conway-Maxwell-Poisson
      if(ObsModel_ez(e_i(i),0)==6){
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Poisson
      if(ObsModel_ez(e_i(i),0)==7){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i) );
          }
        }
      }
      // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
        /// Doesn't appear stable given spatial or spatio-temporal variation
      if(ObsModel_ez(e_i(i),0)==9){
        vector<Type> logdBinPois(4);
        logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
        logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
        for(int j=3; j<=10; j++){
          logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
        }
        logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
        if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
        if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
        if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
        if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Lognormal Poisson
      if(ObsModel_ez(e_i(i),0)==11){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
          }
        }
      }
      // Non-zero-inflated Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==12){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
        }
      }
      // Non-zero-inflated Bernoulli using cloglog link from 1st lilnear predict
      if(ObsModel_ez(e_i(i),0)==13){
        if( b_i(i)==0 ){
          LogProb2_i(i) = dpois(Type(0), R1_i(i), true);
        }else{
          LogProb2_i(i) = logspace_sub( log(Type(1.0)), dpois(Type(0), R1_i(i), true) );
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = 1;
          }
        }
      }
      // Non-zero-inflated Lognormal-Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==14){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
        }
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( (t_yz(y,z)>=0) & (t_yz(y,z)<n_t) ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( (ObsModel_ez(c,1)==0) | (ObsModel_ez(c,1)==3) ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );

  SIMULATE{
    REPORT( b_i );
  }

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }
  if( Options(3)==1 ){
    vector<Type> D_i( n_i );
    D_i = R1_i * R2_i;
    ADREPORT( D_i );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vf.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        SCALE( AR1(L_z(1)), exp(L_z(0)) ).simulate(Tmp_c);
        eta_vf.row(v) = Tmp_c;
      }
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        eta_vf(v,f) = rnorm( Type(0.0), Type(1.0) );
      }
    }}
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  vector<Type> gmrf_s(n_s);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(c) = gmrf_s + gmrf_mean_sf.col(c);
      }
      // Rescale
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    // Simulate new values when using obj.simulate()
    if(isDouble<Type>::value && of->do_simulate) {
      SEPARABLE( AR1(L_z(1)), gmrf_Q ).simulate(gmrf_input_sf);
      gmrf_input_sf += gmrf_input_sf;
    }
    // Rescale
    logtau = L_z(0) - logkappa;  //
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    for( int f=0; f<n_f; f++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(f) = gmrf_s + gmrf_mean_sf.col(f);
      }
    }
    // Rescale
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j;
  //Type pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on beta1 (year intercepts for 1st linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- AR1 on beta2 (year intercepts for 2nd linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_INTEGER(include_data);   // Always use TRUE except for internal usage to extract GRMF normalization when turn off GMRF normalization in CPP
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SE for Index_xctl
  // Slot 1: Calculate SE for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: Calculate SE for D_i (expected density for every observation)
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  // Slot 8: Calculate proportions and SE
  // Slot 9: Include normalization in GMRF PDF
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,t,c;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( (Options_vec(7)==0) & (Options_vec(0)==0) ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( (Options_vec(7)==0) & (Options_vec(0)==1) ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF( Q1, bool(Options(9)) );
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0), this);
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF( Q2, bool(Options(9)) );
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2), this);
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
  }

  // Normalization of GMRFs to normalize during outer-optimization step in R
  Type jnll_GMRF = jnll_comp(0) + jnll_comp(1) + jnll_comp(2) + jnll_comp(3);
  if( include_data == 0 ){
    return( jnll_GMRF );
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4), this );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5), this );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta1_ct(c,t) = rnorm( Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1) );
      }
    }}
  }
  if( Options_vec(3)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta2_ct(c,t) = rnorm( Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2) );
      }
    }}
  }

  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( (ObsModel_ez(e_i(i),0)==11) | (ObsModel_ez(e_i(i),0)==14) ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        delta_i(i) = rnorm( Type(0.0), Type(1.0) );
      }
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  LogProb1_i.setZero();
  LogProb2_i.setZero();
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( (t_iz(i,zt)>=0) & (t_iz(i,zt)<n_t) ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Apply link function to calculate responses
      if( (ObsModel_ez(c_iz(i,0),1)==0) | (ObsModel_ez(c_iz(i,0),1)==3) ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if( (ObsModel_ez(e_i(i),0)==0) | (ObsModel_ez(e_i(i),0)==1) | (ObsModel_ez(e_i(i),0)==2) ){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0){
            LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rnorm( R2_i(i), SigmaM(e_i(i),0) );
            }
          }
          if(ObsModel_ez(e_i(i),0)==1){
            LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = exp(rnorm( log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0) ));
            }
          }
          if(ObsModel_ez(e_i(i),0)==2){
            LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rgamma( 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2) );
            }
          }
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Likelihood #2 for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      ///// Likelihood for models with discrete support
      // Zero-inflated negative binomial (not numerically stable!)
      if(ObsModel_ez(e_i(i),0)==5){
        var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rnbinom2( R2_i(i), var_i(i) );
          }
        }
      }
      // Conway-Maxwell-Poisson
      if(ObsModel_ez(e_i(i),0)==6){
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Poisson
      if(ObsModel_ez(e_i(i),0)==7){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i) );
          }
        }
      }
      // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
        /// Doesn't appear stable given spatial or spatio-temporal variation
      if(ObsModel_ez(e_i(i),0)==9){
        vector<Type> logdBinPois(4);
        logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
        logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
        for(int j=3; j<=10; j++){
          logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
        }
        logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
        if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
        if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
        if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
        if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Lognormal Poisson
      if(ObsModel_ez(e_i(i),0)==11){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
          }
        }
      }
      // Non-zero-inflated Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==12){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
        }
      }
      // Non-zero-inflated Bernoulli using cloglog link from 1st lilnear predict
      if(ObsModel_ez(e_i(i),0)==13){
        if( b_i(i)==0 ){
          LogProb2_i(i) = dpois(Type(0), R1_i(i), true);
        }else{
          LogProb2_i(i) = logspace_sub( log(Type(1.0)), dpois(Type(0), R1_i(i), true) );
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = 1;
          }
        }
      }
      // Non-zero-inflated Lognormal-Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==14){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
        }
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( (t_yz(y,z)>=0) & (t_yz(y,z)<n_t) ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( (ObsModel_ez(c,1)==0) | (ObsModel_ez(c,1)==3) ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );

  SIMULATE{
    REPORT( b_i );
  }

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }
  if( Options(3)==1 ){
    vector<Type> D_i( n_i );
    D_i = R1_i * R2_i;
    ADREPORT( D_i );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( ObsModel_ez(e_i(i),0)==11 ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( t_iz(i,zt)>=0 & t_iz(i,zt)<n_t ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Responses
      if( ObsModel_ez(c_iz(i,0),1)==0 | ObsModel_ez(c_iz(i,0),1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
          //if( !isNA(c_iz(i,zc)) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel_ez(e_i(i),0)==0 | ObsModel_ez(e_i(i),0)==1 | ObsModel_ez(e_i(i),0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
          if(ObsModel_ez(e_i(i),0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
          if(ObsModel_ez(e_i(i),0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel_ez(e_i(i),0)==4 | ObsModel_ez(e_i(i),0)==5 | ObsModel_ez(e_i(i),0)==6 | ObsModel_ez(e_i(i),0)==7 | ObsModel_ez(e_i(i),0)==9 | ObsModel_ez(e_i(i),0)==11){
        if(ObsModel_ez(e_i(i),0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        }
        if(ObsModel_ez(e_i(i),0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel_ez(e_i(i),0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel_ez(c,1)==0 | ObsModel_ez(c,1)==3 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( ObsModel_ez(e_i(i),0)==11 ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( t_iz(i,zt)>=0 & t_iz(i,zt)<n_t ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Responses
      if( ObsModel_ez(c_iz(i,0),1)==0 | ObsModel_ez(c_iz(i,0),1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
          //if( !isNA(c_iz(i,zc)) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel_ez(e_i(i),0)==0 | ObsModel_ez(e_i(i),0)==1 | ObsModel_ez(e_i(i),0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
          if(ObsModel_ez(e_i(i),0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
          if(ObsModel_ez(e_i(i),0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel_ez(e_i(i),0)==4 | ObsModel_ez(e_i(i),0)==5 | ObsModel_ez(e_i(i),0)==6 | ObsModel_ez(e_i(i),0)==7 | ObsModel_ez(e_i(i),0)==9 | ObsModel_ez(e_i(i),0)==11){
        if(ObsModel_ez(e_i(i),0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        }
        if(ObsModel_ez(e_i(i),0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel_ez(e_i(i),0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel_ez(c,1)==0 | ObsModel_ez(c,1)==3 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vf.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        SCALE( AR1(L_z(1)), exp(L_z(0)) ).simulate(Tmp_c);
        eta_vf.row(v) = Tmp_c;
      }
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        eta_vf(v,f) = rnorm( Type(0.0), Type(1.0) );
      }
    }}
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  vector<Type> gmrf_s(n_s);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(c) = gmrf_s + gmrf_mean_sf.col(c);
      }
      // Rescale
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    // Simulate new values when using obj.simulate()
    if(isDouble<Type>::value && of->do_simulate) {
      SEPARABLE( AR1(L_z(1)), gmrf_Q ).simulate(gmrf_input_sf);
      gmrf_input_sf += gmrf_input_sf;
    }
    // Rescale
    logtau = L_z(0) - logkappa;  //
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    for( int f=0; f<n_f; f++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(f) = gmrf_s + gmrf_mean_sf.col(f);
      }
    }
    // Rescale
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j;
  //Type pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on beta1 (year intercepts for 1st linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- AR1 on beta2 (year intercepts for 2nd linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_INTEGER(include_data);   // Always use TRUE except for internal usage to extract GRMF normalization when turn off GMRF normalization in CPP
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SE for Index_xctl
  // Slot 1: Calculate SE for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: Calculate SE for D_i (expected density for every observation)
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  // Slot 8: Calculate proportions and SE
  // Slot 9: Include normalization in GMRF PDF
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,t,c;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( (Options_vec(7)==0) & (Options_vec(0)==0) ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( (Options_vec(7)==0) & (Options_vec(0)==1) ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF( Q1, bool(Options(9)) );
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0), this);
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF( Q2, bool(Options(9)) );
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2), this);
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
  }

  // Normalization of GMRFs to normalize during outer-optimization step in R
  Type jnll_GMRF = jnll_comp(0) + jnll_comp(1) + jnll_comp(2) + jnll_comp(3);
  if( include_data == 0 ){
    return( jnll_GMRF );
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4), this );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5), this );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta1_ct(c,t) = rnorm( Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1) );
      }
    }}
  }
  if( Options_vec(3)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta2_ct(c,t) = rnorm( Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2) );
      }
    }}
  }

  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( (ObsModel_ez(e_i(i),0)==11) | (ObsModel_ez(e_i(i),0)==14) ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        delta_i(i) = rnorm( Type(0.0), Type(1.0) );
      }
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  LogProb1_i.setZero();
  LogProb2_i.setZero();
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( (t_iz(i,zt)>=0) & (t_iz(i,zt)<n_t) ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Apply link function to calculate responses
      if( (ObsModel_ez(c_iz(i,0),1)==0) | (ObsModel_ez(c_iz(i,0),1)==3) ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if( (ObsModel_ez(e_i(i),0)==0) | (ObsModel_ez(e_i(i),0)==1) | (ObsModel_ez(e_i(i),0)==2) ){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0){
            LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rnorm( R2_i(i), SigmaM(e_i(i),0) );
            }
          }
          if(ObsModel_ez(e_i(i),0)==1){
            LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = exp(rnorm( log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0) ));
            }
          }
          if(ObsModel_ez(e_i(i),0)==2){
            LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rgamma( 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2) );
            }
          }
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Likelihood #2 for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      ///// Likelihood for models with discrete support
      // Zero-inflated negative binomial (not numerically stable!)
      if(ObsModel_ez(e_i(i),0)==5){
        var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rnbinom2( R2_i(i), var_i(i) );
          }
        }
      }
      // Conway-Maxwell-Poisson
      if(ObsModel_ez(e_i(i),0)==6){
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Poisson
      if(ObsModel_ez(e_i(i),0)==7){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i) );
          }
        }
      }
      // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
        /// Doesn't appear stable given spatial or spatio-temporal variation
      if(ObsModel_ez(e_i(i),0)==9){
        vector<Type> logdBinPois(4);
        logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
        logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
        for(int j=3; j<=10; j++){
          logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
        }
        logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
        if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
        if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
        if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
        if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Lognormal Poisson
      if(ObsModel_ez(e_i(i),0)==11){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
          }
        }
      }
      // Non-zero-inflated Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==12){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
        }
      }
      // Non-zero-inflated Bernoulli using cloglog link from 1st lilnear predict
      if(ObsModel_ez(e_i(i),0)==13){
        if( b_i(i)==0 ){
          LogProb2_i(i) = dpois(Type(0), R1_i(i), true);
        }else{
          LogProb2_i(i) = logspace_sub( log(Type(1.0)), dpois(Type(0), R1_i(i), true) );
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = 1;
          }
        }
      }
      // Non-zero-inflated Lognormal-Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==14){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
        }
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( (t_yz(y,z)>=0) & (t_yz(y,z)<n_t) ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( (ObsModel_ez(c,1)==0) | (ObsModel_ez(c,1)==3) ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );

  SIMULATE{
    REPORT( b_i );
  }

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }
  if( Options(3)==1 ){
    vector<Type> D_i( n_i );
    D_i = R1_i * R2_i;
    ADREPORT( D_i );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vf.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        SCALE( AR1(L_z(1)), exp(L_z(0)) ).simulate(Tmp_c);
        eta_vf.row(v) = Tmp_c;
      }
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        eta_vf(v,f) = rnorm( Type(0.0), Type(1.0) );
      }
    }}
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  vector<Type> gmrf_s(n_s);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(c) = gmrf_s + gmrf_mean_sf.col(c);
      }
      // Rescale
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    // Simulate new values when using obj.simulate()
    if(isDouble<Type>::value && of->do_simulate) {
      SEPARABLE( AR1(L_z(1)), gmrf_Q ).simulate(gmrf_input_sf);
      gmrf_input_sf += gmrf_input_sf;
    }
    // Rescale
    logtau = L_z(0) - logkappa;  //
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    for( int f=0; f<n_f; f++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(f) = gmrf_s + gmrf_mean_sf.col(f);
      }
    }
    // Rescale
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j;
  //Type pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on beta1 (year intercepts for 1st linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- AR1 on beta2 (year intercepts for 2nd linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_INTEGER(include_data);   // Always use TRUE except for internal usage to extract GRMF normalization when turn off GMRF normalization in CPP
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SE for Index_xctl
  // Slot 1: Calculate SE for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: Calculate SE for D_i (expected density for every observation)
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  // Slot 8: Calculate proportions and SE
  // Slot 9: Include normalization in GMRF PDF
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,t,c;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( (Options_vec(7)==0) & (Options_vec(0)==0) ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( (Options_vec(7)==0) & (Options_vec(0)==1) ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF( Q1, bool(Options(9)) );
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0), this);
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF( Q2, bool(Options(9)) );
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2), this);
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
  }

  // Normalization of GMRFs to normalize during outer-optimization step in R
  Type jnll_GMRF = jnll_comp(0) + jnll_comp(1) + jnll_comp(2) + jnll_comp(3);
  if( include_data == 0 ){
    return( jnll_GMRF );
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4), this );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5), this );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta1_ct(c,t) = rnorm( Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1) );
      }
    }}
  }
  if( Options_vec(3)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta2_ct(c,t) = rnorm( Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2) );
      }
    }}
  }

  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( (ObsModel_ez(e_i(i),0)==11) | (ObsModel_ez(e_i(i),0)==14) ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        delta_i(i) = rnorm( Type(0.0), Type(1.0) );
      }
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  LogProb1_i.setZero();
  LogProb2_i.setZero();
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( (t_iz(i,zt)>=0) & (t_iz(i,zt)<n_t) ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Apply link function to calculate responses
      if( (ObsModel_ez(c_iz(i,0),1)==0) | (ObsModel_ez(c_iz(i,0),1)==3) ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if( (ObsModel_ez(e_i(i),0)==0) | (ObsModel_ez(e_i(i),0)==1) | (ObsModel_ez(e_i(i),0)==2) ){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0){
            LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rnorm( R2_i(i), SigmaM(e_i(i),0) );
            }
          }
          if(ObsModel_ez(e_i(i),0)==1){
            LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = exp(rnorm( log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0) ));
            }
          }
          if(ObsModel_ez(e_i(i),0)==2){
            LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rgamma( 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2) );
            }
          }
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Likelihood #2 for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      ///// Likelihood for models with discrete support
      // Zero-inflated negative binomial (not numerically stable!)
      if(ObsModel_ez(e_i(i),0)==5){
        var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rnbinom2( R2_i(i), var_i(i) );
          }
        }
      }
      // Conway-Maxwell-Poisson
      if(ObsModel_ez(e_i(i),0)==6){
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Poisson
      if(ObsModel_ez(e_i(i),0)==7){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i) );
          }
        }
      }
      // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
        /// Doesn't appear stable given spatial or spatio-temporal variation
      if(ObsModel_ez(e_i(i),0)==9){
        vector<Type> logdBinPois(4);
        logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
        logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
        for(int j=3; j<=10; j++){
          logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
        }
        logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
        if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
        if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
        if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
        if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Lognormal Poisson
      if(ObsModel_ez(e_i(i),0)==11){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
          }
        }
      }
      // Non-zero-inflated Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==12){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
        }
      }
      // Non-zero-inflated Bernoulli using cloglog link from 1st lilnear predict
      if(ObsModel_ez(e_i(i),0)==13){
        if( b_i(i)==0 ){
          LogProb2_i(i) = dpois(Type(0), R1_i(i), true);
        }else{
          LogProb2_i(i) = logspace_sub( log(Type(1.0)), dpois(Type(0), R1_i(i), true) );
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = 1;
          }
        }
      }
      // Non-zero-inflated Lognormal-Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==14){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
        }
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( (t_yz(y,z)>=0) & (t_yz(y,z)<n_t) ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( (ObsModel_ez(c,1)==0) | (ObsModel_ez(c,1)==3) ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );

  SIMULATE{
    REPORT( b_i );
  }

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }
  if( Options(3)==1 ){
    vector<Type> D_i( n_i );
    D_i = R1_i * R2_i;
    ADREPORT( D_i );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vf.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        SCALE( AR1(L_z(1)), exp(L_z(0)) ).simulate(Tmp_c);
        eta_vf.row(v) = Tmp_c;
      }
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        eta_vf(v,f) = rnorm( Type(0.0), Type(1.0) );
      }
    }}
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int timing, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  vector<Type> gmrf_s(n_s);
  matrix<Type> Cov_cc(n_c,n_c);
  array<Type> diff_gmrf_sc(n_s, n_c); // Requires an array
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(c) = gmrf_s + gmrf_mean_sf.col(c);
      }
      // Rescale
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    // Simulate new values when using obj.simulate()
    if(isDouble<Type>::value && of->do_simulate) {
      SEPARABLE( AR1(L_z(1)), gmrf_Q ).simulate(gmrf_input_sf);
      gmrf_input_sf += gmrf_input_sf;
    }
    // Rescale
    logtau = L_z(0) - logkappa;  //
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    // PDF if density-dependence/interactions occurs prior to correlated dynamics
    if( timing==0 ){
      for( int f=0; f<n_f; f++ ){
        // Calculate likelihood
        jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
        // Simulate new values when using obj.simulate()
        if(isDouble<Type>::value && of->do_simulate) {
          gmrf_Q.simulate(gmrf_s);
          gmrf_input_sf.col(f) = gmrf_s + gmrf_mean_sf.col(f);
        }
      }
      // Rescale
      matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
    }
    // PDF if density-dependence/interactions occurs after correlated dynamics (Only makes sense if n_f == n_c)
    if( timing==1 ){
      // Calculate difference without rescaling
      gmrf_sc = gmrf_input_sf.matrix();
      for( int s=0; s<n_s; s++){
      for( int c=0; c<n_c; c++){
        diff_gmrf_sc(s,c) = gmrf_sc(s,c) - gmrf_mean_sf(s,c);
      }}
      // Calculate likelihood
      matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
      Cov_cc = L_cf * L_cf.transpose();
      jnll_pointer += SEPARABLE(MVNORM(Cov_cc), gmrf_Q)( diff_gmrf_sc );
      gmrf_sc = gmrf_sc / exp(logtau);
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        SEPARABLE(MVNORM(Cov_cc), gmrf_Q).simulate( diff_gmrf_sc );
        gmrf_sc = gmrf_mean_sf + diff_gmrf_sc/exp(logtau);
      }
    }
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j;
  //Type pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// Calculate B_cc
template<class Type>
matrix<Type> calculate_B( int method, int n_f, int n_r, matrix<Type> Chi_fr, matrix<Type> Psi_fr ){
  matrix<Type> B_ff( n_f, n_f );
  matrix<Type> BplusI_ff( n_f, n_f );
  matrix<Type> Chi_rf = Chi_fr.transpose();
  matrix<Type> Psi_rf = Psi_fr.transpose();
  matrix<Type> Identity_ff( n_f, n_f );
  Identity_ff.setIdentity();

  // No interactions (default)
  if( method==0 ){
    B_ff.setZero();
  }
  // Simple co-integration -- complex unbounded eigenvalues
  if( method==1 ){
    B_ff = Chi_fr * Psi_rf;
  }
  // Real eigenvalues
  if( method==2 ){
    matrix<Type> Chi_ff( n_f, n_f );
    Chi_ff = Identity_ff;
    // Make Chi_ff
    vector<Type> colnorm_r( n_r );
    colnorm_r.setZero();
    for(int f=0; f<n_f; f++){
    for(int r=0; r<n_r; r++){
      Chi_ff(f,r) = Chi_fr(f,r);
      colnorm_r(r) += pow( Chi_ff(f,r), 2 );
    }}
    for(int f=0; f<n_f; f++){
    for(int r=0; r<n_r; r++){
      Chi_ff(f,r) /= pow( colnorm_r(r), 0.5 );
    }}
    // Make Psi_ff
    matrix<Type> Psi_ff( n_f, n_f );
    Psi_ff = Identity_ff;
    for(int f=n_r; f<n_f; f++){
    for(int r=0; r<n_r; r++){
      Psi_ff(f,r) = Psi_fr(f,r);
    }}
    // Make L_ff
    matrix<Type> L_ff(n_f, n_f);
    L_ff.setZero();
    for(int r=0; r<n_r; r++){
      L_ff(r,r) = Psi_fr(r,r);
    }
    // Build B_ff
    matrix<Type> invChi_ff = atomic::matinv( Chi_ff );
    matrix<Type> trans_Psi_ff = Psi_ff.transpose();
    matrix<Type> trans_invPsi_ff = atomic::matinv( Psi_ff ).transpose();
    B_ff = Chi_ff * trans_Psi_ff;
    B_ff = B_ff * L_ff;
    B_ff = B_ff * trans_invPsi_ff;
    B_ff = B_ff * invChi_ff;
    // Penalize colnorm_r
    //if( Options_vec(0)==3 ) jnll_comp(3) += PenMult_z(1) * ( log(colnorm_r)*log(colnorm_r) ).sum();
  }
  // Complex bounded eigenvalues
  if( method==3 ){
    BplusI_ff = Chi_fr * Psi_rf + Identity_ff;
    // Extract eigenvalues
    vector< std::complex<Type> > eigenvalues_B_ff = B_ff.eigenvalues();
    vector<Type> real_eigenvalues_B_ff = eigenvalues_B_ff.real();
    vector<Type> imag_eigenvalues_B_ff = eigenvalues_B_ff.imag();
    vector<Type> mod_eigenvalues_B_ff( n_f );
    // Calculate maximum eigenvalues
    Type MaxEigen = 1;
    for(int f=0; f<n_f; f++){
      mod_eigenvalues_B_ff(f) = pow( pow(real_eigenvalues_B_ff(f),2) + pow(imag_eigenvalues_B_ff(f),2), 0.5 );
      MaxEigen = CppAD::CondExpGt(mod_eigenvalues_B_ff(f), MaxEigen, mod_eigenvalues_B_ff(f), MaxEigen);
    }
    // Rescale interaction matrix
    BplusI_ff = BplusI_ff / MaxEigen;
    B_ff = BplusI_ff - Identity_ff;
    //jnll_comp(3) += PenMult_z(0) * CppAD::CondExpGe( MaxEigen, Type(1.0), pow(MaxEigen-Type(1.0),2), Type(0.0) );
  }
  return B_ff;
}

// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on beta1 (year intercepts for 1st linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- AR1 on beta2 (year intercepts for 2nd linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(VamConfig);
  // Slot 0 -- method for calculating n_c-by-n_c interaction matrix, B_ff
  // Slot 1 -- rank of interaction matrix B_ff
  // Current implementation only makes sense when (1) intercepts are constant among years; (2) using a Poisson-link delta model; (3) n_f=n_c for spatio-temporal variation; (4) starts near equilibrium manifold
  DATA_INTEGER(include_data);   // Always use TRUE except for internal usage to extract GRMF normalization when turn off GMRF normalization in CPP
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SE for Index_xctl
  // Slot 1: Calculate SE for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: Calculate SE for D_i (expected density for every observation)
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  // Slot 8: Calculate proportions and SE
  // Slot 9: Include normalization in GMRF PDF
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  PARAMETER_MATRIX(Chi_fr);   // error correction responses
  PARAMETER_MATRIX(Psi_fr);   // error correction loadings, B_ff = Chi_fr %*% t(Psi_fr)

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,t,c;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( (Options_vec(7)==0) & (Options_vec(0)==0) ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( (Options_vec(7)==0) & (Options_vec(0)==1) ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF( Q1, bool(Options(9)) );
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), VamConfig(2), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0), this);
  // Define interaction matrix for Epsilon1
  matrix<Type> B_ff( n_f, n_f );
  B_ff = calculate_B( VamConfig(0), n_f, VamConfig(1), Chi_fr, Psi_fr );
  // PDF for Epsilon1
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), VamConfig(2), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
    if(t>=1){
      // Prediction for spatio-temporal component
      // Default, and also necessary whenever VamConfig(2)==1 & n_f!=n_c
      if( (VamConfig(0)==0) | ((n_f!=n_c) & (VamConfig(2)==1)) ){
        // If no interactions, then just autoregressive for factors
        Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      }else{
        // Impact of interactions, B_ff
        Epsilonmean1_sf.setZero();
        for(int s=0; s<n_s; s++){
        for(int f1=0; f1<n_f; f1++){
        for(int f2=0; f2<n_f; f2++){
          if( VamConfig(2)==0 ){
            Epsilonmean1_sf(s,f1) += B_ff(f1,f2) * Epsiloninput1_sft(s,f2,t-1);
            if( f1==f2 ) Epsilonmean1_sf(s,f1) += Epsilon_rho1 * Epsiloninput1_sft(s,f2,t-1);
          }
          if( VamConfig(2)==1 ){
            Epsilonmean1_sf(s,f1) += B_ff(f1,f2) * Epsilon1_sct(s,f2,t-1);
            if( f1==f2 ) Epsilonmean1_sf(s,f1) += Epsilon_rho1 * Epsilon1_sct(s,f2,t-1);
          }
        }}}
      }
      // Hyperdistribution for spatio-temporal component
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), VamConfig(2), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF( Q2, bool(Options(9)) );
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), VamConfig(2), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2), this);
  // PDF for Epsilon1
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), VamConfig(2), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
    if(t>=1){
      // Prediction for spatio-temporal component
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      // Hyperdistribution for spatio-temporal component
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), VamConfig(2), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
  }

  // Normalization of GMRFs to normalize during outer-optimization step in R
  Type jnll_GMRF = jnll_comp(0) + jnll_comp(1) + jnll_comp(2) + jnll_comp(3);
  if( include_data == 0 ){
    return( jnll_GMRF );
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4), this );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5), this );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta1_ct(c,t) = rnorm( Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1) );
      }
    }}
  }
  if( Options_vec(3)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta2_ct(c,t) = rnorm( Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2) );
      }
    }}
  }

  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( (ObsModel_ez(e_i(i),0)==11) | (ObsModel_ez(e_i(i),0)==14) ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        delta_i(i) = rnorm( Type(0.0), Type(1.0) );
      }
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  LogProb1_i.setZero();
  LogProb2_i.setZero();
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( (t_iz(i,zt)>=0) & (t_iz(i,zt)<n_t) ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Apply link function to calculate responses
      if( (ObsModel_ez(c_iz(i,0),1)==0) | (ObsModel_ez(c_iz(i,0),1)==3) ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if( (ObsModel_ez(e_i(i),0)==0) | (ObsModel_ez(e_i(i),0)==1) | (ObsModel_ez(e_i(i),0)==2) ){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0){
            LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rnorm( R2_i(i), SigmaM(e_i(i),0) );
            }
          }
          if(ObsModel_ez(e_i(i),0)==1){
            LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = exp(rnorm( log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0) ));
            }
          }
          if(ObsModel_ez(e_i(i),0)==2){
            LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rgamma( 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2) );
            }
          }
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Likelihood #2 for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      ///// Likelihood for models with discrete support
      // Zero-inflated negative binomial (not numerically stable!)
      if(ObsModel_ez(e_i(i),0)==5){
        var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rnbinom2( R2_i(i), var_i(i) );
          }
        }
      }
      // Conway-Maxwell-Poisson
      if(ObsModel_ez(e_i(i),0)==6){
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Poisson
      if(ObsModel_ez(e_i(i),0)==7){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i) );
          }
        }
      }
      // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
        /// Doesn't appear stable given spatial or spatio-temporal variation
      if(ObsModel_ez(e_i(i),0)==9){
        vector<Type> logdBinPois(4);
        logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
        logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
        for(int j=3; j<=10; j++){
          logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
        }
        logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
        if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
        if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
        if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
        if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Lognormal Poisson
      if(ObsModel_ez(e_i(i),0)==11){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
          }
        }
      }
      // Non-zero-inflated Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==12){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
        }
      }
      // Non-zero-inflated Bernoulli using cloglog link from 1st lilnear predict
      if(ObsModel_ez(e_i(i),0)==13){
        if( b_i(i)==0 ){
          LogProb2_i(i) = dpois(Type(0), R1_i(i), true);
        }else{
          LogProb2_i(i) = logspace_sub( log(Type(1.0)), dpois(Type(0), R1_i(i), true) );
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = 1;
          }
        }
      }
      // Non-zero-inflated Lognormal-Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==14){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
        }
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( (t_yz(y,z)>=0) & (t_yz(y,z)<n_t) ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( (ObsModel_ez(c,1)==0) | (ObsModel_ez(c,1)==3) ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( B_ff );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );

  SIMULATE{
    REPORT( b_i );
  }

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }
  if( Options(3)==1 ){
    vector<Type> D_i( n_i );
    D_i = R1_i * R2_i;
    ADREPORT( D_i );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( ObsModel_ez(e_i(i),0)==11 ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( t_iz(i,zt)>=0 & t_iz(i,zt)<n_t ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Responses
      if( ObsModel_ez(c_iz(i,0),1)==0 | ObsModel_ez(c_iz(i,0),1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
          //if( !isNA(c_iz(i,zc)) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel_ez(e_i(i),0)==0 | ObsModel_ez(e_i(i),0)==1 | ObsModel_ez(e_i(i),0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
          if(ObsModel_ez(e_i(i),0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
          if(ObsModel_ez(e_i(i),0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel_ez(e_i(i),0)==4 | ObsModel_ez(e_i(i),0)==5 | ObsModel_ez(e_i(i),0)==6 | ObsModel_ez(e_i(i),0)==7 | ObsModel_ez(e_i(i),0)==9 | ObsModel_ez(e_i(i),0)==11){
        if(ObsModel_ez(e_i(i),0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        }
        if(ObsModel_ez(e_i(i),0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel_ez(e_i(i),0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel_ez(c,1)==0 | ObsModel_ez(c,1)==3 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( ObsModel_ez(e_i(i),0)==11 ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( t_iz(i,zt)>=0 & t_iz(i,zt)<n_t ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Responses
      if( ObsModel_ez(c_iz(i,0),1)==0 | ObsModel_ez(c_iz(i,0),1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
          //if( !isNA(c_iz(i,zc)) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel_ez(e_i(i),0)==0 | ObsModel_ez(e_i(i),0)==1 | ObsModel_ez(e_i(i),0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
          if(ObsModel_ez(e_i(i),0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
          if(ObsModel_ez(e_i(i),0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel_ez(e_i(i),0)==4 | ObsModel_ez(e_i(i),0)==5 | ObsModel_ez(e_i(i),0)==6 | ObsModel_ez(e_i(i),0)==7 | ObsModel_ez(e_i(i),0)==9 | ObsModel_ez(e_i(i),0)==11){
        if(ObsModel_ez(e_i(i),0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        }
        if(ObsModel_ez(e_i(i),0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel_ez(e_i(i),0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel_ez(c,1)==0 | ObsModel_ez(c,1)==3 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vf.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        SCALE( AR1(L_z(1)), exp(L_z(0)) ).simulate(Tmp_c);
        eta_vf.row(v) = Tmp_c;
      }
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        eta_vf(v,f) = rnorm( Type(0.0), Type(1.0) );
      }
    }}
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  vector<Type> gmrf_s(n_s);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(c) = gmrf_s + gmrf_mean_sf.col(c);
      }
      // Rescale
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    // Simulate new values when using obj.simulate()
    if(isDouble<Type>::value && of->do_simulate) {
      SEPARABLE( AR1(L_z(1)), gmrf_Q ).simulate(gmrf_input_sf);
      gmrf_input_sf += gmrf_input_sf;
    }
    // Rescale
    logtau = L_z(0) - logkappa;  //
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    for( int f=0; f<n_f; f++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(f) = gmrf_s + gmrf_mean_sf.col(f);
      }
    }
    // Rescale
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j;
  //Type pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on beta1 (year intercepts for 1st linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- AR1 on beta2 (year intercepts for 2nd linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_INTEGER(include_data);   // Always use TRUE except for internal usage to extract GRMF normalization when turn off GMRF normalization in CPP
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SE for Index_xctl
  // Slot 1: Calculate SE for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: Calculate SE for D_i (expected density for every observation)
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  // Slot 8: Calculate proportions and SE
  // Slot 9: Include normalization in GMRF PDF
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,t,c;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( (Options_vec(7)==0) & (Options_vec(0)==0) ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( (Options_vec(7)==0) & (Options_vec(0)==1) ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF( Q1, bool(Options(9)) );
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0), this);
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF( Q2, bool(Options(9)) );
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2), this);
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
  }

  // Normalization of GMRFs to normalize during outer-optimization step in R
  Type jnll_GMRF = jnll_comp(0) + jnll_comp(1) + jnll_comp(2) + jnll_comp(3);
  if( include_data == 0 ){
    return( jnll_GMRF );
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4), this );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5), this );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta1_ct(c,t) = rnorm( Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1) );
      }
    }}
  }
  if( Options_vec(3)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta2_ct(c,t) = rnorm( Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2) );
      }
    }}
  }

  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( (ObsModel_ez(e_i(i),0)==11) | (ObsModel_ez(e_i(i),0)==14) ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        delta_i(i) = rnorm( Type(0.0), Type(1.0) );
      }
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  LogProb1_i.setZero();
  LogProb2_i.setZero();
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( (t_iz(i,zt)>=0) & (t_iz(i,zt)<n_t) ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Apply link function to calculate responses
      if( (ObsModel_ez(c_iz(i,0),1)==0) | (ObsModel_ez(c_iz(i,0),1)==3) ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if( (ObsModel_ez(e_i(i),0)==0) | (ObsModel_ez(e_i(i),0)==1) | (ObsModel_ez(e_i(i),0)==2) ){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0){
            LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rnorm( R2_i(i), SigmaM(e_i(i),0) );
            }
          }
          if(ObsModel_ez(e_i(i),0)==1){
            LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = exp(rnorm( log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0) ));
            }
          }
          if(ObsModel_ez(e_i(i),0)==2){
            LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rgamma( 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2) );
            }
          }
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Likelihood #2 for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      ///// Likelihood for models with discrete support
      // Zero-inflated negative binomial (not numerically stable!)
      if(ObsModel_ez(e_i(i),0)==5){
        var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rnbinom2( R2_i(i), var_i(i) );
          }
        }
      }
      // Conway-Maxwell-Poisson
      if(ObsModel_ez(e_i(i),0)==6){
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Poisson
      if(ObsModel_ez(e_i(i),0)==7){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i) );
          }
        }
      }
      // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
        /// Doesn't appear stable given spatial or spatio-temporal variation
      if(ObsModel_ez(e_i(i),0)==9){
        vector<Type> logdBinPois(4);
        logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
        logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
        for(int j=3; j<=10; j++){
          logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
        }
        logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
        if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
        if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
        if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
        if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Lognormal Poisson
      if(ObsModel_ez(e_i(i),0)==11){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
          }
        }
      }
      // Non-zero-inflated Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==12){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
        }
      }
      // Non-zero-inflated Bernoulli using cloglog link from 1st lilnear predict
      if(ObsModel_ez(e_i(i),0)==13){
        if( b_i(i)==0 ){
          LogProb2_i(i) = dpois(Type(0), R1_i(i), true);
        }else{
          LogProb2_i(i) = logspace_sub( log(Type(1.0)), dpois(Type(0), R1_i(i), true) );
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = 1;
          }
        }
      }
      // Non-zero-inflated Lognormal-Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==14){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
        }
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( (t_yz(y,z)>=0) & (t_yz(y,z)<n_t) ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( (ObsModel_ez(c,1)==0) | (ObsModel_ez(c,1)==3) ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );

  SIMULATE{
    REPORT( b_i );
  }

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }
  if( Options(3)==1 ){
    vector<Type> D_i( n_i );
    D_i = R1_i * R2_i;
    ADREPORT( D_i );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vf.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        SCALE( AR1(L_z(1)), exp(L_z(0)) ).simulate(Tmp_c);
        eta_vf.row(v) = Tmp_c;
      }
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        eta_vf(v,f) = rnorm( Type(0.0), Type(1.0) );
      }
    }}
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  vector<Type> gmrf_s(n_s);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(c) = gmrf_s + gmrf_mean_sf.col(c);
      }
      // Rescale
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    // Simulate new values when using obj.simulate()
    if(isDouble<Type>::value && of->do_simulate) {
      SEPARABLE( AR1(L_z(1)), gmrf_Q ).simulate(gmrf_input_sf);
      gmrf_input_sf += gmrf_input_sf;
    }
    // Rescale
    logtau = L_z(0) - logkappa;  //
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    for( int f=0; f<n_f; f++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(f) = gmrf_s + gmrf_mean_sf.col(f);
      }
    }
    // Rescale
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j;
  //Type pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on beta1 (year intercepts for 1st linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- AR1 on beta2 (year intercepts for 2nd linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_INTEGER(include_data);   // Always use TRUE except for internal usage to extract GRMF normalization when turn off GMRF normalization in CPP
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SE for Index_xctl
  // Slot 1: Calculate SE for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: Calculate SE for D_i (expected density for every observation)
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  // Slot 8: Calculate proportions and SE
  // Slot 9: Include normalization in GMRF PDF
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,t,c;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( (Options_vec(7)==0) & (Options_vec(0)==0) ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( (Options_vec(7)==0) & (Options_vec(0)==1) ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF( Q1, bool(Options(9)) );
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0), this);
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF( Q2, bool(Options(9)) );
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2), this);
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
  }

  // Normalization of GMRFs to normalize during outer-optimization step in R
  Type jnll_GMRF = jnll_comp(0) + jnll_comp(1) + jnll_comp(2) + jnll_comp(3);
  if( include_data == 0 ){
    return( jnll_GMRF );
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4), this );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5), this );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta1_ct(c,t) = rnorm( Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1) );
      }
    }}
  }
  if( Options_vec(3)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta2_ct(c,t) = rnorm( Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2) );
      }
    }}
  }

  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( (ObsModel_ez(e_i(i),0)==11) | (ObsModel_ez(e_i(i),0)==14) ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        delta_i(i) = rnorm( Type(0.0), Type(1.0) );
      }
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  LogProb1_i.setZero();
  LogProb2_i.setZero();
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( (t_iz(i,zt)>=0) & (t_iz(i,zt)<n_t) ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Apply link function to calculate responses
      if( (ObsModel_ez(c_iz(i,0),1)==0) | (ObsModel_ez(c_iz(i,0),1)==3) ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if( (ObsModel_ez(e_i(i),0)==0) | (ObsModel_ez(e_i(i),0)==1) | (ObsModel_ez(e_i(i),0)==2) ){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0){
            LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rnorm( R2_i(i), SigmaM(e_i(i),0) );
            }
          }
          if(ObsModel_ez(e_i(i),0)==1){
            LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = exp(rnorm( log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0) ));
            }
          }
          if(ObsModel_ez(e_i(i),0)==2){
            LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rgamma( 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2) );
            }
          }
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Likelihood #2 for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      ///// Likelihood for models with discrete support
      // Zero-inflated negative binomial (not numerically stable!)
      if(ObsModel_ez(e_i(i),0)==5){
        var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rnbinom2( R2_i(i), var_i(i) );
          }
        }
      }
      // Conway-Maxwell-Poisson
      if(ObsModel_ez(e_i(i),0)==6){
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Poisson
      if(ObsModel_ez(e_i(i),0)==7){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i) );
          }
        }
      }
      // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
        /// Doesn't appear stable given spatial or spatio-temporal variation
      if(ObsModel_ez(e_i(i),0)==9){
        vector<Type> logdBinPois(4);
        logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
        logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
        for(int j=3; j<=10; j++){
          logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
        }
        logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
        if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
        if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
        if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
        if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Lognormal Poisson
      if(ObsModel_ez(e_i(i),0)==11){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
          }
        }
      }
      // Non-zero-inflated Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==12){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
        }
      }
      // Non-zero-inflated Bernoulli using cloglog link from 1st lilnear predict
      if(ObsModel_ez(e_i(i),0)==13){
        if( b_i(i)==0 ){
          LogProb2_i(i) = dpois(Type(0), R1_i(i), true);
        }else{
          LogProb2_i(i) = logspace_sub( log(Type(1.0)), dpois(Type(0), R1_i(i), true) );
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = 1;
          }
        }
      }
      // Non-zero-inflated Lognormal-Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==14){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
        }
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( (t_yz(y,z)>=0) & (t_yz(y,z)<n_t) ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( (ObsModel_ez(c,1)==0) | (ObsModel_ez(c,1)==3) ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );

  SIMULATE{
    REPORT( b_i );
  }

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }
  if( Options(3)==1 ){
    vector<Type> D_i( n_i );
    D_i = R1_i * R2_i;
    ADREPORT( D_i );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vf.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        SCALE( AR1(L_z(1)), exp(L_z(0)) ).simulate(Tmp_c);
        eta_vf.row(v) = Tmp_c;
      }
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        eta_vf(v,f) = rnorm( Type(0.0), Type(1.0) );
      }
    }}
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  vector<Type> gmrf_s(n_s);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(c) = gmrf_s + gmrf_mean_sf.col(c);
      }
      // Rescale
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    // Simulate new values when using obj.simulate()
    if(isDouble<Type>::value && of->do_simulate) {
      SEPARABLE( AR1(L_z(1)), gmrf_Q ).simulate(gmrf_input_sf);
      gmrf_input_sf += gmrf_input_sf;
    }
    // Rescale
    logtau = L_z(0) - logkappa;  //
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    for( int f=0; f<n_f; f++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(f) = gmrf_s + gmrf_mean_sf.col(f);
      }
    }
    // Rescale
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j;
  //Type pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on beta1 (year intercepts for 1st linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- AR1 on beta2 (year intercepts for 2nd linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_INTEGER(include_data);   // Always use TRUE except for internal usage to extract GRMF normalization when turn off GMRF normalization in CPP
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SE for Index_xctl
  // Slot 1: Calculate SE for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: Calculate SE for D_i (expected density for every observation)
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  // Slot 8: Calculate proportions and SE
  // Slot 9: Include normalization in GMRF PDF
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,t,c;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( (Options_vec(7)==0) & (Options_vec(0)==0) ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( (Options_vec(7)==0) & (Options_vec(0)==1) ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF( Q1, bool(Options(9)) );
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0), this);
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF( Q2, bool(Options(9)) );
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2), this);
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
  }

  // Normalization of GMRFs to normalize during outer-optimization step in R
  Type jnll_GMRF = jnll_comp(0) + jnll_comp(1) + jnll_comp(2) + jnll_comp(3);
  if( include_data == 0 ){
    return( jnll_GMRF );
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4), this );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5), this );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta1_ct(c,t) = rnorm( Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1) );
      }
    }}
  }
  if( Options_vec(3)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta2_ct(c,t) = rnorm( Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2) );
      }
    }}
  }

  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( (ObsModel_ez(e_i(i),0)==11) | (ObsModel_ez(e_i(i),0)==14) ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        delta_i(i) = rnorm( Type(0.0), Type(1.0) );
      }
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  LogProb1_i.setZero();
  LogProb2_i.setZero();
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( (t_iz(i,zt)>=0) & (t_iz(i,zt)<n_t) ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Apply link function to calculate responses
      if( (ObsModel_ez(c_iz(i,0),1)==0) | (ObsModel_ez(c_iz(i,0),1)==3) ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if( (ObsModel_ez(e_i(i),0)==0) | (ObsModel_ez(e_i(i),0)==1) | (ObsModel_ez(e_i(i),0)==2) ){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0){
            LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rnorm( R2_i(i), SigmaM(e_i(i),0) );
            }
          }
          if(ObsModel_ez(e_i(i),0)==1){
            LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = exp(rnorm( log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0) ));
            }
          }
          if(ObsModel_ez(e_i(i),0)==2){
            LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rgamma( 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2) );
            }
          }
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Likelihood #2 for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      ///// Likelihood for models with discrete support
      // Zero-inflated negative binomial (not numerically stable!)
      if(ObsModel_ez(e_i(i),0)==5){
        var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rnbinom2( R2_i(i), var_i(i) );
          }
        }
      }
      // Conway-Maxwell-Poisson
      if(ObsModel_ez(e_i(i),0)==6){
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Poisson
      if(ObsModel_ez(e_i(i),0)==7){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i) );
          }
        }
      }
      // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
        /// Doesn't appear stable given spatial or spatio-temporal variation
      if(ObsModel_ez(e_i(i),0)==9){
        vector<Type> logdBinPois(4);
        logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
        logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
        for(int j=3; j<=10; j++){
          logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
        }
        logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
        if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
        if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
        if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
        if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Lognormal Poisson
      if(ObsModel_ez(e_i(i),0)==11){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
          }
        }
      }
      // Non-zero-inflated Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==12){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
        }
      }
      // Non-zero-inflated Bernoulli using cloglog link from 1st lilnear predict
      if(ObsModel_ez(e_i(i),0)==13){
        if( b_i(i)==0 ){
          LogProb2_i(i) = dpois(Type(0), R1_i(i), true);
        }else{
          LogProb2_i(i) = logspace_sub( log(Type(1.0)), dpois(Type(0), R1_i(i), true) );
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = 1;
          }
        }
      }
      // Non-zero-inflated Lognormal-Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==14){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
        }
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( (t_yz(y,z)>=0) & (t_yz(y,z)<n_t) ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( (ObsModel_ez(c,1)==0) | (ObsModel_ez(c,1)==3) ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );

  SIMULATE{
    REPORT( b_i );
  }

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }
  if( Options(3)==1 ){
    vector<Type> D_i( n_i );
    D_i = R1_i * R2_i;
    ADREPORT( D_i );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vf.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        SCALE( AR1(L_z(1)), exp(L_z(0)) ).simulate(Tmp_c);
        eta_vf.row(v) = Tmp_c;
      }
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        eta_vf(v,f) = rnorm( Type(0.0), Type(1.0) );
      }
    }}
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  vector<Type> gmrf_s(n_s);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(c) = gmrf_s + gmrf_mean_sf.col(c);
      }
      // Rescale
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    // Simulate new values when using obj.simulate()
    if(isDouble<Type>::value && of->do_simulate) {
      SEPARABLE( AR1(L_z(1)), gmrf_Q ).simulate(gmrf_input_sf);
      gmrf_input_sf += gmrf_input_sf;
    }
    // Rescale
    logtau = L_z(0) - logkappa;  //
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    for( int f=0; f<n_f; f++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(f) = gmrf_s + gmrf_mean_sf.col(f);
      }
    }
    // Rescale
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j;
  //Type pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on beta1 (year intercepts for 1st linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- AR1 on beta2 (year intercepts for 2nd linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_INTEGER(include_data);   // Always use TRUE except for internal usage to extract GRMF normalization when turn off GMRF normalization in CPP
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SE for Index_xctl
  // Slot 1: Calculate SE for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: Calculate SE for D_i (expected density for every observation)
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  // Slot 8: Calculate proportions and SE
  // Slot 9: Include normalization in GMRF PDF
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,t,c;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( (Options_vec(7)==0) & (Options_vec(0)==0) ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( (Options_vec(7)==0) & (Options_vec(0)==1) ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF( Q1, bool(Options(9)) );
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0), this);
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF( Q2, bool(Options(9)) );
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2), this);
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
  }

  // Normalization of GMRFs to normalize during outer-optimization step in R
  Type jnll_GMRF = jnll_comp(0) + jnll_comp(1) + jnll_comp(2) + jnll_comp(3);
  if( include_data == 0 ){
    return( jnll_GMRF );
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4), this );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5), this );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta1_ct(c,t) = rnorm( Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1) );
      }
    }}
  }
  if( Options_vec(3)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta2_ct(c,t) = rnorm( Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2) );
      }
    }}
  }

  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( (ObsModel_ez(e_i(i),0)==11) | (ObsModel_ez(e_i(i),0)==14) ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        delta_i(i) = rnorm( Type(0.0), Type(1.0) );
      }
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  LogProb1_i.setZero();
  LogProb2_i.setZero();
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( (t_iz(i,zt)>=0) & (t_iz(i,zt)<n_t) ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Apply link function to calculate responses
      if( (ObsModel_ez(c_iz(i,0),1)==0) | (ObsModel_ez(c_iz(i,0),1)==3) ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if( (ObsModel_ez(e_i(i),0)==0) | (ObsModel_ez(e_i(i),0)==1) | (ObsModel_ez(e_i(i),0)==2) ){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0){
            LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rnorm( R2_i(i), SigmaM(e_i(i),0) );
            }
          }
          if(ObsModel_ez(e_i(i),0)==1){
            LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = exp(rnorm( log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0) ));
            }
          }
          if(ObsModel_ez(e_i(i),0)==2){
            LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rgamma( 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2) );
            }
          }
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Likelihood #2 for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      ///// Likelihood for models with discrete support
      // Zero-inflated negative binomial (not numerically stable!)
      if(ObsModel_ez(e_i(i),0)==5){
        var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rnbinom2( R2_i(i), var_i(i) );
          }
        }
      }
      // Conway-Maxwell-Poisson
      if(ObsModel_ez(e_i(i),0)==6){
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Poisson
      if(ObsModel_ez(e_i(i),0)==7){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i) );
          }
        }
      }
      // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
        /// Doesn't appear stable given spatial or spatio-temporal variation
      if(ObsModel_ez(e_i(i),0)==9){
        vector<Type> logdBinPois(4);
        logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
        logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
        for(int j=3; j<=10; j++){
          logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
        }
        logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
        if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
        if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
        if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
        if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Lognormal Poisson
      if(ObsModel_ez(e_i(i),0)==11){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
          }
        }
      }
      // Non-zero-inflated Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==12){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
        }
      }
      // Non-zero-inflated Bernoulli using cloglog link from 1st lilnear predict
      if(ObsModel_ez(e_i(i),0)==13){
        if( b_i(i)==0 ){
          LogProb2_i(i) = dpois(Type(0), R1_i(i), true);
        }else{
          LogProb2_i(i) = logspace_sub( log(Type(1.0)), dpois(Type(0), R1_i(i), true) );
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = 1;
          }
        }
      }
      // Non-zero-inflated Lognormal-Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==14){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
        }
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( (t_yz(y,z)>=0) & (t_yz(y,z)<n_t) ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( (ObsModel_ez(c,1)==0) | (ObsModel_ez(c,1)==3) ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );

  SIMULATE{
    REPORT( b_i );
  }

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }
  if( Options(3)==1 ){
    vector<Type> D_i( n_i );
    D_i = R1_i * R2_i;
    ADREPORT( D_i );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( ObsModel_ez(e_i(i),0)==11 ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( t_iz(i,zt)>=0 & t_iz(i,zt)<n_t ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Responses
      if( ObsModel_ez(c_iz(i,0),1)==0 | ObsModel_ez(c_iz(i,0),1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
          //if( !isNA(c_iz(i,zc)) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel_ez(e_i(i),0)==0 | ObsModel_ez(e_i(i),0)==1 | ObsModel_ez(e_i(i),0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
          if(ObsModel_ez(e_i(i),0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
          if(ObsModel_ez(e_i(i),0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel_ez(e_i(i),0)==4 | ObsModel_ez(e_i(i),0)==5 | ObsModel_ez(e_i(i),0)==6 | ObsModel_ez(e_i(i),0)==7 | ObsModel_ez(e_i(i),0)==9 | ObsModel_ez(e_i(i),0)==11){
        if(ObsModel_ez(e_i(i),0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        }
        if(ObsModel_ez(e_i(i),0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel_ez(e_i(i),0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel_ez(c,1)==0 | ObsModel_ez(c,1)==3 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vf.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        SCALE( AR1(L_z(1)), exp(L_z(0)) ).simulate(Tmp_c);
        eta_vf.row(v) = Tmp_c;
      }
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate){
        eta_vf(v,f) = rnorm( Type(0.0), Type(1.0) );
      }
    }}
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer, objective_function<Type>* of){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  vector<Type> gmrf_s(n_s);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(c) = gmrf_s + gmrf_mean_sf.col(c);
      }
      // Rescale
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    // Simulate new values when using obj.simulate()
    if(isDouble<Type>::value && of->do_simulate) {
      SEPARABLE( AR1(L_z(1)), gmrf_Q ).simulate(gmrf_input_sf);
      gmrf_input_sf += gmrf_input_sf;
    }
    // Rescale
    logtau = L_z(0) - logkappa;  //
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    for( int f=0; f<n_f; f++ ){
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
      // Simulate new values when using obj.simulate()
      if(isDouble<Type>::value && of->do_simulate) {
        gmrf_Q.simulate(gmrf_s);
        gmrf_input_sf.col(f) = gmrf_s + gmrf_mean_sf.col(f);
      }
    }
    // Rescale
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j;
  //Type pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on beta1 (year intercepts for 1st linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- AR1 on beta2 (year intercepts for 2nd linear predictor) to deal with missing years: 0=No, 1=Yes
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_INTEGER(include_data);   // Always use TRUE except for internal usage to extract GRMF normalization when turn off GMRF normalization in CPP
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SE for Index_xctl
  // Slot 1: Calculate SE for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: Calculate SE for D_i (expected density for every observation)
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  // Slot 8: Calculate proportions and SE
  // Slot 9: Include normalization in GMRF PDF
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,t,c;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( (Options_vec(7)==0) & (Options_vec(0)==0) ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( (Options_vec(7)==0) & (Options_vec(0)==1) ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF( Q1, bool(Options(9)) );
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0), this);
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1), this);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF( Q2, bool(Options(9)) );
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2), this);
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3), this);
    }
  }

  // Normalization of GMRFs to normalize during outer-optimization step in R
  Type jnll_GMRF = jnll_comp(0) + jnll_comp(1) + jnll_comp(2) + jnll_comp(3);
  if( include_data == 0 ){
    return( jnll_GMRF );
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4), this );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5), this );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta1_ct(c,t) = rnorm( Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1) );
      }
    }}
  }
  if( Options_vec(3)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        beta2_ct(c,t) = rnorm( Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2) );
      }
    }}
  }

  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( (ObsModel_ez(e_i(i),0)==11) | (ObsModel_ez(e_i(i),0)==14) ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
      // Simulate new values when using obj.simulate()
      SIMULATE{
        delta_i(i) = rnorm( Type(0.0), Type(1.0) );
      }
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  LogProb1_i.setZero();
  LogProb2_i.setZero();
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( (t_iz(i,zt)>=0) & (t_iz(i,zt)<n_t) ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Apply link function to calculate responses
      if( (ObsModel_ez(c_iz(i,0),1)==0) | (ObsModel_ez(c_iz(i,0),1)==3) ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( (c_iz(i,zc)>=0) & (c_iz(i,zc)<n_c) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if( (ObsModel_ez(e_i(i),0)==0) | (ObsModel_ez(e_i(i),0)==1) | (ObsModel_ez(e_i(i),0)==2) ){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0){
            LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rnorm( R2_i(i), SigmaM(e_i(i),0) );
            }
          }
          if(ObsModel_ez(e_i(i),0)==1){
            LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = exp(rnorm( log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0) ));
            }
          }
          if(ObsModel_ez(e_i(i),0)==2){
            LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
            // Simulate new values when using obj.simulate()
            SIMULATE{
              b_i(i) = rgamma( 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2) );
            }
          }
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Likelihood #2 for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      ///// Likelihood for models with discrete support
      // Zero-inflated negative binomial (not numerically stable!)
      if(ObsModel_ez(e_i(i),0)==5){
        var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rnbinom2( R2_i(i), var_i(i) );
          }
        }
      }
      // Conway-Maxwell-Poisson
      if(ObsModel_ez(e_i(i),0)==6){
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Poisson
      if(ObsModel_ez(e_i(i),0)==7){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i) );
          }
        }
      }
      // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
        /// Doesn't appear stable given spatial or spatio-temporal variation
      if(ObsModel_ez(e_i(i),0)==9){
        vector<Type> logdBinPois(4);
        logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
        logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
        for(int j=3; j<=10; j++){
          logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
        }
        logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
        logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
        if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
        if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
        if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
        if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = 0;   // Option not available
        }
      }
      // Zero-inflated Lognormal Poisson
      if(ObsModel_ez(e_i(i),0)==11){
        if( b_i(i)==0 ){
          //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rbinom( Type(1), R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = rpois( R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
          }
        }
      }
      // Non-zero-inflated Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==12){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
        }
      }
      // Non-zero-inflated Bernoulli using cloglog link from 1st lilnear predict
      if(ObsModel_ez(e_i(i),0)==13){
        if( b_i(i)==0 ){
          LogProb2_i(i) = dpois(Type(0), R1_i(i), true);
        }else{
          LogProb2_i(i) = logspace_sub( log(Type(1.0)), dpois(Type(0), R1_i(i), true) );
        }
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i) );
          if( b_i(i)>0 ){
            b_i(i) = 1;
          }
        }
      }
      // Non-zero-inflated Lognormal-Poisson using log link from 1st linear predictor
      if(ObsModel_ez(e_i(i),0)==14){
        LogProb2_i(i) = dpois(b_i(i), R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)), true);
        // Simulate new values when using obj.simulate()
        SIMULATE{
          b_i(i) = rpois( R1_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-0.5*pow(SigmaM(e_i(i),0),2)) );
        }
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( (t_yz(y,z)>=0) & (t_yz(y,z)<n_t) ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( (ObsModel_ez(c,1)==0) | (ObsModel_ez(c,1)==3) ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );

  SIMULATE{
    REPORT( b_i );
  }

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }
  if( Options(3)==1 ){
    vector<Type> D_i( n_i );
    D_i = R1_i * R2_i;
    ADREPORT( D_i );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // IID
  if(n_f == -2){
    for( int c=0; c<n_c; c++ ){
      if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
      if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
      jnll_pointer += gmrf_Q(gmrf_input_sf.col(c) - gmrf_mean_sf.col(c));
      gmrf_sc.col(c) = gmrf_input_sf.col(c) / exp(logtau) * L_z(c);                                // Rescaling from comp_index_v1d.cpp
    }
  }
  // Turn off
  if(n_f == -1){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_e);         // Number of error distributions
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IMATRIX(ObsModel_ez);    // Observation model
  // Column 0: Probability distribution for data for each level of e_i
  // Column 1: Link function for linear predictors for each level of c_i
  // NOTE:  nlevels(c_i) must be <= nlevels(e_i)
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IMATRIX(c_iz);         // Category for each observation
  DATA_IVECTOR(e_i);         // Error distribution for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters

  //  -- presence/absence fixed effects
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz

  // -- presence/absence random effects
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation

  //  -- positive catch rates fixed effects
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz

  // Error distribution parameters
  PARAMETER_ARRAY(logSigmaM);
  // Columns: 0=CV, 1=[usually not used], 2=[usually not used]
  // Rows:  Each level of e_i and/or c_i
  // SigmaM[,0] indexed by e_i, e.g., SigmaM(e_i(i),0)
  // SigmaM[,1] and SigmaM[,2] indexed by c_i, e.g., SigmaM(c_i(i),2)

  // -- positive catch rates random effects
  PARAMETER_VECTOR(delta_i);
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  // Slot 12 -- Likelihood of Lognormal-Poisson overdispersion delta_i
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_e, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  // Omega1
  int n_f;
  n_f = Omegainput1_sf.cols();
  array<Type> Omegamean1_sf(n_s, n_f);
  Omegamean1_sf.setZero();
  // Epsilon1
  n_f = Epsiloninput1_sft.col(0).cols();
  array<Type> Epsilonmean1_sf(n_s, n_f);
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  // Omega2
  n_f = Omegainput2_sf.cols();
  array<Type> Omegamean2_sf(n_s, n_f);
  Omegamean2_sf.setZero();
  // Epsilon2
  n_f = Epsiloninput2_sft.col(0).cols();
  array<Type> Epsilonmean2_sf(n_s, n_f);
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Penalty on lognormal-Poisson overdispesrion delta_i
  for(i=0; i<delta_i.size(); i++){
    if( ObsModel_ez(e_i(i),0)==11 ){
      jnll_comp(12) -= dnorm( delta_i(i), Type(0.0), Type(1.0), true );
    }
  }

  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  Type tmp_calc1;
  Type tmp_calc2;
  // Linear predictor (pre-link) for presence/absence component
  matrix<Type> P1_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel_ez(e,0) = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi
  vector<Type> R1_i(n_i);   
  vector<Type> log_one_minus_R1_i(n_i);
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  matrix<Type> P2_iz(n_i,c_iz.row(0).size());
  // Response predictor (post-link)
  // ObsModel_ez(e,0) = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel_ez(e,0) = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel_ez(e,0) = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)
  P1_iz.setZero();
  P2_iz.setZero();

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    if( !isNA(b_i(i)) ){
      // Linear predictors
      for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
        if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
        //if( !isNA(c_iz(i,zc)) ){
          P1_iz(i,zc) = Omega1_sc(s_i(i),c_iz(i,zc)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_iz(i,zc));
          P2_iz(i,zc) = Omega2_sc(s_i(i),c_iz(i,zc)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_iz(i,zc));
          for( int zt=0; zt<t_iz.row(0).size(); zt++ ){
            if( t_iz(i,zt)>=0 & t_iz(i,zt)<n_t ){  // isNA doesn't seem to work for IMATRIX type
              P1_iz(i,zc) += beta1_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon1_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio1_z(zt)) + eta1_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
              P2_iz(i,zc) += beta2_ct(c_iz(i,zc),t_iz(i,zt)) + Epsilon2_sct(s_i(i),c_iz(i,zc),t_iz(i,zt))*exp(log_sigmaratio2_z(zt)) + eta2_xct(s_i(i),c_iz(i,zc),t_iz(i,zt));
            }
          }
        }
      }
      // Responses
      if( ObsModel_ez(c_iz(i,0),1)==0 | ObsModel_ez(c_iz(i,0),1)==3 ){
        // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
        // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
        // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
        R1_i(i) = invlogit( P1_iz(i,0) );
        R2_i(i) = a_i(i) * exp( P2_iz(i,0) );
      }
      if( ObsModel_ez(c_iz(i,0),1)==1 ){
        // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
        // P2_i: Log-average weight;  R2_i:  Positive density prediction
        tmp_calc1 = 0;
        tmp_calc2 = 0;
        for( int zc=0; zc<c_iz.row(0).size(); zc++ ){
          if( c_iz(i,zc)>=0 & c_iz(i,zc)<n_c ){
          //if( !isNA(c_iz(i,zc)) ){
            tmp_calc1 += exp(P1_iz(i,zc));
            tmp_calc2 += exp(P1_iz(i,zc)) * exp(P2_iz(i,zc));
          }
        }
        R1_i(i) = Type(1.0) - exp( -1*a_i(i)*tmp_calc1 );
        R2_i(i) = a_i(i) * tmp_calc2 / R1_i(i);
        // log_one_minus_R1_i is useful to prevent numerical underflow e.g., for 1 - exp(-40)
        log_one_minus_R1_i(i) = -1*a_i(i)*tmp_calc1;
      }
      if( ObsModel_ez(c_iz(i,0),1)==2 ){
        // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
        // P1_i: Log-numbers density;  R1_i:  Expected numbers
        // P2_i: Log-average weight;  R2_i:  Expected average weight
        R1_i(i) = a_i(i) * exp( P1_iz(i,0) );
        R2_i(i) = exp( P2_iz(i,0) );
      }
      // Likelihood for delta-models with continuous positive support
      if(ObsModel_ez(e_i(i),0)==0 | ObsModel_ez(e_i(i),0)==1 | ObsModel_ez(e_i(i),0)==2){
        // Presence-absence likelihood
        if( b_i(i) > 0 ){
          LogProb1_i(i) = log( R1_i(i) );
        }else{
          if( ObsModel_ez(e_i(i),1)==1 ){
            LogProb1_i(i) = log_one_minus_R1_i(i);
          }else{
            LogProb1_i(i) = log( 1-R1_i(i) );
          }
        }
        // Positive density likelihood -- models with continuous positive support
        if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
          if(ObsModel_ez(e_i(i),0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(e_i(i),0), true);
          if(ObsModel_ez(e_i(i),0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(e_i(i),0),2)/2, SigmaM(e_i(i),0), true); // log-space
          if(ObsModel_ez(e_i(i),0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(e_i(i),0),2), R2_i(i)*pow(SigmaM(e_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
        }else{
          LogProb2_i(i) = 0;
        }
      }
      // Likelihood for Tweedie model with continuous positive support
      if(ObsModel_ez(e_i(i),0)==8){
        LogProb1_i(i) = 0;
        //dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
        LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(e_i(i),0), R2_i(i), R1_i(i), diag_z, Options_vec(5), Options_vec(6), true );
        diag_iz.row(i) = diag_z;
      }
      if(ObsModel_ez(e_i(i),0)==10){
        // Packaged code
        LogProb1_i(i) = 0;
        // dtweedie( Type y, Type mu, Type phi, Type p, int give_log=0 )
        // R1*R2 = mean
        LogProb2_i(i) = dtweedie( b_i(i), R1_i(i)*R2_i(i), R1_i(i), invlogit(SigmaM(e_i(i),0))+1.0, true );
      }
      // Likelihood for models with discrete support
      if(ObsModel_ez(e_i(i),0)==4 | ObsModel_ez(e_i(i),0)==5 | ObsModel_ez(e_i(i),0)==6 | ObsModel_ez(e_i(i),0)==7 | ObsModel_ez(e_i(i),0)==9 | ObsModel_ez(e_i(i),0)==11){
        if(ObsModel_ez(e_i(i),0)==5){
          // Zero-inflated negative binomial (not numerically stable!)
          var_i(i) = R2_i(i)*(1.0+SigmaM(e_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_iz(i,0),1);
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dnbinom2(Type(0.0),R2_i(i),var_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
          }else{
            LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==6){
          // Conway-Maxwell-Poisson
          LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_iz(i,0)), true, Options_vec(5));
        }
        if(ObsModel_ez(e_i(i),0)==7){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        if(ObsModel_ez(e_i(i),0)==9){
          // Binned Poisson (for REEF data: 0=none; 1=1; 2=2-10; 3=>11)
          /// Doesn't appear stable given spatial or spatio-temporal variation
          vector<Type> logdBinPois(4);
          logdBinPois(0) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0), R2_i(i), true) + log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          logdBinPois(1) = dpois(Type(1.0), R2_i(i), true) + log(R1_i(i));                                 //  Pr[X | X>0] = Pois(X)*phi
          logdBinPois(2) = dpois(Type(2.0), R2_i(i), true) + log(R1_i(i));                                 // SUM_J( Pr[X|X>0] ) = phi * SUM_J( Pois(J) )
          for(int j=3; j<=10; j++){
            logdBinPois(2) += logspace_add( logdBinPois(2), dpois(Type(j), R2_i(i), true) + log(R1_i(i)) );
          }
          logdBinPois(3) = logspace_sub( log(Type(1.0)), logdBinPois(0) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(1) );
          logdBinPois(3) = logspace_sub( logdBinPois(3), logdBinPois(2) );
          if( b_i(i)==0 ) LogProb2_i(i) = logdBinPois(0);
          if( b_i(i)==1 ) LogProb2_i(i) = logdBinPois(1);
          if( b_i(i)==2 ) LogProb2_i(i) = logdBinPois(2);
          if( b_i(i)==3 ) LogProb2_i(i) = logdBinPois(3);
        }
        if(ObsModel_ez(e_i(i),0)==11){
          // Zero-inflated Poisson
          if( b_i(i)==0 ){
            //LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
            LogProb2_i(i) = logspace_add( log(1-R1_i(i)), dpois(Type(0.0),R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2),true)+log(R1_i(i)) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
          }else{
            LogProb2_i(i) = dpois(b_i(i), R2_i(i)*exp(SigmaM(e_i(i),0)*delta_i(i)-pow(SigmaM(e_i(i),0),2)/2), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
          }
        }
        LogProb1_i(i) = 0;
      }
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );
  REPORT( tmp_calc1 );
  REPORT( tmp_calc2 );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel_ez(e,0)==4 isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel_ez(c,1)==0 | ObsModel_ez(c,1)==3 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
    if( ObsModel_ez(c,1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = exp( P1_xcy(x,c,y) ) * exp( P2_xcy(x,c,y) );        // Use this line to prevent numerical over/underflow
    }
    if( ObsModel_ez(c,1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
      D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
    }
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( B_y );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel_ez[c,1]==1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Calculate proportion of biomass for each category
  if( Options(8)==1 ){
    array<Type> PropIndex_cyl(n_c, n_y, n_l);
    array<Type> ln_PropIndex_cyl(n_c, n_y, n_l);
    Type sumtemp;
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){                 // .col(0).cols()
      sumtemp = 0;
      for(int c=0; c<n_c; c++){
        sumtemp += Index_cyl(c,y,l);
      }
      for(int c=0; c<n_c; c++){
        PropIndex_cyl(c,y,l) = Index_cyl(c,y,l) / sumtemp;
      }
    }}
    ln_PropIndex_cyl = log( PropIndex_cyl );
    REPORT( PropIndex_cyl );
    REPORT( ln_PropIndex_cyl );
    ADREPORT( PropIndex_cyl );
    ADREPORT( ln_PropIndex_cyl );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_iz );
  REPORT( P2_iz );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Compare Apportionment Between VAST and
#                                                              RE Model for GOA - Running in Series to Permit Bias Corr
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 5.11.17
#
#Purpose: Evaluate apportionment (Eastern, Central, Western) for Gulf of Alaska RACE
#           Bottom Trawl indices of abundance. 
#             1) Evaluate across three alternative knot numbers
#             2) Several specifications for autocorrelation in EC and PCR intercept
#
#
#==================================================================================================
#NOTES:
#  a) Spiny Dogfish removed from evaluation because design based index is 0 for Western GOA in 2013.
#  b) Big Skate Causes Problems... not diagnosed.
#  c) Non-convergence with intercepts estimted as IaY i.e. ==1, independent of spatio-temporal RE specs.
#  d) Must estimate spatio-temporal random effects, fails to converge without: flag.spatial <- c(TRUE) ONLY
#  e) Convergence failure when trying to do partial bias correction results in the 3-column error message. 
#  f) rho  4,4,4,4, fails on Northern Rockfish, Epsilon_rho1 (autocorr coeff for EP) has HIGH gradient.
#  g) GOA Harlequin Rockfish causes problem with AR - hessian not positive definite.
#      Epsilon_rho1, Epsilon_rho2, and Beta_rho2 all at upper limits.
#  h) ObsModel = c(1, 1) #Lognormal - Poisson-link Delta, 100 knots - Spiny Dogfish - rho=c(2,2,2,2)
#       ADMB Fail "Error: Invalid index 4 used for array range [1, 3] in "dvector& dmatrix::operator[] (int i)". 
#                     matrix bound exceeded -- row index too high"

#  i) Big Skate has trouble with AR intercepts, mixed convergence max grad 0.02
#==================================================================================================
#TIMING:
# 100 knots obs model c(1,1) - lognormal with poisson-link delta - no bias correction, spatial==TRUE only, and rho (2,2,2,2 and 4,4,4,4)
# [1] "### START: Tue Jan 29 09:38:47 2019"
# [1] "### END: Tue Jan 29 13:25:35 2019"

# [1] "### START: Sun Apr 14 22:50:32 2019"
# [1] "### END: Mon Apr 15 06:46:08 2019"

#Lognormal with Po
##==================================================================================================
# require(SpatialDeltaGLMM)
require(VAST)
require(TMB)
require(TMBhelper)
require(parallel)
require(snowfall)
require(ggplot2)
require(R2admb)
require(reshape2)
require(gridExtra)
require(ggthemes)
require(cowplot)
require(RANN)
require(PBSmodelling)
require(PBSmapping)
require(XML)
require(FishStatsUtils)


source("R/calc-design-based-index.r")
source("R/create-VAST-input.r")
source("R/create-Data-Geostat.r")
source("R/load-RACE-data.r")

source("R/cleanup-VAST-file.r")
source("R/get-VAST-index.r")

source("R/run-RE-model.r")


home.dir <- getwd()
#Create working directory
working.dir <- paste0(home.dir, "/examples/Test_Apportion")

alpha <- 1

#Determine species list
species.list <- read.csv("data/eval_species_list.csv", stringsAsFactors=FALSE)

#Limit species included
species.list <- species.list[species.list$include=='Y',]
#Limit to GOA
species.list <- species.list[species.list$survey=='GOA',]
# species.list <- species.list[species.list$name!='Spiny dogfish' & species.list$name!='Big skate',]
species.list <- species.list[species.list$name!='Spiny dogfish',]

n.species <- nrow(species.list)

#Create
species.series <- c(1:n.species)

#=======================================================================
##### CONTROL SECTION #####
#Specify process error terms for RE model
# n_PE <- 3
# PE_vec <- c(1:3)

n_PE <- 1
PE_vec <- rep(1,3)
#####

#Number of cores to use
n.cores <- detectCores()-1

#Boolean for running estimation models
do.estim <- FALSE

#Trial Knot Numbers
trial.knots <- c(100)
n.trial.knots <- length(trial.knots)

#Trial AUTOREGRESSIVE specifications
#Note starts at 0
# rho.int.types <- c('Fixed_Effect','Independent_Among_Years','Random_Walk','Constant_Intercept','Autoregressive')
rho.int.types <- c('FE','IaY','RW','CI','AR')
# rho.stRE.types <- c('Independent_Among_Years',NA,'Random_Walk',NA,'Autoregressive')
rho.stRE.types <- c('IaY',NA,'RW',NA,'AR')

#Read in Autoregressive Input
# trial.rho <- t(read.csv('Data/Test-Autoregressive-Input.csv', header=TRUE, stringsAsFactors=FALSE)[,-c(1:2)])
# trial.rho <- matrix(c(1,1,0,0,
#                       2,2,0,0,
#                       4,4,0,0,
#                       1,1,4,4,
#                       1,1,2,2,
#                       2,2,4,4),ncol=4, nrow=6, byrow=TRUE)

# trial.rho <- matrix(c(2,2,0,0,
#                       4,4,0,0,
#                       2,2,4,4,
#                       4,4,2,2),ncol=4, nrow=4, byrow=TRUE)

# trial.rho <- matrix(c(2,2,0,0,
#                       4,4,0,0,
#                       2,2,2,2,
#                       4,4,4,4),ncol=4, nrow=4, byrow=TRUE)

# trial.rho <- matrix(c(2,2,2,2,
#                       4,4,4,4
#                       ),ncol=4, nrow=2, byrow=TRUE)

# trial.rho <- matrix(c(2,2,2,2,
#                       4,4,2,2
#                       ),ncol=4, nrow=2, byrow=TRUE)

trial.rho <- matrix(c(0,0,2,2,
                      2,2,2,2,
                      4,4,2,2
                      ),ncol=4, nrow=3, byrow=TRUE)

n.trial.rho <- nrow(trial.rho)

flag.spatial <- c(TRUE)#,FALSE)
n.flag.spatial <- length(flag.spatial)
# #Intercept
# rho.inter.ep <- c(0) #Encounter Probability Component 
# rho.inter.pcr <- c(0) #Positive Catch Rate Component
# #Spatio-temporal RE
# rho.stRE.ep #Encounter Probability Component 
# rho.stRE.pcr #Positive Catch Rate Component

#Boolean for bias correction
bias.correct <- FALSE
#=======================================================================
##### Run VAST model  #####
Version <- "VAST_v4_0_0"
lat_lon.def <- "start"

#SPATIAL SETTINGS
Method = c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km = 25
# n_x = c(100, 250, 500, 1000, 2000)[2] # Number of stations
Kmeans_Config = list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )

#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("Western","Central",'Eastern'),
                            west_border = c(-Inf, -159, -147),
                            east_border = c(-159, -147, Inf))

#MODEL SETTINGS
# FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
# RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

ObsModel = c(1, 1) #Lognormal - Poisson-link Delta
# ObsModel = c(1, 0) #Lognormal - Delta

#SPECIFY OUTPUTS
Options = c(SD_site_density = 0, SD_site_logdensity = 0,
            Calculate_Range = 0, Calculate_evenness = 0, Calculate_effective_area = 0,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)

#Output Directory Name
output.dir <- paste0(working.dir,"/output_bias.correct_",bias.correct, " n_PE-",n_PE)
dir.create(output.dir)


#=======================================================================
##### WRAPPER FUNCTION FOR RUNNING IN PARALLEL #####

# s <- 1 #Spiny dogfish
# for(s in 1:n.species) {

#=======================================================================
##### Loop Through Trial Knots  ##### 
vast_est.output <- vector('list', length=(n.trial.knots * n.trial.rho * n.flag.spatial))
vast_knots <- vector(length=(n.trial.knots * n.trial.rho * n.flag.spatial))
vast_rho.int <- vector(length=(n.trial.knots * n.trial.rho * n.flag.spatial))
vast_rho.stRE <- vector(length=(n.trial.knots * n.trial.rho * n.flag.spatial))
vast_do.spatial <-  vector(length=(n.trial.knots * n.trial.rho * n.flag.spatial))

if(do.estim==TRUE) {
  
  
  time.1 <- date()
  
  #Counter for knots by rho
  counter <- 1
  
  t <- 1
  for(t in 1:n.trial.knots) {
    print(paste('## Trial Knot Number',t,'of',n.trial.knots))
    print(paste('# Trial Knots:',trial.knots[t]))
    #Specify trial observation model
    
    #Specify knots
    n_x <- trial.knots[t]
    
    r <- 1
    # r <- 3
    for(r in 1:n.trial.rho) {
      #Specify intercepts and spatio-temporal variation across time
      RhoConfig <- trial.rho[r,]
      names(RhoConfig) <- c('Beta1','Beta2','Epsilon1','Epsilon2')
      
      f <- 1
      for(f in 1:n.flag.spatial) {
        do.spatial <- flag.spatial[f]
        # print(paste('f',f))
        #Turn ON/OFF spatial components
        if(do.spatial==TRUE) {
          #ON
          FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
        }else {
          #OFF
          FieldConfig = c(Omega1 = 0, Epsilon1 = 1, Omega2 = 0, Epsilon2 = 1)
        }
        vast_do.spatial[counter] <- do.spatial
        
        #Record
        if(RhoConfig[1]==RhoConfig[2]) {#IF intercept specs are the same
          vast_rho.int[counter] <- rho.int.types[RhoConfig[1]+1]
        }else {#IF different
          vast_rho.int[counter] <- paste0(rho.int.types[RhoConfig[1]+1], "-", rho.int.types[RhoConfig[2]+1])
        }
        if(RhoConfig[3]==RhoConfig[4]) {
          vast_rho.stRE[counter] <- rho.stRE.types[RhoConfig[3]+1]
        }else {
          vast_rho.stRE[counter] <- paste0(rho.stRE.types[RhoConfig[3]+1], "-", rho.stRE.types[RhoConfig[4]+1])
        }
        #Update Knot List
        vast_knots[counter] <- n_x
        
        #Setup File
        trial.dir <- paste0(working.dir,"/",n_x,"_bias.corr_",bias.correct)
        dir.create(trial.dir)
        trial.dir <- paste0(trial.dir, "/int_",vast_rho.int[counter], 
                            " stRE_",vast_rho.stRE[counter], 
                            " do.spatial_",vast_do.spatial[counter])
        dir.create(trial.dir)
        
        
        #=======================================================================
        ##### SNOWFALL CODE FOR PARALLEL #####
        # sfInit(parallel=TRUE, cpus=n.cores, type='SOCK')
        # sfExportAll() #Exportas all global variables to cores
        # sfLibrary(TMB)  #Loads a package on all nodes
        # sfLibrary(VAST)
        # output <- sfLapply(species.series, fun=wrapper_fxn, n_x=n_x, RhoConfig=RhoConfig,
        #                    n_PE=n_PE, PE_vec=PE_vec, FieldConfig=FieldConfig)
        # #
        # # temp.out <- wrapper_fxn(s=1, n_x=n_x, RhoConfig=RhoConfig,
        # #                         n_PE=n_PE, PE_vec=PE_vec, FieldConfig=FieldConfig, bias.correct=bias.correct, species.list=species.list)
        # 
        # sfStop()
        
        output <- vector('list', length=n.species)
        s <- 1
        for(s in species.series) {
          # output[[s]]
          # wrapper_fxn <- function(ss, n_x, RhoConfig, n_PE, PE_vec, FieldConfig) {
            # require(TMB)
            # require(TMBhelper)
            # require(VAST)
            
            #Define file for analyses
            DateFile <- paste0(trial.dir,"/",species.list$survey[s],"_",species.list$name[s],"/")
            
            dir.create(DateFile, recursive=TRUE)
            
            #Define species.codes
            species.codes <- species.list$species.code[s]
            survey <- species.list$survey[s]
            
            #=======================================================================
            ##### READ IN DATA AND BUILD VAST INPUT #####
            #  NOTE: this will create the DateFile
            VAST_input <- create_VAST_input(species.codes=species.codes, combineSpecies=FALSE,
                                            lat_lon.def=lat_lon.def, save.Record=FALSE,
                                            Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                            Kmeans_Config=Kmeans_Config,
                                            strata.limits=strata.limits, survey=survey,
                                            DateFile=DateFile,
                                            FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                            OverdispersionConfig=OverdispersionConfig,
                                            ObsModel=ObsModel, Options=Options, Version=Version)
            
            # str(VAST_input)
            #Unpack
            TmbData <- VAST_input$TmbData
            Data_Geostat <- VAST_input$Data_Geostat
            Spatial_List <- VAST_input$Spatial_List
            Extrapolation_List <- VAST_input$Extrapolation_List
            
            # print(TmbData)
            
            print('Before create TmbList')
            #=======================================================================
            ##### RUN VAST #####
            #Build TMB Object
            #  Compilation may take some time - ERROR HERE
            TmbList <- VAST::Build_TMB_Fn(TmbData = TmbData, RunDir = DateFile,
                                          Version = Version, 
                                          Q_Config=FALSE, CovConfig=FALSE,
                                          RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                                          Method = Method)
            print('After create TmbList')
            
            Obj <- TmbList[["Obj"]]
            
            if(bias.correct==FALSE) {
              Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
                                         upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
                                         bias.correct = bias.correct, newtonsteps=1)
              # summary(Opt)
            }else {
              # #NEW: Only Bias Correct Index
              # Opt <- TMBhelper::Optimize(obj=Obj, lower=TmbList[["Lower"]], 
              #                            upper=TmbList[["Upper"]], getsd=TRUE, savedir=DateFile, 
              #                            bias.correct=bias.correct, newtonsteps=1,
              #                            bias.correct.control=list(sd=TRUE, nsplit=10, split=NULL,
              #                                                      vars_to_correct="Index_cyl"))
              Opt <- TMBhelper::Optimize(obj=Obj, lower=TmbList[["Lower"]], 
                                  upper=TmbList[["Upper"]], getsd=TRUE, savedir=DateFile, 
                                  bias.correct=bias.correct, newtonsteps=1)
            }
            #Save output
            # Report = Obj$report()
            # Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
            # save(Save, file=paste0(DateFile,"Save.RData"))
            
            #HESSIAN NOT POSITIVE DEFINITE
            # bs <- sdreport( obj=Obj, bias.correct=FALSE )  #
            # Opt$SD <- bs
            #Calculate index values
            # TmbData = TmbData, Sdreport = Opt[["SD"]]
            vast_est <- get_VAST_index(TmbData=TmbData, Sdreport=Opt[["SD"]], bias.correct=bias.correct, Data_Geostat=Data_Geostat)
            #========================================================================
            ##### DIAGNOSTIC AND PREDICTION PLOTS #####
            # plot_VAST_output(Opt, Report, DateFile, survey, TmbData, Data_Geostat, Extrapolation_List, Spatial_List)
            
            #========================================================================
            ##### CLEANUP VAST OUTPUT #####
            # cleanup_VAST_file(DateFile=DateFile, Version=Version) #No longer necessary as we are deleting everything at the end
            
            rm("VAST_input", "TmbData", "Data_Geostat", "Spatial_List", "Extrapolation_List",
               "TmbList", "Obj","Save","Report")#, "Save")#, "Opt", "Report")
            #========================================================================
            setwd(home.dir)
            #========================================================================
            ##### CALL RE MODEL #####
            #Calculate Separate Design-based indices for GOA
            temp.west <- calc_design_based_index(species.codes=species.codes, survey=survey, reg.area="WESTERN GOA")
            temp.cent <- calc_design_based_index(species.codes=species.codes, survey=survey, reg.area="CENTRAL GOA")  
            temp.east <- calc_design_based_index(species.codes=species.codes, survey=survey, reg.area="EASTERN GOA")
            
            #Bring Together (except 2001 when Eastern GOA was not surveyed)
            input.yrs <- sort(unique(temp.west$YEAR))
            #Remove 2001
            input.yrs <- input.yrs[-which(input.yrs==2001)]
            
            #Index values
            input.idx <- data.frame(temp.west$Biomass[temp.west$YEAR %in% input.yrs],
                                    temp.cent$Biomass[temp.cent$YEAR %in% input.yrs],
                                    temp.east$Biomass[temp.east$YEAR %in% input.yrs])
            names(input.idx) <- c("Western","Centeral","Eastern")
            input.idx <- as.matrix(input.idx)
            
            #Index CV
            input.cv <- data.frame(temp.west$CV[temp.west$YEAR %in% input.yrs],
                                   temp.cent$CV[temp.cent$YEAR %in% input.yrs],
                                   temp.east$CV[temp.east$YEAR %in% input.yrs])
            
            names(input.cv) <- c("Western","Centeral","Eastern")
            input.cv <- as.matrix(input.cv)
            
            #Copy, compile, call ADMB-RE model
            biomA <- run_RE_model(input.yrs, input.idx, input.cv, DateFile, home.dir, n_PE=n_PE, PE_vec=PE_vec)
            
            #========================================================================
            setwd(home.dir)
            
            ##### RETURN SECTION #####
            out <- NULL
            out$vast_est <- vast_est
            out$Opt <- Opt
            out$biomA <- biomA
          #   return(out)
          # } 
          
          #RETURN SECTION ============
          output[[s]] <- out
          
        }#next s
        
        #Add to list
        vast_est.output[[counter]] <- output
        #Save Object for storage
        saveRDS(output, file=paste0(output.dir,"/VAST_output_",counter,".rds"))
        #Update Counter
        # print(counter)
        counter <- counter + 1
      }#next f
      
    }#next r
  }#next t
  
  #Dimensions for vast_est.output are 1) Trial knots, 2) Species
  # vast_est.output[[1:n.trial.knots]][[1:n.species]]
  
  #Create output directory
  # dir.create(output.dir)
  # save(vast_est.output, file=paste0(output.dir,"/vast_est.output.RData"))
  #Also save specifications
  vast_specs <- data.frame(vast_knots, vast_rho.int, vast_rho.stRE, vast_do.spatial)
  write.csv(vast_specs, file=paste0(output.dir,"/vast_specs.csv"))
  
  #=======================================================================
  ##### DELETE UNNECESSARY FILE STRUCTURE #####
  #Must reset working directory
  setwd(working.dir)
  t <- 1
  for(t in 1:n.trial.knots) {
    unlink(paste0(working.dir,"/",trial.knots[t],"_bias.corr_",bias.correct), recursive=TRUE)
  }#next t
  
  time.2 <- date()
  
  print(paste('### START:', time.1))
  print(paste('### END:', time.2))
  
  #Need to re-set working directory
  setwd(home.dir)
  
}else {
  #Old
  # load(paste0(output.dir,"/vast_est.output.RData"))
  
  #New
  specs <- read.csv(paste0(output.dir,"/vast_specs.csv"), header=TRUE, stringsAsFactors=FALSE)
  n.specs <- nrow(specs)
  
  for(i in 1:n.specs) {
    print(i)
    vast_est.output[[i]] <- readRDS(file=paste0(output.dir, "/VAST_output_",i,".rds"))
    vast_knots[i] <- specs$vast_knots[i]
    vast_rho.int[i] <- specs$vast_rho.int[i]
    vast_rho.stRE[i] <- specs$vast_rho.stRE[i]
    vast_do.spatial[i] <- specs$vast_do.spatial[i]
  }#next i
}

#Plot the output
#  First example is using not fully specified output


# plot.dat <- vast_est.output[[3]] #500 RW
# plot.dat <- vast_est.output[[4]] #500 AR
# plot.dat <- vast_est.output[[1]] #100 RW
#Determine years


#Loop through species
#
re.list <- NULL
vast.list <- NULL
aic.list <- NULL
aic.vect <- vector(length=0)
converge.vect <- vector(length=0)
maxGrad.vect <- vector(length=0)

#Determine true survey years for flag
goa.yrs <- sort(unique(load_RACE_data(species.codes=30420, survey='GOA')$Year))


s <- 1
for(s in 1:n.species) {
  #Species Information
  temp.species <- species.list$name[s]
  temp.survey <- species.list$survey[s]
  temp.name <- paste0(temp.survey,": ",temp.species)
  
  #VAST
  i <- 1
  for(i in 1:n.specs) { 
    
    #Update Convergence and AIC estimates
    aic.vect <- append(aic.vect, vast_est.output[[i]][[s]]$Opt$AIC)
    converge.vect <- append(converge.vect, ifelse(vast_est.output[[i]][[s]]$Opt$Convergence_check=="There is no evidence that the model is not converged", TRUE, FALSE))
    # maxGrad.vect <- append(maxGrad.vect, max(abs(vast_est.output[[i]][[s]]$Opt$diagnostics$final_gradient)))  #Equivalent
    maxGrad.vect <- append(maxGrad.vect, vast_est.output[[i]][[s]]$Opt$max_gradient)
    
    yrs <- sort(unique(vast_est.output[[i]][[s]]$vast_est$Year))
    n.yrs <- length(yrs)
    
    survey.year <- vast_est.output[[i]][[s]]$vast_est$Year %in% goa.yrs
    
    indices <- c('Western','Central','Eastern')
    n.indices <- length(indices)
    
    #VAST
    dat.vast <- vast_est.output[[i]][[s]]$vast_est
    #Convert to a matrix
    temp.vast <- matrix(nrow=n.yrs, ncol=n.indices, dimnames=list(yrs, indices))
    j <- 1
    for(j in 1:n.indices) {
      temp.vast[,j] <- dat.vast$Estimate_metric_tons[dat.vast$Fleet==j]
    }
    prop.vast <- temp.vast
    y <- 1
    for(y in 1:n.yrs) {
      prop.vast[y,] <- prop.vast[y,]/sum(temp.vast[y,])
    }
    
    temp.RhoConfig <- paste(vast_rho.int[i], "+",vast_rho.stRE[i])
    
    #Create the grand list
    temp.vast <- melt(prop.vast)
    model <- 'VAST'
    temp.vast <- cbind(temp.vast, model, temp.survey, temp.species, temp.name, vast_knots[i], 
                       vast_rho.int[i], vast_rho.stRE[i], temp.RhoConfig, survey.year,
                       vast_do.spatial[i])
    
    #Skeleton in AIC/convergence data frame
    temp.aic <- cbind(temp.survey, temp.species, temp.name, 'VAST', vast_knots[i], 
                      vast_rho.int[i], vast_rho.stRE[i], temp.RhoConfig,
                      vast_do.spatial[i])
    
    #Combine to larger lists
    #VAST list
    vast.list <- rbind(vast.list, temp.vast)
    #AIC
    aic.list <- rbind(aic.list, temp.aic) 
    
  }#next i
  
  #ADMB-RE
  #Random Effects Model
  temp.re <- vast_est.output[[i]][[s]]$biomA
  prop.re <- temp.re
  y <- 1
  for(y in 1:n.yrs) {
    prop.re[y,] <- prop.re[y,]/sum(temp.re[y,])
  }
  dimnames(prop.re) <- list(yrs, indices)
  
  #Create larger list
  temp.re <- melt(prop.re)
  model <- 'ADMB-RE'
  temp.re <- cbind(temp.re, model, temp.survey, temp.species, temp.name, FALSE, FALSE, FALSE, "ADMB-RE", 
                   survey.year, NA)
  #Randome-effects list
  re.list <- rbind(re.list, temp.re)
  
}#next s

#Add names
re.df <- data.frame(re.list)
names(re.df) <- c('Year','Region','value','Model','Survey','Species', 'Name','Knots',
                  'Rho_Intercept','Rho_stRE','RhoConfig','SurveyYear','Est_Spatial_RE')

vast.df <- data.frame(vast.list)
names(vast.df) <- c('Year','Region','value','Model','Survey','Species', 'Name','Knots',
                    'Rho_Intercept','Rho_stRE','RhoConfig','SurveyYear','Est_Spatial_RE')

#Bind Together
output.df <- rbind(re.df, vast.df)

aic.df <- data.frame(aic.list, aic.vect, converge.vect, maxGrad.vect)

names(aic.df) <- c('Survey','Species','Name','Model','Knots',
                   'Rho_Intercept','Rho_stRE','RhoConfig','Est_Spatial_RE',
                   'AIC','Converge','maxGradient')
aic.df$Converge <- as.factor(aic.df$Converge)

#===========================================================
#Remove the FE + IaY Model
#Unobserved estimates are unreliable
output.df <- output.df[output.df$RhoConfig!='FE + IaY',]
aic.df <- aic.df[aic.df$RhoConfig!='FE + IaY',]


#===========================================================
#Plot the Max Gradients
g <- ggplot(aic.df, aes(x=RhoConfig, y=maxGradient, color=Knots)) +
  theme_gray() +
  geom_point() +
  # facet_grid(Est_Spatial_RE~Species, scales='free') +
  facet_wrap(~Species, ncol=4, scales='free_y') +
  theme(axis.text.x=element_text(angle=45, hjust=1)) +
  geom_hline(yintercept=1e-3)
g
ggsave(paste0(output.dir,'/maxGradient.png'), plot=g, height=5, width=8, units='in', dpi=250)

g <- ggplot(aic.df, aes(x=RhoConfig, y=Converge, color=Knots)) +
  theme_gray() +
  geom_point() +
  # facet_grid(Est_Spatial_RE~Species, scales='free') +
  facet_wrap(~Species, ncol=4) +
  theme(axis.text.x=element_text(angle=45, hjust=1))
g
ggsave(paste0(output.dir,'/Convergence.png'), plot=g, height=5, width=8, units='in', dpi=250)




#Plotting Rockfish ===========================================================

heights <- 9
widths <- 7

rockfish <- c('Pacific ocean perch','Northern rockfish','Harlequin rockfish')

t <- 1
for(t in 1:n.trial.knots) {
  temp.knots <- trial.knots[t]
  
  #Estimate Spatial RE
  do.spatial <- TRUE
  temp.df <- output.df[which(output.df$Species %in% rockfish),]
  temp.df <- temp.df[temp.df$Est_Spatial_RE==do.spatial | is.na(temp.df$Est_Spatial_RE),]
  temp.df$Species <- gsub(" ", "\n", temp.df$Species)
  
  g <- ggplot(temp.df, aes(x=Year, y=value, fill=Region)) +
    theme_dark()+
    theme(legend.position='top') +
    scale_fill_colorblind() +
    geom_area(position='stack', alpha=alpha) +
    facet_grid(Species~RhoConfig) +
    ggtitle(paste('Estimate Spatial RE:', do.spatial)) +#, 
    # subtitle=paste('Knots:',temp.knots)) +
    ylab('Proportion') +
    theme(axis.text.x=element_text(angle=45, hjust=1))
  # g
  ggsave(paste0(output.dir,'/Rockfish ', temp.knots,'kt-STRE.png'), plot=g, 
         height=heights/2, width=widths, units='in', dpi=1e3)
  
  #Don't Estimate Spatial RE
  do.spatial <- FALSE
  temp.df <- output.df[which(output.df$Species %in% rockfish),]
  temp.df <- temp.df[temp.df$Est_Spatial_RE==do.spatial | is.na(temp.df$Est_Spatial_RE),]
  temp.df$Species <- gsub(" ", "\n", temp.df$Species)
  
  g2 <- ggplot(temp.df, aes(x=Year, y=value, fill=Region)) +
    theme_dark()+
    theme(legend.position='top') +
    scale_fill_colorblind() +
    geom_area(position='stack', alpha=alpha) +
    facet_grid(Species~RhoConfig) +
    ggtitle(paste('Estimate Spatial RE:', do.spatial)) +#, 
    # subtitle=paste('Knots:',temp.knots)) +
    ylab('Proportion') +
    theme(axis.text.x=element_text(angle=45, hjust=1))
  # g2
  
  #Bring the figures together
  g12 <- plot_grid(g, g2, nrow=2, ncol=1, align='v')
  # g12
  ggsave(paste0(output.dir,'/Rockfish ', temp.knots,'kt.png'), plot=g12, 
         height=heights, width=widths, units='in', dpi=1e3)
}#next t


#Plotting Pollock and Pcod ===========================================================
t <- 1
for(t in 1:n.trial.knots) {
  temp.knots <- trial.knots[t]
  
  #Estimate Spatial RE
  do.spatial <- TRUE
  temp.df <- output.df[which(output.df$Species %in% c('Walleye pollock','Pacific cod')),]
  temp.df <- temp.df[temp.df$Est_Spatial_RE==do.spatial | is.na(temp.df$Est_Spatial_RE),]
  temp.df$Species <- gsub(" ", "\n", temp.df$Species)
  
  g <- ggplot(temp.df, aes(x=Year, y=value, fill=Region)) +
    theme_dark()+
    theme(legend.position='top') +
    scale_fill_colorblind() +
    geom_area(position='stack', alpha=alpha) +
    facet_grid(Species~RhoConfig) +
    ggtitle(paste('Estimate Spatial RE:', do.spatial)) +#, 
    # subtitle=paste('Knots:',temp.knots)) +
    ylab('Proportion') +
    theme(axis.text.x=element_text(angle=45, hjust=1))
  # g
  ggsave(paste0(output.dir,'/Pollock_Cod ', temp.knots,'kt-STRE.png'), plot=g, 
         height=heights/2, width=widths, units='in', dpi=1e3)
  
  #Don't Estimate Spatial RE
  do.spatial <- FALSE
  temp.df <- output.df[which(output.df$Species %in% c('Walleye pollock','Pacific cod')),]
  temp.df <- temp.df[temp.df$Est_Spatial_RE==do.spatial | is.na(temp.df$Est_Spatial_RE),]
  temp.df$Species <- gsub(" ", "\n", temp.df$Species)
  
  g2 <- ggplot(temp.df, aes(x=Year, y=value, fill=Region)) +
    theme_dark()+
    theme(legend.position='top') +
    scale_fill_colorblind() +
    geom_area(position='stack', alpha=alpha) +
    facet_grid(Species~RhoConfig) +
    ggtitle(paste('Estimate Spatial RE:', do.spatial)) +#, 
    # subtitle=paste('Knots:',temp.knots)) +
    ylab('Proportion') +
    theme(axis.text.x=element_text(angle=45, hjust=1))
  # g2
  
  #Bring the figures together
  g12 <- plot_grid(g, g2, nrow=2, ncol=1, align='v')
  # g12
  ggsave(paste0(output.dir,'/Pollock_Cod ', temp.knots,'kt.png'), plot=g12, 
         height=heights, width=widths, units='in', dpi=1e3)
}#next t


#Plotting Other Species ===========================================================
t <- 1
for(t in 1:n.trial.knots) {
  temp.knots <- trial.knots[t]
  
  #Estimate Spatial RE
  do.spatial <- TRUE
  temp.df <- output.df[-which(output.df$Species %in% c('Walleye pollock','Pacific cod',rockfish)),]
  temp.df <- temp.df[temp.df$Est_Spatial_RE==do.spatial | is.na(temp.df$Est_Spatial_RE),]
  temp.df$Species <- gsub(" ", "\n", temp.df$Species)
  
  g <- ggplot(temp.df, aes(x=Year, y=value, fill=Region)) +
    theme_dark()+
    theme(legend.position='top') +
    scale_fill_colorblind() +
    geom_area(position='stack', alpha=alpha) +
    facet_grid(Species~RhoConfig) +
    ggtitle(paste('Estimate Spatial RE:', do.spatial)) +#, 
    # subtitle=paste('Knots:',temp.knots)) +
    ylab('Proportion') +
    theme(axis.text.x=element_text(angle=45, hjust=1))
  # g
  ggsave(paste0(output.dir,'/Others ', temp.knots,'kt-STRE.png'), plot=g, 
         height=heights/2, width=widths, units='in', dpi=1e3)
  
  #Don't Estimate Spatial RE
  do.spatial <- FALSE
  temp.df <- output.df[-which(output.df$Species %in% c('Walleye pollock','Pacific cod',rockfish)),]
  temp.df <- temp.df[temp.df$Est_Spatial_RE==do.spatial | is.na(temp.df$Est_Spatial_RE),]
  temp.df$Species <- gsub(" ", "\n", temp.df$Species)
  
  g2 <- ggplot(temp.df, aes(x=Year, y=value, fill=Region)) +
    theme_dark()+
    theme(legend.position='top') +
    scale_fill_colorblind() +
    geom_area(position='stack', alpha=alpha) +
    facet_grid(Species~RhoConfig) +
    ggtitle(paste('Estimate Spatial RE:', do.spatial)) +#, 
    # subtitle=paste('Knots:',temp.knots)) +
    ylab('Proportion') +
    theme(axis.text.x=element_text(angle=45, hjust=1))
  # g2
  
  #Bring the figures together
  g12 <- plot_grid(g, g2, nrow=2, ncol=1, align='v')
  # g12
  ggsave(paste0(output.dir,'/Others ', temp.knots,'kt.png'), plot=g12, 
         height=heights, width=widths, units='in', dpi=1e3)
}#next t

#PLOT: Spatial Distribution Examples ==============================================
n.species
species

loc.rockfish <- which(species.list$name %in% rockfish)

#Extract the model fit

names(vast_est.output[[1]][[3]])

#Harlequin Rockfish
Opt <- vast_est.output[[1]][[loc.rockfish[3]]]


MapDetails_List = SpatialDeltaGLMM::MapDetails_Fn( "Region"="Gulf_of_Alaska",
                                                   "NN_Extrap"=Spatial_List$PolygonList$NN_Extrap,
                                                   "Extrapolation_List"=Extrapolation_List )

SpatialDeltaGLMM::PlotResultsOnMap_Fn(plot_set = c(3),
                                      MappingDetails = MapDetails_List[["MappingDetails"]],
                                      Report = Report, Sdreport = Opt$SD, PlotDF = MapDetails_List[["PlotDF"]],
                                      MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
                                      Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
                                      FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
                                      Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
                                      Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
                                      mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8,
                                      plot_legend_fig = TRUE)




#===========================================================
#Printing GOA POP Table
# write.csv(vast_est.output[[7]][[which(species.list$name %in% 'Pacific ocean perch')]]$vast_est, file=paste0(output.dir,'/POP AR+RW spat_TRUE.csv'))

#===========================================================
#Plotting All Species separately

# spec.surv <- unique(output.df$Name)
# n.spec.surv <- length(spec.surv)
# 
# s <- 1
# for(s in 1:n.spec.surv) {
#   temp.name <- spec.surv[s]
#   
#   temp.df <- output.df[output.df$Name==temp.name,]
#   
#   t <- 1
#   for(t in 1:n.trial.knots) {
#     temp.knots <- trial.knots[t]
#     g <- ggplot(temp.df[temp.df$Knots==temp.knots | temp.df$Knots==FALSE,],
#                   aes(x=Year, y=value, fill=Region)) +
#            theme_dark() +
#            theme(legend.position='top') +
#            scale_fill_colorblind() +
#            ylab('Proportion') +
#            geom_area(position='stack', alpha=alpha) +
#            facet_grid(Est_Spatial_RE~RhoConfig) +
#            ggtitle(temp.name, subtitle=paste('Knots:',temp.knots)) 
#     g
#   }#next t
# }#next s



#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Compare Apportionment Between VAST and
#                                                              RE Model for GOA - Running in Series to Permit Bias Corr
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 5.11.17
#
#Purpose: Evaluate apportionment (Eastern, Central, Western) for Gulf of Alaska RACE
#           Bottom Trawl indices of abundance. 
#             1) Evaluate across three alternative knot numbers
#             2) Several specifications for autocorrelation in EC and PCR intercept
#
#
#==================================================================================================
#NOTES:
#  a) Spiny Dogfish removed from evaluation because design based index is 0 for Western GOA in 2013.
#  b) Big Skate Causes Problems... not diagnosed.
#  c) Non-convergence with intercepts estimted as IaY i.e. ==1, independent of spatio-temporal RE specs.
#==================================================================================================
#TIMING:
# [1] "### START: Tue Jul 18 15:32:25 2017"
# [1] "### END: Wed Jul 19 01:35:23 2017"
##==================================================================================================
# require(SpatialDeltaGLMM)
require(VAST)
require(TMB)
require(TMBhelper)
require(parallel)
require(snowfall)
require(ggplot2)
require(R2admb)
require(reshape2)
require(gridExtra)
require(ggthemes)
require(cowplot)
require(RANN)
require(PBSmodelling)
require(PBSmapping)
require(XML)


source("R/calc-design-based-index.r")
source("R/create-VAST-input.r")
source("R/create-Data-Geostat.r")
source("R/load-RACE-data.r")

source("R/cleanup-VAST-file.r")
source("R/get-VAST-index.r")

source("R/run-RE-model.r")


home.dir <- getwd()
#Create working directory
working.dir <- paste0(home.dir, "/examples/Test_Apportion")

alpha <- 1

#Determine species list
species.list <- read.csv("data/eval_species_list.csv", stringsAsFactors=FALSE)

#Limit species included
species.list <- species.list[species.list$include=='Y',]
#Limit to GOA
species.list <- species.list[species.list$survey=='GOA',]
species.list <- species.list[species.list$name!='Spiny dogfish' & species.list$name!='Big skate',]
n.species <- nrow(species.list)

#Create
species.series <- c(1:n.species)

#=======================================================================
##### CONTROL SECTION #####
#Specify process error terms for RE model
n_PE <- 3
PE_vec <- c(1:3)
#####

#Number of cores to use
n.cores <- detectCores()-1

#Boolean for running estimation models
do.estim <- TRUE

#Trial Knot Numbers
trial.knots <- c(100)
n.trial.knots <- length(trial.knots)

#Trial AUTOREGRESSIVE specifications
#Note starts at 0
# rho.int.types <- c('Fixed_Effect','Independent_Among_Years','Random_Walk','Constant_Intercept','Autoregressive')
rho.int.types <- c('FE','IaY','RW','CI','AR')
# rho.stRE.types <- c('Independent_Among_Years',NA,'Random_Walk',NA,'Autoregressive')
rho.stRE.types <- c('IaY',NA,'RW',NA,'AR')

#Read in Autoregressive Input
# trial.rho <- t(read.csv('Data/Test-Autoregressive-Input.csv', header=TRUE, stringsAsFactors=FALSE)[,-c(1:2)])
# trial.rho <- matrix(c(1,1,0,0,
#                       2,2,0,0,
#                       4,4,0,0,
#                       1,1,4,4,
#                       1,1,2,2,
#                       2,2,4,4),ncol=4, nrow=6, byrow=TRUE)

# trial.rho <- matrix(c(2,2,0,0,
#                       4,4,0,0,
#                       2,2,4,4,
#                       4,4,2,2),ncol=4, nrow=4, byrow=TRUE)

trial.rho <- matrix(c(2,2,0,0,
                      4,4,0,0,
                      2,2,2,2,
                      4,4,4,4),ncol=4, nrow=4, byrow=TRUE)

n.trial.rho <- nrow(trial.rho)

flag.spatial <- c(TRUE)#,FALSE)
n.flag.spatial <- length(flag.spatial)
# #Intercept
# rho.inter.ep <- c(0) #Encounter Probability Component 
# rho.inter.pcr <- c(0) #Positive Catch Rate Component
# #Spatio-temporal RE
# rho.stRE.ep #Encounter Probability Component 
# rho.stRE.pcr #Positive Catch Rate Component

#Boolean for bias correction
bias.correct <- FALSE
#=======================================================================
##### Run VAST model  #####
Version <- "VAST_v4_0_0"
lat_lon.def <- "start"

#SPATIAL SETTINGS
Method = c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km = 25
# n_x = c(100, 250, 500, 1000, 2000)[2] # Number of stations
Kmeans_Config = list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )

#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("Western","Central",'Eastern'),
                            west_border = c(-Inf, -159, -147),
                            east_border = c(-159, -147, Inf))

#MODEL SETTINGS
# FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
# RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

ObsModel = c(1, 0) #Lognormal

#SPECIFY OUTPUTS
Options = c(SD_site_density = 0, SD_site_logdensity = 0,
            Calculate_Range = 1, Calculate_evenness = 0, Calculate_effective_area = 1,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)

#Output Directory Name
output.dir <- paste0(working.dir,"/output_bias.correct_",bias.correct)
dir.create(output.dir)


#=======================================================================
##### WRAPPER FUNCTION FOR RUNNING IN PARALLEL #####

# s <- 1 #Spiny dogfish
# for(s in 1:n.species) {
wrapper_fxn <- function(s, n_x, RhoConfig, n_PE, PE_vec, FieldConfig, ...) {
  # require(SpatialDeltaGLMM)
  require(TMB)
  require(TMBhelper)
  require(VAST)
  
  #Define file for analyses
  DateFile <- paste0(trial.dir,"/",species.list$survey[s],"_",species.list$name[s],"/")
  
  dir.create(DateFile)
  
  #Define species.codes
  species.codes <- species.list$species.code[s]
  survey <- species.list$survey[s]
  
  #=======================================================================
  ##### READ IN DATA AND BUILD VAST INPUT #####
  #  NOTE: this will create the DateFile
  VAST_input <- create_VAST_input(species.codes=species.codes, combineSpecies=FALSE,
                                  lat_lon.def=lat_lon.def, save.Record=FALSE,
                                  Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                  Kmeans_Config=Kmeans_Config,
                                  strata.limits=strata.limits, survey=survey,
                                  DateFile=DateFile,
                                  FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                  OverdispersionConfig=OverdispersionConfig,
                                  ObsModel=ObsModel, Options=Options, Version=Version)
  
  # str(VAST_input)
  #Unpack
  TmbData <- VAST_input$TmbData
  Data_Geostat <- VAST_input$Data_Geostat
  Spatial_List <- VAST_input$Spatial_List
  Extrapolation_List <- VAST_input$Extrapolation_List
  
  # print(TmbData)
  
  print('Before create TmbList')
  #=======================================================================
  ##### RUN VAST #####
  #Build TMB Object
  #  Compilation may take some time - ERROR HERE - Fails when inside wrapper function... not sure why.
  TmbList <- VAST::Build_TMB_Fn(TmbData = TmbData, RunDir = DateFile,
                                Version = Version, 
                                Q_Config=FALSE, CovConfig=FALSE,
                                RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                                Method = Method)
  print('After create TmbList')
  
  Obj <- TmbList[["Obj"]]
  
  if(bias.correct==FALSE) {
    Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
                               upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
                               bias.correct = bias.correct, newtonsteps=1)
    # summary(Opt)
  }else {
    #NEW: Only Bias Correct Index
    Opt <- TMBhelper::Optimize(obj=Obj, lower=TmbList[["Lower"]], 
                               upper=TmbList[["Upper"]], getsd=TRUE, savedir=DateFile, 
                               bias.correct=bias.correct, newtonsteps=1,
                               bias.correct.control=list(sd=TRUE, nsplit=10, split=NULL,
                                                         vars_to_correct="Index_cyl"))
  }
  #Save output
  # Report = Obj$report()
  # Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
  # save(Save, file=paste0(DateFile,"Save.RData"))
  
  #Calculate index values
  # TmbData = TmbData, Sdreport = Opt[["SD"]]
  vast_est <- get_VAST_index(TmbData=TmbData, Sdreport=Opt[["SD"]], bias.correct=bias.correct, Data_Geostat=Data_Geostat)
  #========================================================================
  ##### DIAGNOSTIC AND PREDICTION PLOTS #####
  # plot_VAST_output(Opt, Report, DateFile, survey, TmbData, Data_Geostat, Extrapolation_List, Spatial_List)
  
  #========================================================================
  ##### CLEANUP VAST OUTPUT #####
  # cleanup_VAST_file(DateFile=DateFile, Version=Version) #No longer necessary as we are deleting everything at the end
  
  rm("VAST_input", "TmbData", "Data_Geostat", "Spatial_List", "Extrapolation_List",
     "TmbList", "Obj","Save","Report")#, "Save")#, "Opt", "Report")
  #========================================================================
  setwd(home.dir)
  #========================================================================
  ##### CALL RE MODEL #####
  #Calculate Separate Design-based indices for GOA
  temp.west <- calc_design_based_index(species.codes=species.codes, survey=survey, reg.area="WESTERN GOA")
  temp.cent <- calc_design_based_index(species.codes=species.codes, survey=survey, reg.area="CENTRAL GOA")  
  temp.east <- calc_design_based_index(species.codes=species.codes, survey=survey, reg.area="EASTERN GOA")
  
  #Bring Together (except 2001 when Eastern GOA was not surveyed)
  input.yrs <- sort(unique(temp.west$YEAR))
  #Remove 2001
  input.yrs <- input.yrs[-which(input.yrs==2001)]
  
  #Index values
  input.idx <- data.frame(temp.west$Biomass[temp.west$YEAR %in% input.yrs],
                          temp.cent$Biomass[temp.cent$YEAR %in% input.yrs],
                          temp.east$Biomass[temp.east$YEAR %in% input.yrs])
  names(input.idx) <- c("Western","Centeral","Eastern")
  input.idx <- as.matrix(input.idx)
  
  #Index CV
  input.cv <- data.frame(temp.west$CV[temp.west$YEAR %in% input.yrs],
                         temp.cent$CV[temp.cent$YEAR %in% input.yrs],
                         temp.east$CV[temp.east$YEAR %in% input.yrs])
  
  names(input.cv) <- c("Western","Centeral","Eastern")
  input.cv <- as.matrix(input.cv)
  
  #Copy, compile, call ADMB-RE model
  biomA <- run_RE_model(input.yrs, input.idx, input.cv, DateFile, home.dir, n_PE=n_PE, PE_vec=PE_vec)
  
  #========================================================================
  setwd(home.dir)
  
  ##### RETURN SECTION #####
  out <- NULL
  out$vast_est <- vast_est
  out$Opt <- Opt
  out$biomA <- biomA
  return(out)
} 

#=======================================================================
##### Loop Through Trial Knots  ##### 
vast_est.output <- vector('list', length=(n.trial.knots * n.trial.rho * n.flag.spatial))
vast_knots <- vector(length=(n.trial.knots * n.trial.rho * n.flag.spatial))
vast_rho.int <- vector(length=(n.trial.knots * n.trial.rho * n.flag.spatial))
vast_rho.stRE <- vector(length=(n.trial.knots * n.trial.rho * n.flag.spatial))
vast_do.spatial <-  vector(length=(n.trial.knots * n.trial.rho * n.flag.spatial))

if(do.estim==TRUE) {
  
  
  time.1 <- date()
  
  #Counter for knots by rho
  counter <- 1
  
  t <- 1
  for(t in 1:n.trial.knots) {
    print(paste('## Trial Knot Number',t,'of',n.trial.knots))
    print(paste('# Trial Knots:',trial.knots[t]))
    #Specify trial observation model
    
    #Specify knots
    n_x <- trial.knots[t]
    
    r <- 1
    for(r in 1:n.trial.rho) {
      #Specify intercepts and spatio-temporal variation across time
      RhoConfig <- trial.rho[r,]
      names(RhoConfig) <- c('Beta1','Beta2','Epsilon1','Epsilon2')
      
      f <- 1
      for(f in 1:n.flag.spatial) {
        do.spatial <- flag.spatial[f]
        # print(paste('f',f))
        #Turn ON/OFF spatial components
        if(do.spatial==TRUE) {
          #ON
          FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
        }else {
          #OFF
          FieldConfig = c(Omega1 = 0, Epsilon1 = 1, Omega2 = 0, Epsilon2 = 1)
        }
        vast_do.spatial[counter] <- do.spatial
        
        #Record
        if(RhoConfig[1]==RhoConfig[2]) {#IF intercept specs are the same
          vast_rho.int[counter] <- rho.int.types[RhoConfig[1]+1]
        }else {#IF different
          vast_rho.int[counter] <- paste0(rho.int.types[RhoConfig[1]+1], "-", rho.int.types[RhoConfig[2]+1])
        }
        if(RhoConfig[3]==RhoConfig[4]) {
          vast_rho.stRE[counter] <- rho.stRE.types[RhoConfig[3]+1]
        }else {
          vast_rho.stRE[counter] <- paste0(rho.stRE.types[RhoConfig[3]+1], "-", rho.stRE.types[RhoConfig[4]+1])
        }
        #Update Knot List
        vast_knots[counter] <- n_x
        
        #Setup File
        trial.dir <- paste0(working.dir,"/",n_x,"_bias.corr_",bias.correct)
        dir.create(trial.dir)
        trial.dir <- paste0(trial.dir, "/int_",vast_rho.int[counter], 
                            " stRE_",vast_rho.stRE[counter], 
                            " do.spatial_",vast_do.spatial[counter])
        dir.create(trial.dir)
        
        
        #=======================================================================
        ##### SNOWFALL CODE FOR PARALLEL #####
        # sfInit(parallel=TRUE, cpus=n.cores, type='SOCK')
        # sfExportAll() #Exportas all global variables to cores
        # sfLibrary(TMB)  #Loads a package on all nodes
        # sfLibrary(VAST)
        # output <- sfLapply(species.series, fun=wrapper_fxn, n_x=n_x, RhoConfig=RhoConfig,
        #                    n_PE=n_PE, PE_vec=PE_vec, FieldConfig=FieldConfig)
        # #
        # # temp.out <- wrapper_fxn(s=1, n_x=n_x, RhoConfig=RhoConfig,
        # #                         n_PE=n_PE, PE_vec=PE_vec, FieldConfig=FieldConfig, bias.correct=bias.correct, species.list=species.list)
        # 
        # sfStop()
        
        output <- vector('list', length=n.species)
        s <- 1
        for(s in species.series) {
          # output[[s]]
          output[[s]] <- wrapper_fxn(s=s, n_x=n_x, RhoConfig=RhoConfig,
                                  n_PE=n_PE, PE_vec=PE_vec, FieldConfig=FieldConfig)
          # , bias.correct=bias.correct, species.list=species.list)
          
        }#next s
        
        #Add to list
        vast_est.output[[counter]] <- output
        #Save Object for storage
        saveRDS(output, file=paste0(output.dir,"/VAST_output_",counter,".rds"))
        #Update Counter
        # print(counter)
        counter <- counter + 1
      }#next f
    }#next r
  }#next t
  
  #Dimensions for vast_est.output are 1) Trial knots, 2) Species
  # vast_est.output[[1:n.trial.knots]][[1:n.species]]
  
  #Create output directory
  # dir.create(output.dir)
  # save(vast_est.output, file=paste0(output.dir,"/vast_est.output.RData"))
  #Also save specifications
  vast_specs <- data.frame(vast_knots, vast_rho.int, vast_rho.stRE, vast_do.spatial)
  write.csv(vast_specs, file=paste0(output.dir,"/vast_specs.csv"))
  
  #=======================================================================
  ##### DELETE UNNECESSARY FILE STRUCTURE #####
  #Must reset working directory
  setwd(working.dir)
  t <- 1
  for(t in 1:n.trial.knots) {
    unlink(paste0(working.dir,"/",trial.knots[t],"_bias.corr_",bias.correct), recursive=TRUE)
  }#next t
  
  time.2 <- date()
  
  print(paste('### START:', time.1))
  print(paste('### END:', time.2))
  
}else {
  #Old
  # load(paste0(output.dir,"/vast_est.output.RData"))
  
  #New
  specs <- read.csv(paste0(output.dir,"/vast_specs.csv"), header=TRUE, stringsAsFactors=FALSE)
  n.specs <- nrow(specs)
  
  for(i in 1:n.specs) {
    print(i)
    vast_est.output[[i]] <- readRDS(file=paste0(output.dir, "/VAST_output_",i,".rds"))
    vast_knots[i] <- specs$vast_knots[i]
    vast_rho.int[i] <- specs$vast_rho.int[i]
    vast_rho.stRE[i] <- specs$vast_rho.stRE[i]
    vast_do.spatial[i] <- specs$vast_do.spatial[i]
  }#next i
}

#Plot the output
#  First example is using not fully specified output


# plot.dat <- vast_est.output[[3]] #500 RW
# plot.dat <- vast_est.output[[4]] #500 AR
# plot.dat <- vast_est.output[[1]] #100 RW
#Determine years


#Loop through species
#
re.list <- NULL
vast.list <- NULL
aic.list <- NULL
aic.vect <- vector(length=0)
converge.vect <- vector(length=0)
maxGrad.vect <- vector(length=0)

#Determine true survey years for flag
goa.yrs <- sort(unique(load_RACE_data(species.codes=30420, survey='GOA')$Year))


s <- 1
for(s in 1:n.species) {
  #Species Information
  temp.species <- species.list$name[s]
  temp.survey <- species.list$survey[s]
  temp.name <- paste0(temp.survey,": ",temp.species)
  
  #VAST
  i <- 1
  for(i in 1:n.specs) { 
    
    #Update Convergence and AIC estimates
    aic.vect <- append(aic.vect, vast_est.output[[i]][[s]]$Opt$AIC)
    converge.vect <- append(converge.vect, vast_est.output[[i]][[s]]$Opt$converge)
    maxGrad.vect <- append(maxGrad.vect, max(abs(vast_est.output[[i]][[s]]$Opt$diagnostics$final_gradient)))
    
    yrs <- sort(unique(vast_est.output[[i]][[s]]$vast_est$Year))
    n.yrs <- length(yrs)
    
    survey.year <- vast_est.output[[i]][[s]]$vast_est$Year %in% goa.yrs
    
    indices <- c('Western','Central','Eastern')
    n.indices <- length(indices)
    
    #VAST
    dat.vast <- vast_est.output[[i]][[s]]$vast_est
    #Convert to a matrix
    temp.vast <- matrix(nrow=n.yrs, ncol=n.indices, dimnames=list(yrs, indices))
    j <- 1
    for(j in 1:n.indices) {
      temp.vast[,j] <- dat.vast$Estimate_metric_tons[dat.vast$Fleet==j]
    }
    prop.vast <- temp.vast
    y <- 1
    for(y in 1:n.yrs) {
      prop.vast[y,] <- prop.vast[y,]/sum(temp.vast[y,])
    }
    
    temp.RhoConfig <- paste(vast_rho.int[i], "+",vast_rho.stRE[i])
    
    #Create the grand list
    temp.vast <- melt(prop.vast)
    model <- 'VAST'
    temp.vast <- cbind(temp.vast, model, temp.survey, temp.species, temp.name, vast_knots[i], 
                       vast_rho.int[i], vast_rho.stRE[i], temp.RhoConfig, survey.year,
                       vast_do.spatial[i])
    
    #Skeleton in AIC/convergence data frame
    temp.aic <- cbind(temp.survey, temp.species, temp.name, 'VAST', vast_knots[i], 
                      vast_rho.int[i], vast_rho.stRE[i], temp.RhoConfig,
                      vast_do.spatial[i])
    
    #Combine to larger lists
    #VAST list
    vast.list <- rbind(vast.list, temp.vast)
    #AIC
    aic.list <- rbind(aic.list, temp.aic) 
    
  }#next i
  
  #ADMB-RE
  #Random Effects Model
  temp.re <- vast_est.output[[i]][[s]]$biomA
  prop.re <- temp.re
  y <- 1
  for(y in 1:n.yrs) {
    prop.re[y,] <- prop.re[y,]/sum(temp.re[y,])
  }
  dimnames(prop.re) <- list(yrs, indices)
  
  #Create larger list
  temp.re <- melt(prop.re)
  model <- 'ADMB-RE'
  temp.re <- cbind(temp.re, model, temp.survey, temp.species, temp.name, FALSE, FALSE, FALSE, "ADMB-RE", 
                   survey.year, NA)
  #Randome-effects list
  re.list <- rbind(re.list, temp.re)
  
}#next s

#Add names
re.df <- data.frame(re.list)
names(re.df) <- c('Year','Region','value','Model','Survey','Species', 'Name','Knots',
                  'Rho_Intercept','Rho_stRE','RhoConfig','SurveyYear','Est_Spatial_RE')

vast.df <- data.frame(vast.list)
names(vast.df) <- c('Year','Region','value','Model','Survey','Species', 'Name','Knots',
                    'Rho_Intercept','Rho_stRE','RhoConfig','SurveyYear','Est_Spatial_RE')

#Bind Together
output.df <- rbind(re.df, vast.df)

aic.df <- data.frame(aic.list, aic.vect, converge.vect, maxGrad.vect)

names(aic.df) <- c('Survey','Species','Name','Model','Knots',
                   'Rho_Intercept','Rho_stRE','RhoConfig','Est_Spatial_RE',
                   'AIC','Converge','maxGradient')
aic.df$Converge <- as.factor(aic.df$Converge)

#===========================================================
#Remove the FE + IaY Model
#Unobserved estimates are unreliable
output.df <- output.df[output.df$RhoConfig!='FE + IaY',]
aic.df <- aic.df[aic.df$RhoConfig!='FE + IaY',]


#===========================================================
#Plot the Max Gradients
g <- ggplot(aic.df, aes(x=RhoConfig, y=maxGradient, color=Knots)) +
  theme_gray() +
  geom_point(alpha=0.5) +
  facet_grid(Est_Spatial_RE~Species, scales='free') +
  theme(axis.text.x=element_text(angle=45, hjust=1))
g
ggsave(paste0(output.dir,'/Convergence.png'), plot=g, height=8, width=8, units='in', dpi=1e3)

g <- ggplot(aic.df, aes(x=RhoConfig, y=Converge, color=Knots)) +
  theme_gray() +
  geom_point(alpha=0.5) +
  facet_grid(Est_Spatial_RE~Species, scales='free') +
  theme(axis.text.x=element_text(angle=45, hjust=1))
g

#===========================================================
#Plotting Rockfish

heights <- 9
widths <- 7

rockfish <- c('Pacific ocean perch','Northern rockfish','Harlequin rockfish')

t <- 1
for(t in 1:n.trial.knots) {
  temp.knots <- trial.knots[t]
  
  #Estimate Spatial RE
  do.spatial <- TRUE
  temp.df <- output.df[which(output.df$Species %in% rockfish),]
  temp.df <- temp.df[temp.df$Est_Spatial_RE==do.spatial | is.na(temp.df$Est_Spatial_RE),]
  temp.df$Species <- gsub(" ", "\n", temp.df$Species)
  
  g <- ggplot(temp.df, aes(x=Year, y=value, fill=Region)) +
    theme_dark()+
    theme(legend.position='top') +
    scale_fill_colorblind() +
    geom_area(position='stack', alpha=alpha) +
    facet_grid(Species~RhoConfig) +
    ggtitle(paste('Estimate Spatial RE:', do.spatial)) +#, 
    # subtitle=paste('Knots:',temp.knots)) +
    ylab('Proportion') +
    theme(axis.text.x=element_text(angle=45, hjust=1))
  # g
  
  #Don't Estimate Spatial RE
  do.spatial <- FALSE
  temp.df <- output.df[which(output.df$Species %in% rockfish),]
  temp.df <- temp.df[temp.df$Est_Spatial_RE==do.spatial | is.na(temp.df$Est_Spatial_RE),]
  temp.df$Species <- gsub(" ", "\n", temp.df$Species)
  
  g2 <- ggplot(temp.df, aes(x=Year, y=value, fill=Region)) +
    theme_dark()+
    theme(legend.position='top') +
    scale_fill_colorblind() +
    geom_area(position='stack', alpha=alpha) +
    facet_grid(Species~RhoConfig) +
    ggtitle(paste('Estimate Spatial RE:', do.spatial)) +#, 
    # subtitle=paste('Knots:',temp.knots)) +
    ylab('Proportion') +
    theme(axis.text.x=element_text(angle=45, hjust=1))
  # g2
  
  #Bring the figures together
  g12 <- plot_grid(g, g2, nrow=2, ncol=1, align='v')
  # g12
  ggsave(paste0(output.dir,'/Rockfish ', temp.knots,'kt.png'), plot=g12, 
         height=heights, width=widths, units='in', dpi=1e3)
}#next t

#===========================================================
#Plotting Pollock and Pcod
t <- 1
for(t in 1:n.trial.knots) {
  temp.knots <- trial.knots[t]
  
  #Estimate Spatial RE
  do.spatial <- TRUE
  temp.df <- output.df[which(output.df$Species %in% c('Walleye pollock','Pacific cod')),]
  temp.df <- temp.df[temp.df$Est_Spatial_RE==do.spatial | is.na(temp.df$Est_Spatial_RE),]
  temp.df$Species <- gsub(" ", "\n", temp.df$Species)
  
  g <- ggplot(temp.df, aes(x=Year, y=value, fill=Region)) +
    theme_dark()+
    theme(legend.position='top') +
    scale_fill_colorblind() +
    geom_area(position='stack', alpha=alpha) +
    facet_grid(Species~RhoConfig) +
    ggtitle(paste('Estimate Spatial RE:', do.spatial)) +#, 
    # subtitle=paste('Knots:',temp.knots)) +
    ylab('Proportion') +
    theme(axis.text.x=element_text(angle=45, hjust=1))
  # g
  
  #Don't Estimate Spatial RE
  do.spatial <- FALSE
  temp.df <- output.df[which(output.df$Species %in% c('Walleye pollock','Pacific cod')),]
  temp.df <- temp.df[temp.df$Est_Spatial_RE==do.spatial | is.na(temp.df$Est_Spatial_RE),]
  temp.df$Species <- gsub(" ", "\n", temp.df$Species)
  
  g2 <- ggplot(temp.df, aes(x=Year, y=value, fill=Region)) +
    theme_dark()+
    theme(legend.position='top') +
    scale_fill_colorblind() +
    geom_area(position='stack', alpha=alpha) +
    facet_grid(Species~RhoConfig) +
    ggtitle(paste('Estimate Spatial RE:', do.spatial)) +#, 
    # subtitle=paste('Knots:',temp.knots)) +
    ylab('Proportion') +
    theme(axis.text.x=element_text(angle=45, hjust=1))
  # g2
  
  #Bring the figures together
  g12 <- plot_grid(g, g2, nrow=2, ncol=1, align='v')
  # g12
  ggsave(paste0(output.dir,'/Pollock_Cod ', temp.knots,'kt.png'), plot=g12, 
         height=heights, width=widths, units='in', dpi=1e3)
}#next t


#===========================================================
#Plotting Other Species
t <- 1
for(t in 1:n.trial.knots) {
  temp.knots <- trial.knots[t]
  
  #Estimate Spatial RE
  do.spatial <- TRUE
  temp.df <- output.df[-which(output.df$Species %in% c('Walleye pollock','Pacific cod',rockfish)),]
  temp.df <- temp.df[temp.df$Est_Spatial_RE==do.spatial | is.na(temp.df$Est_Spatial_RE),]
  temp.df$Species <- gsub(" ", "\n", temp.df$Species)
  
  g <- ggplot(temp.df, aes(x=Year, y=value, fill=Region)) +
    theme_dark()+
    theme(legend.position='top') +
    scale_fill_colorblind() +
    geom_area(position='stack', alpha=alpha) +
    facet_grid(Species~RhoConfig) +
    ggtitle(paste('Estimate Spatial RE:', do.spatial)) +#, 
    # subtitle=paste('Knots:',temp.knots)) +
    ylab('Proportion') +
    theme(axis.text.x=element_text(angle=45, hjust=1))
  # g
  
  #Don't Estimate Spatial RE
  do.spatial <- FALSE
  temp.df <- output.df[-which(output.df$Species %in% c('Walleye pollock','Pacific cod',rockfish)),]
  temp.df <- temp.df[temp.df$Est_Spatial_RE==do.spatial | is.na(temp.df$Est_Spatial_RE),]
  temp.df$Species <- gsub(" ", "\n", temp.df$Species)
  
  g2 <- ggplot(temp.df, aes(x=Year, y=value, fill=Region)) +
    theme_dark()+
    theme(legend.position='top') +
    scale_fill_colorblind() +
    geom_area(position='stack', alpha=alpha) +
    facet_grid(Species~RhoConfig) +
    ggtitle(paste('Estimate Spatial RE:', do.spatial)) +#, 
    # subtitle=paste('Knots:',temp.knots)) +
    ylab('Proportion') +
    theme(axis.text.x=element_text(angle=45, hjust=1))
  # g2
  
  #Bring the figures together
  g12 <- plot_grid(g, g2, nrow=2, ncol=1, align='v')
  # g12
  ggsave(paste0(output.dir,'/Others ', temp.knots,'kt.png'), plot=g12, 
         height=heights, width=widths, units='in', dpi=1e3)
}#next t

#===========================================================
#Printing GOA POP Table
# write.csv(vast_est.output[[7]][[which(species.list$name %in% 'Pacific ocean perch')]]$vast_est, file=paste0(output.dir,'/POP AR+RW spat_TRUE.csv'))

#===========================================================
#Plotting All Species separately

# spec.surv <- unique(output.df$Name)
# n.spec.surv <- length(spec.surv)
# 
# s <- 1
# for(s in 1:n.spec.surv) {
#   temp.name <- spec.surv[s]
#   
#   temp.df <- output.df[output.df$Name==temp.name,]
#   
#   t <- 1
#   for(t in 1:n.trial.knots) {
#     temp.knots <- trial.knots[t]
#     g <- ggplot(temp.df[temp.df$Knots==temp.knots | temp.df$Knots==FALSE,],
#                   aes(x=Year, y=value, fill=Region)) +
#            theme_dark() +
#            theme(legend.position='top') +
#            scale_fill_colorblind() +
#            ylab('Proportion') +
#            geom_area(position='stack', alpha=alpha) +
#            facet_grid(Est_Spatial_RE~RhoConfig) +
#            ggtitle(temp.name, subtitle=paste('Knots:',temp.knots)) 
#     g
#   }#next t
# }#next s



#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Compare Apportionment Between VAST and
#                                                              RE Model for GOA
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 5.11.17
#
#Purpose: Evaluate apportionment (Eastern, Central, Western) for Gulf of Alaska RACE
#           Bottom Trawl indices of abundance. 
#             1) Evaluate across three alternative knot numbers
#             2) Several specifications for autocorrelation in EC and PCR intercept
#
#
#==================================================================================================
#NOTES:
#  a) Spiny Dogfish removed from evaluation because design based index is 0 for Western GOA in 2013
#  b) Non-convergence with intercepts estimted as IaY i.e. ==1, independent of spatio-temporal RE specs.
#==================================================================================================
#TIMING:
# [1] "### START: Tue Jul 18 15:32:25 2017"
# [1] "### END: Wed Jul 19 01:35:23 2017"
##==================================================================================================
require(VAST)
require(TMB)
require(TMBhelper)
require(parallel)
require(snowfall)
require(ggplot2)
require(R2admb)
require(reshape2)
require(gridExtra)
require(ggthemes)
require(cowplot)
require(RANN)
require(PBSmodelling)
require(PBSmapping)
require(XML)
require(FishStatsUtils)


source("R/calc-design-based-index.r")
source("R/create-VAST-input.r")
source("R/create-Data-Geostat.r")
source("R/load-RACE-data.r")

source("R/cleanup-VAST-file.r")
source("R/get-VAST-index.r")

source("R/run-RE-model.r")


home.dir <- getwd()
#Create working directory
working.dir <- paste0(home.dir, "/examples/Test_Apportion")

alpha <- 1

#Determine species list
species.list <- read.csv("data/eval_species_list.csv", stringsAsFactors=FALSE)

#Limit species included
species.list <- species.list[species.list$include=='Y',]
#Limit to GOA
species.list <- species.list[species.list$survey=='GOA',]
# species.list <- species.list[species.list$name!='Spiny dogfish' & species.list$name!='Big skate',]
species.list <- species.list[species.list$name!='Spiny dogfish',]

n.species <- nrow(species.list)

#Create
species.series <- c(1:n.species)

#=======================================================================
##### CONTROL SECTION #####
#Specify process error terms for RE model
# n_PE <- 3
# PE_vec <- c(1:3)

n_PE <- 1
PE_vec <- rep(1,3)
#####

#Number of cores to use
n.cores <- detectCores()-1

#Boolean for running estimation models
do.estim <- TRUE

#Trial Knot Numbers
trial.knots <- c(100)
n.trial.knots <- length(trial.knots)

#Trial AUTOREGRESSIVE specifications
#Note starts at 0
# rho.int.types <- c('Fixed_Effect','Independent_Among_Years','Random_Walk','Constant_Intercept','Autoregressive')
rho.int.types <- c('FE','IaY','RW','CI','AR')
# # rho.stRE.types <- c('Independent_Among_Years',NA,'Random_Walk',NA,'Autoregressive')
rho.stRE.types <- c('IaY',NA,'RW',NA,'AR')

# rho.int.types <- c('FE','I')
# rho.stRE.types <- rho.int.types

#Read in Autoregressive Input
# trial.rho <- t(read.csv('Data/Test-Autoregressive-Input.csv', header=TRUE, stringsAsFactors=FALSE)[,-c(1:2)])
# trial.rho <- matrix(c(1,1,0,0,
#                       2,2,0,0,
#                       4,4,0,0,
#                       1,1,4,4,
#                       1,1,2,2,
#                       2,2,4,4),ncol=4, nrow=6, byrow=TRUE)

# trial.rho <- matrix(c(2,2,0,0,
#                       4,4,0,0,
#                       2,2,4,4,
#                       4,4,2,2),ncol=4, nrow=4, byrow=TRUE)

trial.rho <- matrix(c(0,0,2,2,
                      2,2,2,2,
                      4,4,2,2
                      ),ncol=4, nrow=3, byrow=TRUE)

n.trial.rho <- nrow(trial.rho)


flag.spatial <- c(TRUE)#,FALSE)
n.flag.spatial <- length(flag.spatial)
# #Intercept
# rho.inter.ep <- c(0) #Encounter Probability Component 
# rho.inter.pcr <- c(0) #Positive Catch Rate Component
# #Spatio-temporal RE
# rho.stRE.ep #Encounter Probability Component 
# rho.stRE.pcr #Positive Catch Rate Component

#Boolean for bias correction
bias.correct <- FALSE
#=======================================================================
##### Run VAST model  #####
Version <- "VAST_v4_0_0"
lat_lon.def <- "start"

#SPATIAL SETTINGS
Method = c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km = 25
# n_x = c(100, 250, 500, 1000, 2000)[2] # Number of stations
Kmeans_Config = list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("Western","Central",'Eastern'),
                            west_border = c(-Inf, -159, -147),
                            east_border = c(-159, -147, Inf))


#MODEL SETTINGS
# FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
# RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

ObsModel = c(1, 1) #Lognormal - Poisson-link Delta
# ObsModel = c(1, 0) #Lognormal - Delta

#SPECIFY OUTPUTS
Options = c(SD_site_density = 0, SD_site_logdensity = 0,
            Calculate_Range = 0, Calculate_evenness = 0, Calculate_effective_area = 0,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)

#Output Directory Name
output.dir <- paste0(working.dir,"/output_bias.correct_",bias.correct, " n_PE-",n_PE)
dir.create(output.dir)


#=======================================================================
##### WRAPPER FUNCTION FOR RUNNING IN PARALLEL #####

s <- 1 #Spiny dogfish
# for(s in 1:n.species) {
wrapper_fxn <- function(s, n_x, RhoConfig, n_PE, PE_vec, FieldConfig) {
  require(VAST)
  require(TMB)
  require(TMBhelper)
  
  #Define file for analyses
  DateFile <- paste0(trial.dir,"/",species.list$survey[s],"_",species.list$name[s],"/")
  
  dir.create(DateFile)
  
  #Define species.codes
  species.codes <- species.list$species.code[s]
  survey <- species.list$survey[s]
  
  #=======================================================================
  ##### READ IN DATA AND BUILD VAST INPUT #####
  #  NOTE: this will create the DateFile
  VAST_input <- create_VAST_input(species.codes=species.codes, combineSpecies=FALSE,
                                  lat_lon.def=lat_lon.def, save.Record=FALSE,
                                  Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                  Kmeans_Config=Kmeans_Config,
                                  strata.limits=strata.limits, survey=survey,
                                  DateFile=DateFile,
                                  FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                  OverdispersionConfig=OverdispersionConfig,
                                  ObsModel=ObsModel, Options=Options, Version=Version)
  
  
  #Unpack
  TmbData <- VAST_input$TmbData
  Data_Geostat <- VAST_input$Data_Geostat
  Spatial_List <- VAST_input$Spatial_List
  Extrapolation_List <- VAST_input$Extrapolation_List
  
  
  #=======================================================================
  ##### RUN VAST #####
  #Build TMB Object
  #  Compilation may take some time
  TmbList <- VAST::Build_TMB_Fn(TmbData = TmbData, RunDir = DateFile,
                                Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                                Method = Method,
                                Q_Config=FALSE, CovConfig=FALSE)
  Obj <- TmbList[["Obj"]]
  
  if(bias.correct==FALSE) {
    Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
                               upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
                               bias.correct = bias.correct, newtonsteps=1)
    # summary(Opt)
  }else {
    # #NEW: Only Bias Correct Index
    # Opt <- TMBhelper::Optimize(obj=Obj, lower=TmbList[["Lower"]], 
    #                            upper=TmbList[["Upper"]], getsd=TRUE, savedir=DateFile, 
    #                            bias.correct=bias.correct, newtonsteps=1,
    #                            bias.correct.control=list(sd=TRUE, nsplit=10, split=NULL,
    #                                                      vars_to_correct="Index_cyl"))
    Opt <- TMBhelper::Optimize(obj=Obj, lower=TmbList[["Lower"]], 
                               upper=TmbList[["Upper"]], getsd=TRUE, savedir=DateFile, 
                               bias.correct=bias.correct, newtonsteps=1)
  }
  #Save output
  # Report = Obj$report()
  # Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
  # save(Save, file=paste0(DateFile,"Save.RData"))
  
  #Calculate index values
  # TmbData = TmbData, Sdreport = Opt[["SD"]]
  vast_est <- get_VAST_index(TmbData=TmbData, Sdreport=Opt[["SD"]], bias.correct=bias.correct, Data_Geostat=Data_Geostat)
  #========================================================================
  ##### DIAGNOSTIC AND PREDICTION PLOTS #####
  # plot_VAST_output(Opt, Report, DateFile, survey, TmbData, Data_Geostat, Extrapolation_List, Spatial_List)
  
  #========================================================================
  ##### CLEANUP VAST OUTPUT #####
  # cleanup_VAST_file(DateFile=DateFile, Version=Version) #No longer necessary as we are deleting everything at the end
  
  rm("VAST_input", "TmbData", "Data_Geostat", "Spatial_List", "Extrapolation_List",
     "TmbList", "Obj","Save","Report")#, "Save")#, "Opt", "Report")
  #========================================================================
  setwd(home.dir)
  #========================================================================
  ##### CALL RE MODEL #####
  #Calculate Separate Design-based indices for GOA
  temp.west <- calc_design_based_index(species.codes=species.codes, survey=survey, reg.area="WESTERN GOA")
  temp.cent <- calc_design_based_index(species.codes=species.codes, survey=survey, reg.area="CENTRAL GOA")  
  temp.east <- calc_design_based_index(species.codes=species.codes, survey=survey, reg.area="EASTERN GOA")
  
  #Bring Together (except 2001 when Eastern GOA was not surveyed)
  input.yrs <- sort(unique(temp.west$YEAR))
  #Remove 2001
  input.yrs <- input.yrs[-which(input.yrs==2001)]
  
  #Index values
  input.idx <- data.frame(temp.west$Biomass[temp.west$YEAR %in% input.yrs],
                          temp.cent$Biomass[temp.cent$YEAR %in% input.yrs],
                          temp.east$Biomass[temp.east$YEAR %in% input.yrs])
  names(input.idx) <- c("Western","Centeral","Eastern")
  input.idx <- as.matrix(input.idx)
  
  #Index CV
  input.cv <- data.frame(temp.west$CV[temp.west$YEAR %in% input.yrs],
                         temp.cent$CV[temp.cent$YEAR %in% input.yrs],
                         temp.east$CV[temp.east$YEAR %in% input.yrs])
  
  names(input.cv) <- c("Western","Centeral","Eastern")
  input.cv <- as.matrix(input.cv)
  
  #Copy, compile, call ADMB-RE model
  biomA <- run_RE_model(input.yrs, input.idx, input.cv, DateFile, home.dir, n_PE=n_PE, PE_vec=PE_vec)
    
  #========================================================================
  setwd(home.dir)
  
  ##### RETURN SECTION #####
  out <- NULL
  out$vast_est <- vast_est
  out$Opt <- Opt
  out$biomA <- biomA
  return(out)
} 



#=======================================================================
##### Loop Through Trial Knots  ##### 
vast_est.output <- vector('list', length=(n.trial.knots * n.trial.rho * n.flag.spatial))
vast_knots <- vector(length=(n.trial.knots * n.trial.rho * n.flag.spatial))
vast_rho.int <- vector(length=(n.trial.knots * n.trial.rho * n.flag.spatial))
vast_rho.stRE <- vector(length=(n.trial.knots * n.trial.rho * n.flag.spatial))
vast_do.spatial <-  vector(length=(n.trial.knots * n.trial.rho * n.flag.spatial))
  
if(do.estim==TRUE) {

  
  time.1 <- date()
  
  #Counter for knots by rho
  counter <- 1
  
  t <- 1
  for(t in 1:n.trial.knots) {
    print(paste('## Trial Knot Number',t,'of',n.trial.knots))
    print(paste('# Trial Knots:',trial.knots[t]))
    #Specify trial observation model
    
    #Specify knots
    n_x <- trial.knots[t]
    
    r <- 1
    for(r in 1:n.trial.rho) {
      #Specify intercepts and spatio-temporal variation across time
      RhoConfig <- trial.rho[r,]
      names(RhoConfig) <- c('Beta1','Beta2','Epsilon1','Epsilon2')
      
      f <- 1
      for(f in 1:n.flag.spatial) {
        do.spatial <- flag.spatial[f]
        # print(paste('f',f))
        #Turn ON/OFF spatial components
        if(do.spatial==TRUE) {
          #ON
          FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
        }else {
          #OFF
          FieldConfig = c(Omega1 = 0, Epsilon1 = 1, Omega2 = 0, Epsilon2 = 1)
        }
        vast_do.spatial[counter] <- do.spatial
        
        #Record
        if(RhoConfig[1]==RhoConfig[2]) {#IF intercept specs are the same
          vast_rho.int[counter] <- rho.int.types[RhoConfig[1]+1]
        }else {#IF different
          vast_rho.int[counter] <- paste0(rho.int.types[RhoConfig[1]+1], "-", rho.int.types[RhoConfig[2]+1])
        }
        if(RhoConfig[3]==RhoConfig[4]) {
          vast_rho.stRE[counter] <- rho.stRE.types[RhoConfig[3]+1]
        }else {
          vast_rho.stRE[counter] <- paste0(rho.stRE.types[RhoConfig[3]+1], "-", rho.stRE.types[RhoConfig[4]+1])
        }
        #Update Knot List
        vast_knots[counter] <- n_x
      
        #Setup File
        trial.dir <- paste0(working.dir,"/",n_x,"_bias.corr_",bias.correct)
        dir.create(trial.dir)
        trial.dir <- paste0(trial.dir, "/int_",vast_rho.int[counter], 
                              " stRE_",vast_rho.stRE[counter], 
                              " do.spatial_",vast_do.spatial[counter])
        dir.create(trial.dir)
      
      
        #=======================================================================
        ##### SNOWFALL CODE FOR PARALLEL #####
        sfInit(parallel=TRUE, cpus=n.cores, type='SOCK')
        sfExportAll() #Exportas all global variables to cores
        sfLibrary(TMB)  #Loads a package on all nodes
        sfLibrary(VAST)
        sfLibrary(TMBhelper)
        
        sfSource("R/calc-design-based-index.r")
        sfSource("R/create-VAST-input.r")
        sfSource("R/create-Data-Geostat.r")
        sfSource("R/load-RACE-data.r")
        sfSource("R/cleanup-VAST-file.r")
        sfSource("R/get-VAST-index.r")
        sfSource("R/run-RE-model.r")
        
        output <- sfLapply(species.series, fun=wrapper_fxn, n_x=n_x, RhoConfig=RhoConfig,
                             n_PE=n_PE, PE_vec=PE_vec, FieldConfig=FieldConfig)
        #
        temp.out <- wrapper_fxn(s=1, n_x=n_x, RhoConfig=RhoConfig,
                                n_PE=n_PE, PE_vec=PE_vec, FieldConfig=FieldConfig)

        sfStop()

        #Add to list
        vast_est.output[[counter]] <- output
        #Save Object for storage
        saveRDS(output, file=paste0(output.dir,"/VAST_output_",counter,".rds"))
        #Update Counter
        # print(counter)
        counter <- counter + 1
      }#next f
    }#next r
  }#next t
  
  #Dimensions for vast_est.output are 1) Trial knots, 2) Species
  # vast_est.output[[1:n.trial.knots]][[1:n.species]]
  
  #Create output directory
  # dir.create(output.dir)
  # save(vast_est.output, file=paste0(output.dir,"/vast_est.output.RData"))
  #Also save specifications
  vast_specs <- data.frame(vast_knots, vast_rho.int, vast_rho.stRE, vast_do.spatial)
  write.csv(vast_specs, file=paste0(output.dir,"/vast_specs.csv"))
  
  #=======================================================================
  ##### DELETE UNNECESSARY FILE STRUCTURE #####
  #Must reset working directory
  setwd(working.dir)
  t <- 1
  for(t in 1:n.trial.knots) {
    unlink(paste0(working.dir,"/",trial.knots[t],"_bias.corr_",bias.correct), recursive=TRUE)
  }#next t

  time.2 <- date()

  print(paste('### START:', time.1))
  print(paste('### END:', time.2))
  
}else {
  #Old
  # load(paste0(output.dir,"/vast_est.output.RData"))
  
  #New
  specs <- read.csv(paste0(output.dir,"/vast_specs.csv"), header=TRUE, stringsAsFactors=FALSE)
  n.specs <- nrow(specs)
  
  for(i in 1:n.specs) {
    print(i)
    vast_est.output[[i]] <- readRDS(file=paste0(output.dir, "/VAST_output_",i,".rds"))
    vast_knots[i] <- specs$vast_knots[i]
    vast_rho.int[i] <- specs$vast_rho.int[i]
    vast_rho.stRE[i] <- specs$vast_rho.stRE[i]
    vast_do.spatial[i] <- specs$vast_do.spatial[i]
  }#next i
}

#Plot the output
#  First example is using not fully specified output


# plot.dat <- vast_est.output[[3]] #500 RW
# plot.dat <- vast_est.output[[4]] #500 AR
# plot.dat <- vast_est.output[[1]] #100 RW
#Determine years


#Loop through species
#
re.list <- NULL
vast.list <- NULL
aic.list <- NULL
aic.vect <- vector(length=0)
converge.vect <- vector(length=0)
maxGrad.vect <- vector(length=0)

#Determine true survey years for flag
goa.yrs <- sort(unique(load_RACE_data(species.codes=30420, survey='GOA')$Year))


s <- 1
for(s in 1:n.species) {
  #Species Information
  temp.species <- species.list$name[s]
  temp.survey <- species.list$survey[s]
  temp.name <- paste0(temp.survey,": ",temp.species)

  #VAST
  i <- 1
  for(i in 1:n.specs) { 
    
    #Update Convergence and AIC estimates
    aic.vect <- append(aic.vect, vast_est.output[[i]][[s]]$Opt$AIC)
    converge.vect <- append(converge.vect, vast_est.output[[i]][[s]]$Opt$converge)
    maxGrad.vect <- append(maxGrad.vect, max(abs(vast_est.output[[i]][[s]]$Opt$diagnostics$final_gradient)))
    
    yrs <- sort(unique(vast_est.output[[i]][[s]]$vast_est$Year))
    n.yrs <- length(yrs)
    
    survey.year <- vast_est.output[[i]][[s]]$vast_est$Year %in% goa.yrs
    
    indices <- c('Western','Central','Eastern')
    n.indices <- length(indices)

    #VAST
    dat.vast <- vast_est.output[[i]][[s]]$vast_est
    #Convert to a matrix
    temp.vast <- matrix(nrow=n.yrs, ncol=n.indices, dimnames=list(yrs, indices))
    j <- 1
    for(j in 1:n.indices) {
      temp.vast[,j] <- dat.vast$Estimate_metric_tons[dat.vast$Fleet==j]
    }
    prop.vast <- temp.vast
    y <- 1
    for(y in 1:n.yrs) {
      prop.vast[y,] <- prop.vast[y,]/sum(temp.vast[y,])
    }
    
    temp.RhoConfig <- paste(vast_rho.int[i], "+",vast_rho.stRE[i])
    
    #Create the grand list
    temp.vast <- melt(prop.vast)
    model <- 'VAST'
    temp.vast <- cbind(temp.vast, model, temp.survey, temp.species, temp.name, vast_knots[i], 
                         vast_rho.int[i], vast_rho.stRE[i], temp.RhoConfig, survey.year,
                         vast_do.spatial[i])

    #Skeleton in AIC/convergence data frame
    temp.aic <- cbind(temp.survey, temp.species, temp.name, 'VAST', vast_knots[i], 
                        vast_rho.int[i], vast_rho.stRE[i], temp.RhoConfig,
                        vast_do.spatial[i])
    
    #Combine to larger lists
    #VAST list
    vast.list <- rbind(vast.list, temp.vast)
    #AIC
    aic.list <- rbind(aic.list, temp.aic) 
    
  }#next i
  
  #ADMB-RE
  #Random Effects Model
  temp.re <- vast_est.output[[i]][[s]]$biomA
  prop.re <- temp.re
  y <- 1
  for(y in 1:n.yrs) {
    prop.re[y,] <- prop.re[y,]/sum(temp.re[y,])
  }
  dimnames(prop.re) <- list(yrs, indices)
  
  #Create larger list
  temp.re <- melt(prop.re)
  model <- 'ADMB-RE'
  temp.re <- cbind(temp.re, model, temp.survey, temp.species, temp.name, FALSE, FALSE, FALSE, "ADMB-RE", 
                     survey.year, NA)
  #Randome-effects list
  re.list <- rbind(re.list, temp.re)
  
}#next s

#Add names
re.df <- data.frame(re.list)
names(re.df) <- c('Year','Region','value','Model','Survey','Species', 'Name','Knots',
                    'Rho_Intercept','Rho_stRE','RhoConfig','SurveyYear','Est_Spatial_RE')

vast.df <- data.frame(vast.list)
names(vast.df) <- c('Year','Region','value','Model','Survey','Species', 'Name','Knots',
                      'Rho_Intercept','Rho_stRE','RhoConfig','SurveyYear','Est_Spatial_RE')

#Bind Together
output.df <- rbind(re.df, vast.df)

aic.df <- data.frame(aic.list, aic.vect, converge.vect, maxGrad.vect)

names(aic.df) <- c('Survey','Species','Name','Model','Knots',
                     'Rho_Intercept','Rho_stRE','RhoConfig','Est_Spatial_RE',
                     'AIC','Converge','maxGradient')
aic.df$Converge <- as.factor(aic.df$Converge)

#===========================================================
#Remove the FE + IaY Model
#Unobserved estimates are unreliable
output.df <- output.df[output.df$RhoConfig!='FE + IaY',]
aic.df <- aic.df[aic.df$RhoConfig!='FE + IaY',]


#===========================================================
#Plot the Max Gradients
g <- ggplot(aic.df, aes(x=RhoConfig, y=maxGradient, color=Knots)) +
       theme_gray() +
       geom_point(alpha=0.5) +
       facet_grid(Est_Spatial_RE~Species, scales='free') +
       theme(axis.text.x=element_text(angle=45, hjust=1))
g
ggsave(paste0(output.dir,'/Convergence.png'), plot=g, height=8, width=8, units='in', dpi=1e3)

g <- ggplot(aic.df, aes(x=RhoConfig, y=Converge, color=Knots)) +
  theme_gray() +
  geom_point(alpha=0.5) +
  facet_grid(Est_Spatial_RE~Species, scales='free') +
  theme(axis.text.x=element_text(angle=45, hjust=1))
g

#===========================================================
#Plotting Rockfish

heights <- 9
widths <- 7

rockfish <- c('Pacific ocean perch','Northern rockfish','Harlequin rockfish')

t <- 1
for(t in 1:n.trial.knots) {
  temp.knots <- trial.knots[t]
  
  #Estimate Spatial RE
  do.spatial <- TRUE
  temp.df <- output.df[which(output.df$Species %in% rockfish),]
  temp.df <- temp.df[temp.df$Est_Spatial_RE==do.spatial | is.na(temp.df$Est_Spatial_RE),]
  temp.df$Species <- gsub(" ", "\n", temp.df$Species)
  
  g <- ggplot(temp.df, aes(x=Year, y=value, fill=Region)) +
         theme_dark()+
         theme(legend.position='top') +
         scale_fill_colorblind() +
         geom_area(position='stack', alpha=alpha) +
         facet_grid(Species~RhoConfig) +
         ggtitle(paste('Estimate Spatial RE:', do.spatial)) +#, 
                 # subtitle=paste('Knots:',temp.knots)) +
         ylab('Proportion') +
         theme(axis.text.x=element_text(angle=45, hjust=1))
  # g
  
  #Don't Estimate Spatial RE
  do.spatial <- FALSE
  temp.df <- output.df[which(output.df$Species %in% rockfish),]
  temp.df <- temp.df[temp.df$Est_Spatial_RE==do.spatial | is.na(temp.df$Est_Spatial_RE),]
  temp.df$Species <- gsub(" ", "\n", temp.df$Species)
  
  g2 <- ggplot(temp.df, aes(x=Year, y=value, fill=Region)) +
          theme_dark()+
          theme(legend.position='top') +
          scale_fill_colorblind() +
          geom_area(position='stack', alpha=alpha) +
          facet_grid(Species~RhoConfig) +
          ggtitle(paste('Estimate Spatial RE:', do.spatial)) +#, 
            # subtitle=paste('Knots:',temp.knots)) +
          ylab('Proportion') +
          theme(axis.text.x=element_text(angle=45, hjust=1))
  # g2
  
  #Bring the figures together
  g12 <- plot_grid(g, g2, nrow=2, ncol=1, align='v')
  # g12
  ggsave(paste0(output.dir,'/Rockfish ', temp.knots,'kt.png'), plot=g12, 
           height=heights, width=widths, units='in', dpi=1e3)
}#next t

#===========================================================
#Plotting Pollock and Pcod
t <- 1
for(t in 1:n.trial.knots) {
  temp.knots <- trial.knots[t]
  
  #Estimate Spatial RE
  do.spatial <- TRUE
  temp.df <- output.df[which(output.df$Species %in% c('Walleye pollock','Pacific cod')),]
  temp.df <- temp.df[temp.df$Est_Spatial_RE==do.spatial | is.na(temp.df$Est_Spatial_RE),]
  temp.df$Species <- gsub(" ", "\n", temp.df$Species)
  
  g <- ggplot(temp.df, aes(x=Year, y=value, fill=Region)) +
    theme_dark()+
    theme(legend.position='top') +
    scale_fill_colorblind() +
    geom_area(position='stack', alpha=alpha) +
    facet_grid(Species~RhoConfig) +
    ggtitle(paste('Estimate Spatial RE:', do.spatial)) +#, 
    # subtitle=paste('Knots:',temp.knots)) +
    ylab('Proportion') +
    theme(axis.text.x=element_text(angle=45, hjust=1))
  # g
  
  #Don't Estimate Spatial RE
  do.spatial <- FALSE
  temp.df <- output.df[which(output.df$Species %in% c('Walleye pollock','Pacific cod')),]
  temp.df <- temp.df[temp.df$Est_Spatial_RE==do.spatial | is.na(temp.df$Est_Spatial_RE),]
  temp.df$Species <- gsub(" ", "\n", temp.df$Species)
  
  g2 <- ggplot(temp.df, aes(x=Year, y=value, fill=Region)) +
    theme_dark()+
    theme(legend.position='top') +
    scale_fill_colorblind() +
    geom_area(position='stack', alpha=alpha) +
    facet_grid(Species~RhoConfig) +
    ggtitle(paste('Estimate Spatial RE:', do.spatial)) +#, 
    # subtitle=paste('Knots:',temp.knots)) +
    ylab('Proportion') +
    theme(axis.text.x=element_text(angle=45, hjust=1))
  # g2
  
  #Bring the figures together
  g12 <- plot_grid(g, g2, nrow=2, ncol=1, align='v')
  # g12
  ggsave(paste0(output.dir,'/Pollock_Cod ', temp.knots,'kt.png'), plot=g12, 
         height=heights, width=widths, units='in', dpi=1e3)
}#next t


#===========================================================
#Plotting Other Species
t <- 1
for(t in 1:n.trial.knots) {
  temp.knots <- trial.knots[t]
  
  #Estimate Spatial RE
  do.spatial <- TRUE
  temp.df <- output.df[-which(output.df$Species %in% c('Walleye pollock','Pacific cod',rockfish)),]
  temp.df <- temp.df[temp.df$Est_Spatial_RE==do.spatial | is.na(temp.df$Est_Spatial_RE),]
  temp.df$Species <- gsub(" ", "\n", temp.df$Species)
  
  g <- ggplot(temp.df, aes(x=Year, y=value, fill=Region)) +
    theme_dark()+
    theme(legend.position='top') +
    scale_fill_colorblind() +
    geom_area(position='stack', alpha=alpha) +
    facet_grid(Species~RhoConfig) +
    ggtitle(paste('Estimate Spatial RE:', do.spatial)) +#, 
    # subtitle=paste('Knots:',temp.knots)) +
    ylab('Proportion') +
    theme(axis.text.x=element_text(angle=45, hjust=1))
  # g
  
  #Don't Estimate Spatial RE
  do.spatial <- FALSE
  temp.df <- output.df[-which(output.df$Species %in% c('Walleye pollock','Pacific cod',rockfish)),]
  temp.df <- temp.df[temp.df$Est_Spatial_RE==do.spatial | is.na(temp.df$Est_Spatial_RE),]
  temp.df$Species <- gsub(" ", "\n", temp.df$Species)
  
  g2 <- ggplot(temp.df, aes(x=Year, y=value, fill=Region)) +
    theme_dark()+
    theme(legend.position='top') +
    scale_fill_colorblind() +
    geom_area(position='stack', alpha=alpha) +
    facet_grid(Species~RhoConfig) +
    ggtitle(paste('Estimate Spatial RE:', do.spatial)) +#, 
    # subtitle=paste('Knots:',temp.knots)) +
    ylab('Proportion') +
    theme(axis.text.x=element_text(angle=45, hjust=1))
  # g2
  
  #Bring the figures together
  g12 <- plot_grid(g, g2, nrow=2, ncol=1, align='v')
  # g12
  ggsave(paste0(output.dir,'/Others ', temp.knots,'kt.png'), plot=g12, 
         height=heights, width=widths, units='in', dpi=1e3)
}#next t

#===========================================================
#Printing GOA POP Table
# write.csv(vast_est.output[[7]][[which(species.list$name %in% 'Pacific ocean perch')]]$vast_est, file=paste0(output.dir,'/POP AR+RW spat_TRUE.csv'))

#===========================================================
#Plotting All Species separately

# spec.surv <- unique(output.df$Name)
# n.spec.surv <- length(spec.surv)
# 
# s <- 1
# for(s in 1:n.spec.surv) {
#   temp.name <- spec.surv[s]
#   
#   temp.df <- output.df[output.df$Name==temp.name,]
#   
#   t <- 1
#   for(t in 1:n.trial.knots) {
#     temp.knots <- trial.knots[t]
#     g <- ggplot(temp.df[temp.df$Knots==temp.knots | temp.df$Knots==FALSE,],
#                   aes(x=Year, y=value, fill=Region)) +
#            theme_dark() +
#            theme(legend.position='top') +
#            scale_fill_colorblind() +
#            ylab('Proportion') +
#            geom_area(position='stack', alpha=alpha) +
#            facet_grid(Est_Spatial_RE~RhoConfig) +
#            ggtitle(temp.name, subtitle=paste('Knots:',temp.knots)) 
#     g
#   }#next t
# }#next s



#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Compare Temporal Linkages Between
#                                                              Intercepts and Spatio-temporal RE
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 4.10.17
#
#Purpose: To explore VAST model-based index sensitivity to autoregressive specifications on:
#           1) Intercept
#           2) Spatio-temporal random effect
#           3) Fix the number of knots @ 500, to begin with and consider changing to c(100,500) for sensitivity
#           4) Given 
#
#
#
#==================================================================================================
#NOTES:
#  a) int_RW-FE stRE_IaY - Fails on EBS Arrowtooth flounder. 
#  b) As a result we have removed EBS_SHELF Arrowtooth from this example.
#  c) When bias.correct==TRUE, n_x=100, and RhoConfig=c(2,0,0,0) - fails on s=10, GOA Big Skate
#==================================================================================================
#TIMING:
#
#==================================================================================================
require(VAST)
require(TMB)
require(parallel)
require(snowfall)
require(tidyverse)
require(ggthemes)


source("R/calc-design-based-index.r")
source("R/create-VAST-input.r")
source("R/create-Data-Geostat.r")
source("R/load-RACE-data.r")

source("R/cleanup-VAST-file.r")
source("R/get-VAST-index.r")


home.dir <- getwd()
#Create working directory
working.dir <- paste0(home.dir, "/examples/Test_Autoregressive")

#Determine species list
species.list <- read.csv("data/eval_species_list.csv", stringsAsFactors=FALSE)

#Limit species included
species.list <- species.list[species.list$include=='Y',]
#Remove EBS_SHELF Arrowtooth
species.list <- species.list[species.list$survey!='EBS_SHELF',]
#Remove Big Skate


n.species <- nrow(species.list)

#Create
species.series <- c(1:n.species)

#=======================================================================
##### CONTROL SECTION #####
#Number of cores to use
n.cores <- detectCores()-1

#Boolean for running estimation models
do.estim <- TRUE

#Trial Knot Numbers
trial.knots <- c(100,500)
n.trial.knots <- length(trial.knots)

#Trial AUTOREGRESSIVE specifications
#Note starts at 0
# rho.int.types <- c('Fixed_Effect','Independent_Among_Years','Random_Walk','Constant_Intercept','Autoregressive')
rho.int.types <- c('FE','IaY','RW','CI','AR')
# rho.stRE.types <- c('Independent_Among_Years',NA,'Random_Walk',NA,'Autoregressive')
rho.stRE.types <- c('IaY',NA,'RW',NA,'AR')

#Read in Autoregressive Input
trial.rho <- t(read.csv('Data/Test-Autoregressive-Input.csv', header=TRUE, stringsAsFactors=FALSE)[,-c(1:2)])

#Remove second option
trial.rho <- trial.rho[-c(2:3),]

n.trial.rho <- nrow(trial.rho)

# #Intercept
# rho.inter.ep <- c(0) #Encounter Probability Component 
# rho.inter.pcr <- c(0) #Positive Catch Rate Component
# #Spatio-temporal RE
# rho.stRE.ep #Encounter Probability Component 
# rho.stRE.pcr #Positive Catch Rate Component

#Boolean for bias correction
bias.correct <- TRUE
#=======================================================================
##### Run VAST model  #####
Version <- "VAST_v4_0_0"
lat_lon.def <- "start"

#SPATIAL SETTINGS
Method = c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km = 25
# n_x = c(100, 250, 500, 1000, 2000)[2] # Number of stations
Kmeans_Config = list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("All_areas"))

#MODEL SETTINGS
FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
# RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

ObsModel = c(1, 0) #Lognormal

#SPECIFY OUTPUTS
Options = c(SD_site_density = 0, SD_site_logdensity = 0,
            Calculate_Range = 1, Calculate_evenness = 0, Calculate_effective_area = 1,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)

#Output Directory Name
output.dir <- paste0(working.dir,"/output_bias.correct_",bias.correct)
dir.create(output.dir)


#=======================================================================
##### WRAPPER FUNCTION FOR RUNNING IN PARALLEL #####

s <- 1
# for(s in 1:n.species) {
wrapper_fxn <- function(s, n_x, RhoConfig) {
  require(TMB)
  require(TMBhelper)
  #Define file for analyses
  DateFile <- paste0(trial.dir,"/",species.list$survey[s],"_",species.list$name[s],"/")
  
  dir.create(DateFile)
  
  #Define species.codes
  species.codes <- species.list$species.code[s]
  survey <- species.list$survey[s]
  
  #=======================================================================
  ##### READ IN DATA AND BUILD VAST INPUT #####
  #  NOTE: this will create the DateFile
  
  # VAST_input <- create_VAST_input(species.codes=species.codes, lat_lon.def=lat_lon.def, save.Record=FALSE,
  #                                 Method=Method, grid_size_km=grid_size_km, n_x=n_x,
  #                                 Kmeans_Config=Kmeans_Config,
  #                                 strata.limits=strata.limits, survey=survey,
  #                                 DateFile=DateFile,
  #                                 FieldConfig, RhoConfig, OverdispersionConfig,
  #                                 ObsModel, Options)
  
  VAST_input <- create_VAST_input(species.codes=species.codes, combineSpecies=FALSE,
                                  lat_lon.def=lat_lon.def, save.Record=FALSE,
                                  Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                  Kmeans_Config=Kmeans_Config,
                                  strata.limits=strata.limits, survey=survey,
                                  DateFile=DateFile,
                                  FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                  OverdispersionConfig=OverdispersionConfig,
                                  ObsModel=ObsModel, Options=Options, Version=Version)
  
  
  
  #Unpack
  TmbData <- VAST_input$TmbData
  Data_Geostat <- VAST_input$Data_Geostat
  Spatial_List <- VAST_input$Spatial_List
  Extrapolation_List <- VAST_input$Extrapolation_List
  
  
  #=======================================================================
  ##### RUN VAST #####
  
  
  
  #Build TMB Object
  #  Compilation may take some time
  TmbList <- VAST::Build_TMB_Fn(TmbData = TmbData, RunDir = DateFile,
                                Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                                Method = Method)
  Obj <- TmbList[["Obj"]]
  
  
  if(bias.correct==FALSE) {
    Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
                               upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
                               bias.correct = bias.correct)
  }else {
    #NEW: Only Bias Correct Index
    Opt <- TMBhelper::Optimize(obj=Obj, lower=TmbList[["Lower"]], 
                               upper=TmbList[["Upper"]], getsd=TRUE, savedir=DateFile, 
                               bias.correct=bias.correct, newtonsteps=1,
                               bias.correct.control=list(sd=FALSE, nsplit=200, split=NULL,
                                                         vars_to_correct="Index_cyl"))
  }
  #Save output
  Report = Obj$report()
  # Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
  # save(Save, file=paste0(DateFile,"Save.RData"))
  
  #Calculate index values
  # TmbData = TmbData, Sdreport = Opt[["SD"]]
  vast_est <- get_VAST_index(TmbData=TmbData, Sdreport=Opt[["SD"]], bias.correct=bias.correct, Data_Geostat=Data_Geostat)
  #========================================================================
  ##### DIAGNOSTIC AND PREDICTION PLOTS #####
  # plot_VAST_output(Opt, Report, DateFile, survey, TmbData, Data_Geostat, Extrapolation_List, Spatial_List)
  
  #========================================================================
  ##### CLEANUP VAST OUTPUT #####
  # cleanup_VAST_file(DateFile=DateFile, Version=Version) #No longer necessary as we are deleting everything at the end
  
  rm("VAST_input", "TmbData", "Data_Geostat", "Spatial_List", "Extrapolation_List", "TmbList", "Obj")#, "Save")#, "Opt", "Report")
  
  #========================================================================
  setwd(home.dir)
  ##### RETURN SECTION #####
  out <- NULL
  out$vast_est <- vast_est
  out$Opt <- Opt
  return(out)
} 

# for(s in species.series) {
# wrapper_fxn(s, n_x, RhoConfig)
# }
#=======================================================================
##### Loop Through Trial Knots  #####
vast_est.output <- vector('list', length=(n.trial.knots * n.trial.rho))
vast_knots <- vector(length=(n.trial.knots * n.trial.rho))
vast_rho.int <- vector(length=(n.trial.knots * n.trial.rho))
vast_rho.stRE <- vector(length=(n.trial.knots * n.trial.rho))

if(do.estim==TRUE) {

  
  time.1 <- date()
  
  #Counter for knots by rho
  counter <- 1
  
  t <- 1
  for(t in 1:n.trial.knots) {
    print(paste('## Trial Knot Number',t,'of',n.trial.knots))
    print(paste('# Trial Knots:',trial.knots[t]))
    #Specify trial observation model
    
    #Specify knots
    n_x <- trial.knots[t]
    
    r <- 1
    for(r in 1:n.trial.rho) {
      print(paste('### trial.rho, r:',r))
      #Specify intercepts and spatio-temporal variation across time
      RhoConfig <- trial.rho[r,]
      names(RhoConfig) <- c('Beta1','Beta2','Epsilon1','Epsilon2')
      
      #Record
      if(RhoConfig[1]==RhoConfig[2]) {#IF intercept specs are the same
        vast_rho.int[counter] <- rho.int.types[RhoConfig[1]+1]
      }else {#IF different
        vast_rho.int[counter] <- paste0(rho.int.types[RhoConfig[1]+1], "-", rho.int.types[RhoConfig[2]+1])
      }
      if(RhoConfig[3]==RhoConfig[4]) {
        vast_rho.stRE[counter] <- rho.stRE.types[RhoConfig[3]+1]
      }else {
        vast_rho.stRE[counter] <- paste0(rho.stRE.types[RhoConfig[3]+1], "-", rho.stRE.types[RhoConfig[4]+1])
      }
      vast_knots[counter] <- n_x
      
      #Setup File
      trial.dir <- paste0(working.dir,"/",n_x,"_bias.corr_",bias.correct)
      dir.create(trial.dir)
      trial.dir <- paste0(trial.dir, "/int_",vast_rho.int[counter], " stRE_",vast_rho.stRE[counter])
      dir.create(trial.dir)
    
      
      #=======================================================================
      ##### TEST WRAPPER FUNCTION #####
      # wrapper_fxn(s=1, n_x=100, RhoConfig=RhoConfig)
       
      #=======================================================================
      ##### SNOWFALL CODE FOR PARALLEL #####
      sfInit(parallel=TRUE, cpus=n.cores, type='SOCK')
      sfExportAll() #Exportas all global variables to cores
      sfLibrary(TMB)  #Loads a package on all nodes
      sfLibrary(VAST)
      output <- sfLapply(species.series, fun=wrapper_fxn, n_x=n_x, RhoConfig=RhoConfig)
      sfStop()

      vast_est.output[[counter]] <- output

      #For Update
      # output <- vast_est.output[[counter]]
      # save(output, file=paste0(output.dir, "/testVAST_output_",counter,".RData"), compression_level=9)
      saveRDS(output, file=paste0(output.dir, "/VAST_output_",counter,".rds"))
      #Real
      
      
      counter <- counter+1
    }#next r
    
  }#next t
  
  #Dimensions for vast_est.output are 1) Trial knots, 2) Species
  # vast_est.output[[1:n.trial.knots]][[1:n.species]]
  
  #Create output directory
  # save(vast_est.output, file=paste0(output.dir,"/vast_est.output.RData"))
  #Also save specifications
  vast_specs <- data.frame(vast_knots, vast_rho.int, vast_rho.stRE)
  write.csv(vast_specs, file=paste0(output.dir,"/vast_specs.csv"))
  
  #=======================================================================
  ##### DELETE UNNECESSARY FILE STRUCTURE #####
  #Must reset working directory
  setwd(working.dir)
  t <- 1
  for(t in 1:n.trial.knots) {
    unlink(paste0(working.dir,"/",trial.knots[t],"_bias.corr_",bias.correct), recursive=TRUE)
  }#next t

  time.2 <- date()

  print(paste('### START:', time.1))
  print(paste('### END:', time.2))
  
}else {
  
  
  #Old
  # load(paste0(output.dir,"/vast_est.output.RData"))
  
  specs <- read.csv(paste0(output.dir,"/vast_specs.csv"), header=TRUE, stringsAsFactors=FALSE)
  n.specs <- nrow(specs)
  
  for(i in 1:n.specs) {
    print(i)
    vast_est.output[[i]] <- readRDS(file=paste0(output.dir, "/VAST_output_",i,".rds"))
    vast_knots[i] <- specs$vast_knots[i]
    vast_rho.int[i] <- specs$vast_rho.int[i]
    vast_rho.stRE[i] <- specs$vast_rho.stRE[i]
  }#next i
}


#=====================================================
# Gather Data
vast.list <- NULL
aic.list <- NULL
aic.vect <- vector(length=0)
converge.vect <- vector(length=0)

#Load dataset to determine which years to include
goa.yrs <- sort(unique(load_RACE_data(species.codes=30420,
                                        combineSpecies=FALSE, survey='GOA')$Year))
ai.yrs <- sort(unique(load_RACE_data(species.codes=30420,
                        combineSpecies=FALSE, survey='AI')$Year))


i <- 1
for(i in 1:n.specs) {
  s <- 1
  for(s in 1:n.species) {
    #Species Information
    temp.species <- species.list$name[s]
    temp.survey <- species.list$survey[s]
    temp.name <- paste0(temp.survey,": ",temp.species)
    
    #Determine Survey years (Currently only GOA and AI)
    if(temp.survey=='GOA') {
      temp.yrs <- goa.yrs
    }else {
      temp.yrs <- ai.yrs
    }
    
    #Get VAST model index
    temp.list <- vast_est.output[[i]][[s]]$vast_est[c(1,4,6)]
    
    #Calculate CV
    CV <- temp.list$SD_mt/temp.list$Estimate_metric_tons
    
    #Determine which are survey years
    survey.year <- temp.list$Year %in% temp.yrs
    
    #Bind it
    temp.list <- cbind(temp.list, CV, temp.survey, temp.species, temp.name, 'VAST', vast_knots[i], vast_rho.int[i], vast_rho.stRE[i], survey.year)
    
    
    #AIC and convergence
    #Get AIC and convergence
    temp.aic <- cbind(temp.survey, temp.species, temp.name, 'VAST', vast_knots[i], vast_rho.int[i], vast_rho.stRE[i])

    aic.vect <- append(aic.vect, vast_est.output[[i]][[s]]$Opt$AIC)
    converge.vect <- append(converge.vect, vast_est.output[[i]][[s]]$Opt$converge)
    
    #Combine to larger lists
    vast.list <- rbind(vast.list, temp.list)
    aic.list <- rbind(aic.list, temp.aic) 
    
    
  }#Next species
  
}#next model configuration i



#Add names
vast.df <- data.frame(vast.list)
names(vast.df) <- c('Year','Biomass','SD','CV','Survey','Species', 'Name','Model','Knots','Rho_Intercept','Rho_stRE','SurveyYear')

head(vast.df)

aic.df <- data.frame(aic.list, aic.vect, converge.vect)
names(aic.df) <- c('Survey','Species','Name','Model','Knots','Rho_Intercept','Rho_stRE','AIC','Converge')
aic.df$Converge <- as.factor(aic.df$Converge)


#=====================================================
#PLOT IT OUT
g <- ggplot(aic.df, aes(x=Rho_Intercept, y=AIC, colour=Knots, shape=Converge)) +
       theme_gray() +
       geom_point(size=5, alpha=0.5) +
       facet_wrap(Survey~Species, scales='free')
g

# ggsave(paste0(output.dir,'/AIC Compare.png'), plot=g, height=7, width=9, units='in', dpi=500)

g <- ggplot(aic.df[aic.df$Survey=='GOA',], aes(x=Rho_Intercept, y=AIC, colour=Knots)) +
       theme_gray() +
       geom_point(size=5, alpha=0.5) +
       facet_wrap(~Species, scales='free') +
       ggtitle('Gulf of Alaska Survey') +
       theme(axis.text.x=element_text(angle=90, hjust=1))
g
ggsave(paste0(output.dir,'/AIC Compare_GOA.png'), plot=g, height=7, width=9, units='in', dpi=500)

g <- ggplot(aic.df[aic.df$Survey=='AI',], aes(x=Rho_Intercept, y=AIC, colour=Knots)) +
       theme_gray() +
       geom_point(size=5, alpha=0.5) +
       facet_wrap(~Species, scales='free') +
       ggtitle('Aleutian Islands Survey') +
       theme(axis.text.x=element_text(angle=90, hjust=1))
g
ggsave(paste0(output.dir,'/AIC Compare_AI.png'), plot=g, height=7, width=9, units='in', dpi=500)


#===================================
#Plotting Index Values

g <- ggplot(aic.df, aes(x=Knots, y=AIC, colour=Rho_Intercept)) +
  theme_gray() +
  # geom_point(size=5, alpha=0.5) +
  facet_wrap(Survey~Species, scales='free') +
  geom_jitter()
# g

g <- ggplot(aic.df, aes(x=Rho_Intercept, y=AIC, colour=Knots)) +
  theme_gray() +
  # geom_point(size=5, alpha=0.5) +
  geom_jitter() +
  facet_grid(Species~Survey, scales='free')
# g


#Plot some trends

#Determine which years to include

vast.df$Knots <- as.factor(vast.df$Knots)

#species x knots
g <- ggplot(vast.df[vast.df$Survey=='GOA' & vast.df$SurveyYear==TRUE,],
            aes(x=Year, y=Biomass/1e3, group=Rho_Intercept, colour=Rho_Intercept)) +
  theme_gray() +
  geom_line() +
  geom_point() +
  facet_grid(Species~Knots, scales='free') +
  ggtitle('Gulf of Alaska Survey') +
  ylab('Biomass (thousands of metric tonnes)')
  
g

#Break it down by species groups
goa.species <- as.vector(unique(vast.df$Species[vast.df$Survey=='GOA' & vast.df$SurveyYear==TRUE]))

goa.rockfish <- c('Pacific ocean perch', 'Northern rockfish', 'Harlequin rockfish')
goa.others <- goa.species[-which(goa.species %in% goa.rockfish)]

#Plot rockfish
g <- ggplot(vast.df[vast.df$Survey=='GOA' & vast.df$SurveyYear==TRUE & vast.df$Species%in%goa.rockfish,],
            aes(x=Year, y=Biomass/1e3, group=Rho_Intercept, colour=Rho_Intercept)) +
  theme_gray() +
  # scale_color_colorblind() +
  geom_line(alpha=0.5) +
  geom_point(alpha=0.5) +
  # theme_fivethirtyeight() +
  # scale_color_colorblind() +
  facet_grid(Species~Knots, scales='free') +
  ggtitle('Gulf of Alaska Survey') +
  ylab('Biomass (thousands of metric tonnes)')

g

ggsave(paste0(output.dir,'/Rockfish Trend Compare_GOA.png'), plot=g, height=9, width=8, units='in', dpi=500)
ggsave(paste0(getwd(),"/Output/Figs for Sept_2017 GPT/Rockfish Trend Compare_GOA.png"), plot=g, height=7, width=10, units='in', dpi=1e3)


#Plot others
g <- ggplot(vast.df[vast.df$Survey=='GOA' & vast.df$SurveyYear==TRUE & vast.df$Species%in%goa.others,],
            aes(x=Year, y=Biomass/1e3, group=Rho_Intercept, colour=Rho_Intercept)) +
  theme_gray() +
  # scale_color_colorblind()+
  geom_line(alpha=0.5) +
  geom_point(alpha=0.5) +
  # scale_color_solarized() +
  facet_grid(Species~Knots, scales='free') +
  ggtitle('Gulf of Alaska Survey') +
  ylab('Biomass (thousands of metric tonnes)')
  # theme(legend.position='bottom')

g

ggsave(paste0(output.dir,'/Others Trend Compare_GOA.png'), plot=g, height=9, width=8, units='in', dpi=500)
ggsave(paste0(getwd(),"/Output/Figs for Sept_2017 GPT/Others Trend Compare_GOA.png"), plot=g, height=7.75, width=11, units='in', dpi=1e3)

#================
g <- ggplot(vast.df[vast.df$Survey=='AI' & vast.df$SurveyYear==TRUE,],
            aes(x=Year, y=Biomass/1e3, group=Rho_Intercept, colour=Rho_Intercept)) +
  theme_gray() +
  geom_line(alpha=0.5) +
  geom_point(alpha=0.5) +
  facet_grid(Species~Knots, scales='free') +
  ggtitle('Aleutian Islands Survey') +
  ylab('Biomass (thousands of metric tonnes)')

g
ggsave(paste0(output.dir,'/Trend Compare_AI.png'), plot=g, height=9, width=8, units='in', dpi=500)
ggsave(paste0(getwd(),"/Output/Figs for Sept_2017 GPT/Trend Compare_AI.png"), plot=g, height=7, width=10, units='in', dpi=1e3)


#=================================================
g <- ggplot(vast.df[vast.df$Survey=='GOA' & vast.df$Species%in%goa.rockfish & vast.df$Knots==100 &
            vast.df$Rho_Intercept!='AR-FE' & vast.df$Rho_Intercept!='RW-FE' &
            vast.df$Rho_Intercept!='FE-AR' & vast.df$Rho_Intercept!='FE-RW',],
            aes(x=Year, y=Biomass/1e3, group=Rho_Intercept, colour=Rho_Intercept)) +
  theme_gray() +
  # scale_color_colorblind() +
  geom_line(alpha=0.5) +
  geom_point(alpha=0.5) +
  # theme_fivethirtyeight() +
  # scale_color_colorblind() +
  facet_grid(Species~SurveyYear, scales='free') +
  ggtitle('Gulf of Alaska Survey') +
  ylab('Biomass (thousands of metric tonnes)')

g
ggsave(paste0(getwd(),"/Output/Figs for Sept_2017 GPT/Survey_non Compare Rockfish_GOA.png"), plot=g, height=7, width=10, units='in', dpi=1e3)

g <- ggplot(vast.df[vast.df$Survey=='GOA' & vast.df$Species%in%goa.others & vast.df$Knots==100 &
                      vast.df$Rho_Intercept!='AR-FE' & vast.df$Rho_Intercept!='RW-FE' &
                      vast.df$Rho_Intercept!='FE-AR' & vast.df$Rho_Intercept!='FE-RW',],
            aes(x=Year, y=Biomass/1e3, group=Rho_Intercept, colour=Rho_Intercept)) +
  theme_gray() +
  # scale_color_colorblind() +
  geom_line(alpha=0.5) +
  geom_point(alpha=0.5) +
  # theme_fivethirtyeight() +
  # scale_color_colorblind() +
  facet_grid(Species~SurveyYear, scales='free') +
  ggtitle('Gulf of Alaska Survey') +
  ylab('Biomass (thousands of metric tonnes)')

g
ggsave(paste0(getwd(),"/Output/Figs for Sept_2017 GPT/Survey_non Compare others_GOA.png"), plot=g, height=7.75, width=11, units='in', dpi=1e3)
#=================================================




g <- ggplot(vast.df[vast.df$Survey=='GOA' & vast.df$Species%in%goa.rockfish & vast.df$Knots==100,],
            aes(x=Year, y=Biomass/1e3, group=Rho_Intercept, colour=Rho_Intercept, ymin=0)) +
  theme_gray() +
  # scale_color_colorblind() +
  geom_line(alpha=0.5) +
  geom_point(alpha=0.5) +
  # theme_fivethirtyeight() +
  # scale_color_colorblind() +
  facet_grid(Species~SurveyYear, scales='free') +
  ggtitle('Gulf of Alaska Survey') +
  ylab('Biomass (thousands of metric tonnes)')

g






g <- ggplot(vast.df[vast.df$Survey=='GOA' & vast.df$Species%in%goa.rockfish & vast.df$Knots==100,],
            aes(x=Year, y=Biomass/1e3, group=Rho_Intercept, colour=Rho_Intercept, ymin=0)) +
  theme_gray() +
  # scale_color_colorblind() +
  geom_line(alpha=0.5) +
  geom_point(alpha=0.5) +
  # theme_fivethirtyeight() +
  # scale_color_colorblind() +
  facet_grid(Species~SurveyYear, scales='free') +
  ggtitle('Gulf of Alaska Survey') +
  ylab('Biomass (thousands of metric tonnes)')

g


g <- ggplot(vast.df[vast.df$Survey=='GOA' & vast.df$Species%in%goa.rockfish & vast.df$Knots==500 &
                      vast.df$Rho_Intercept!='AR-FE' & vast.df$Rho_Intercept!='RW-FE',],
            aes(x=Year, y=Biomass/1e3, group=Rho_Intercept, colour=Rho_Intercept, ymin=0)) +
  theme_gray() +
  # scale_color_colorblind() +
  geom_line(alpha=0.5) +
  geom_point(alpha=0.5) +
  # theme_fivethirtyeight() +
  # scale_color_colorblind() +
  facet_grid(Species~SurveyYear, scales='free') +
  ggtitle('Gulf of Alaska Survey') +
  ylab('Biomass (thousands of metric tonnes)')

g

g <- ggplot(vast.df[vast.df$Survey=='GOA' & vast.df$Species%in%goa.rockfish & vast.df$SurveyYear==TRUE,],
            aes(x=Year, y=Biomass/1e3, group=Rho_Intercept, colour=Knots, ymin=0)) +
  theme_gray() +
  # scale_color_colorblind() +
  geom_line(alpha=0.5) +
  geom_point(alpha=0.5) +
  # theme_fivethirtyeight() +
  # scale_color_colorblind() +
  facet_wrap(Species~Rho_Intercept, scales='free', nrow=length(goa.rockfish)) +
  ggtitle('Gulf of Alaska Survey') +
  ylab('Biomass (thousands of metric tonnes)')

g


#=====================================================================================================
g <- ggplot(vast.df[vast.df$Survey=='GOA' & vast.df$Species%in%goa.rockfish & vast.df$Knots==100,],# &
                      # vast.df$Rho_Intercept!='AR-FE' & vast.df$Rho_Intercept!='RW-FE' &
                      # vast.df$Rho_Intercept!='FE-AR' & vast.df$Rho_Intercept!='FE-RW',],
            aes(x=Year, y=Biomass/1e3, group=Rho_Intercept, colour=Rho_Intercept, ymin=0)) +
  theme_gray() +
  # scale_color_colorblind() +
  geom_line(alpha=0.5) +
  geom_point(alpha=0.5) +
  # theme_fivethirtyeight() +
  # scale_color_colorblind() +
  facet_grid(Species~SurveyYear, scales='free') +
  ggtitle('Gulf of Alaska Survey') +
  ylab('Biomass (thousands of metric tonnes)')

g

g <- ggplot(vast.df[vast.df$Survey=='GOA' & vast.df$Species%in%goa.others & vast.df$Knots==100 &
                      vast.df$Rho_Intercept!='AR-FE' & vast.df$Rho_Intercept!='RW-FE' &
                      vast.df$Rho_Intercept!='FE-AR' & vast.df$Rho_Intercept!='FE-RW',],
            aes(x=Year, y=Biomass/1e3, group=Rho_Intercept, colour=Rho_Intercept, ymin=0)) +
  theme_gray() +
  # scale_color_colorblind() +
  geom_line(alpha=0.5) +
  geom_point(alpha=0.5) +
  # theme_fivethirtyeight() +
  # scale_color_colorblind() +
  facet_grid(Species~SurveyYear, scales='free') +
  ggtitle('Gulf of Alaska Survey') +
  ylab('Biomass (thousands of metric tonnes)')

g


g <- ggplot(vast.df[vast.df$Survey=='AI' & vast.df$Knots==100 &
                      vast.df$Rho_Intercept!='AR-FE' & vast.df$Rho_Intercept!='RW-FE' &
                      vast.df$Rho_Intercept!='FE-AR' & vast.df$Rho_Intercept!='FE-RW',],
            aes(x=Year, y=Biomass/1e3, group=Rho_Intercept, colour=Rho_Intercept, ymin=0)) +
  theme_gray() +
  # scale_color_colorblind() +
  geom_line(alpha=0.5) +
  geom_point(alpha=0.5) +
  # theme_fivethirtyeight() +
  # scale_color_colorblind() +
  facet_grid(Species~SurveyYear, scales='free') +
  ggtitle('Aleutian Islands Survey') +
  ylab('Biomass (thousands of metric tonnes)')

g

g <- ggplot(vast.df[vast.df$Survey=='AI' &
                      vast.df$Rho_Intercept!='AR-FE' & vast.df$Rho_Intercept!='RW-FE' &
                      vast.df$Rho_Intercept!='FE-AR' & vast.df$Rho_Intercept!='FE-RW',],
            aes(x=Year, y=Biomass/1e3, group=Rho_Intercept, colour=Rho_Intercept, ymin=0)) +
  theme_gray() +
  # scale_color_colorblind() +
  geom_line(alpha=0.5) +
  geom_point(alpha=0.5) +
  # theme_fivethirtyeight() +
  # scale_color_colorblind() +
  facet_grid(Species~Knots, scales='free') +
  ggtitle('Aleutian Islands Survey') +
  ylab('Biomass (thousands of metric tonnes)')

g


g <- ggplot(vast.df[vast.df$Survey=='GOA' & vast.df$Knots==100 &
                      vast.df$Rho_Intercept!='AR-FE' & vast.df$Rho_Intercept!='RW-FE' &
                      vast.df$Rho_Intercept!='FE-AR' & vast.df$Rho_Intercept!='FE-RW',],
            aes(x=Year, y=Biomass/1e3, group=Rho_Intercept, colour=Rho_Intercept, ymin=0)) +
  theme_gray() +
  # scale_color_colorblind() +
  geom_line(alpha=0.5) +
  geom_point(alpha=0.5) +
  # theme_fivethirtyeight() +
  # scale_color_colorblind() +
  # facet_grid(Species~Knots, scales='free') +
  facet_wrap(~Species, scales='free') +
  ggtitle('Gulf of Alaska Survey') +
  ylab('Biomass (thousands of metric tonnes)')

g



# g <- ggplot(vast.list[vast.list$Survey=='GOA',],
#             aes(x=Knots, y=Biomass, group=Rho_Intercept, fill=Rho_Intercept, ymin=0)) +
#   theme_gray() +
#   geom_boxplot() +
#   facet_wrap(~Species, scales='free')
# g

# g <- ggplot(vast.list[vast.list$Survey=='GOA' & vast.list$Rho_Intercept!='RW-FE',],
#             aes(x=Year, y=Biomass/1e3,  colour=Rho_Intercept, ymin=0)) +
#   theme_gray() +
#   geom_line() +
#   facet_grid(Species~Knots, scales='free')
# g



#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Compare VAST estimates with and without BIAS CORRECTION
#
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 11.30.17
#
#Purpose: Determine if index scale is more sensitive sensitive to: a) spatial complexity (number of knots), or b) bias correction.
#
#
#==================================================================================================
#NOTES:
# 1)  Error in checkForRemoteErrors(val) : 8 nodes produced errors; first error: Memory allocation fail in function 'MakeADHessObject2' 
#  Suggests cannot be done in parallel, will attempt in series.

# 2) Running in series (32 GB Ram) stopped with bias.corr=TRUE, knots=400, s=4 (AI Walleye Pollock)

#Spiny Dogfish May be the Problem

#  3) Does not work for Arrowtooth Flounder
#==================================================================================================
#TIMING:

# [1] "### START: Thu May 31 16:35:10 2018"
# [1] "### END: Sat Jun 02 02:54:21 2018"


##==================================================================================================
require(parallel)
require(snowfall)
require(tidyverse)
require(ggthemes)
require(VAST)
require(TMB)
require(viridis)
require(TMBhelper)

source("R/calc-design-based-index.r")
source("R/create-VAST-input.r")
source("R/create-Data-Geostat.r")
source("R/load-RACE-data.r")
source("R/cleanup-VAST-file.r")
source("R/get-VAST-index.r")


home.dir <- getwd()
#Create working directory
working.dir <- paste0(home.dir, "/examples/Test_Bias_Correct")

#Determine species list
species.list <- read.csv("data/eval_species_list.csv", stringsAsFactors=FALSE)

#Limit species included
species.list <- species.list[species.list$include=='Y',]
#Remove EBS_SHELF Arrowtooth
# species.list <- species.list[species.list$survey!='EBS_SHELF',]

#Remove Spiny Dogfish
# species.list <- species.list[-which(species.list$name=='Spiny dogfish'),]

#Remove Arrowtooth
# species.list <- species.list[-which(species.list$name=='Arrowtooth flounder'),]

n.species <- nrow(species.list)

#Create
species.series <- c(1:n.species)

#=======================================================================
##### CONTROL SECTION #####
#Number of cores to use
n.cores <- detectCores()-1

#Boolean for running estimation models
do.estim <- FALSE

#Trial Knot Numbers
trial.knots <- c(100,250,500,750)
n.trial.knots <- length(trial.knots)

#Trial RANDOM EFECTS SPECIFICATIONS specifications
trial.bias.correct <- c(FALSE,TRUE)
n.trial.bias.correct <- length(trial.bias.correct)


#Plotting Stuff
height <- 6
width <- 10

#Boolean for bias correction
# bias.correct <- FALSE
#=======================================================================
##### Run VAST model  #####
Version <- "VAST_v4_0_0"
lat_lon.def <- "start"

#SPATIAL SETTINGS
Method = c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km = 25
# n_x = c(100, 250, 500, 1000, 2000)[2] # Number of stations
Kmeans_Config = list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("All_areas"))

#MODEL SETTINGS
FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

ObsModel = c(1, 0) #Lognormal

#SPECIFY OUTPUTS
Options = c(SD_site_density = 0, SD_site_logdensity = 0,
            Calculate_Range = 1, Calculate_evenness = 0, Calculate_effective_area = 1,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)

#Output Directory Name
output.dir <- paste0(working.dir,"/Testing")
dir.create(output.dir)


#=======================================================================
##### WRAPPER FUNCTION FOR RUNNING IN PARALLEL #####

s <- 1
# n_x <- 100
# bias.correct <- FALSE
# for(s in 1:n.species) {
wrapper_fxn <- function(s, n_x, bias.correct, Version=Version, ...) {
  require(TMB)
  require(TMBhelper)
  #Define file for analyses
  DateFile <- paste0(trial.dir,"/",species.list$survey[s],"_",species.list$name[s],"/")
  
  dir.create(DateFile)
  
  #Define species.codes
  species.codes <- species.list$species.code[s]
  survey <- species.list$survey[s]
  
  #=======================================================================
  ##### READ IN DATA AND BUILD VAST INPUT #####
  #  NOTE: this will create the DateFile
  VAST_input <- create_VAST_input(species.codes=species.codes, combineSpecies=FALSE,
                                  lat_lon.def=lat_lon.def, save.Record=FALSE,
                                  Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                  Kmeans_Config=Kmeans_Config,
                                  strata.limits=strata.limits, survey=survey,
                                  DateFile=DateFile,
                                  FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                  OverdispersionConfig=OverdispersionConfig,
                                  ObsModel=ObsModel, Options=Options, Version=Version)
  
  
  
  #Unpack
  TmbData <- VAST_input$TmbData
  Data_Geostat <- VAST_input$Data_Geostat
  Spatial_List <- VAST_input$Spatial_List
  Extrapolation_List <- VAST_input$Extrapolation_List
  
  
  #=======================================================================
  ##### RUN VAST #####

  #Build TMB Object
  #  Compilation may take some time
  TmbList <- VAST::Build_TMB_Fn(TmbData = TmbData, RunDir = DateFile,
                                Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                                Method = Method)
  
  Obj <- TmbList[["Obj"]]
  
  if(bias.correct==FALSE) {
    Opt <- TMBhelper::Optimize(obj=Obj, lower=TmbList[["Lower"]],
                               upper=TmbList[["Upper"]], getsd=TRUE, savedir=DateFile,
                               bias.correct=bias.correct, newtonsteps=1)
  }else {
    #NEW: Only Bias Correct Index
    Opt <- TMBhelper::Optimize(obj=Obj, lower=TmbList[["Lower"]], 
                               upper=TmbList[["Upper"]], getsd=TRUE, savedir=DateFile, 
                               bias.correct=bias.correct, newtonsteps=1,
                               bias.correct.control=list(sd=FALSE, nsplit=200, split=NULL,
                               vars_to_correct="Index_cyl"))
  }
  
  print('Here? #1')
  #Save output
  Report = Obj$report()
  # Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
  # save(Save, file=paste0(DateFile,"Save.RData"))
  print('Here? #2')
  #Calculate index values
  # TmbData = TmbData, Sdreport = Opt[["SD"]]
  vast_est <- get_VAST_index(TmbData=TmbData, Sdreport=Opt[["SD"]], bias.correct=bias.correct, Data_Geostat=Data_Geostat)
  print('Here? #3')
  #========================================================================
  ##### DIAGNOSTIC AND PREDICTION PLOTS #####
  # plot_VAST_output(Opt, Report, DateFile, survey, TmbData, Data_Geostat, Extrapolation_List, Spatial_List)
  
  #========================================================================
  ##### CLEANUP VAST OUTPUT #####
  cleanup_VAST_file(DateFile=DateFile, Version=Version) #No longer necessary as we are deleting everything at the end
  print('Here? #4')
  
  rm("VAST_input", "TmbData", "Data_Geostat", "Spatial_List", "Extrapolation_List", "TmbList", "Obj","Report")#, "Save")#, "Opt", "Report")
  print('Here? #5')
  if(is.loaded(Version)) {
    dyn.unload(Version)
  }
  print('Here? #6')
  #========================================================================
  setwd(home.dir)
  ##### RETURN SECTION #####
  out <- NULL
  out$vast_est <- vast_est
  out$Opt <- Opt
  return(out)
} 

#=======================================================================
##### Loop Through Trial Knots  #####
vast_est.output <- vector('list', length=(n.species*n.trial.knots*n.trial.bias.correct))
vast_knots <- vector(length=(n.species*n.trial.knots*n.trial.bias.correct))
vast_bias.correct <- vector(length=(n.species*n.trial.knots*n.trial.bias.correct))
vast_species <- vector(length=(n.species*n.trial.knots*n.trial.bias.correct))

if(do.estim==TRUE) {
  
  time.1 <- date()
  
  #Counter for knots by rho
  counter <- 1
  s <- 1
  for(s in 1:n.species) {
    
    t <- 1
    for(t in 1:n.trial.knots) {
      print(paste('### Trial Species Number',s,'of',n.species))
      print(paste('## Trial Knot Number',t,'of',n.trial.knots))
      print(paste('# Trial Knots:',trial.knots[t]))
      #Specify trial observation model
    
      #Specify knots
      n_x <- trial.knots[t]
    
      r <- 1
      for(r in 1:n.trial.bias.correct) {
        print(paste('#### Trial Bias Correct',r,'of',n.trial.bias.correct))
      
        bias.correct <- trial.bias.correct[r]
        #Record
        vast_bias.correct[counter] <- bias.correct
        vast_knots[counter] <- n_x
        vast_species[counter] <- s
        #Setup File
        trial.dir <- paste0(working.dir,"/",n_x,"_bias.corr_",bias.correct)
        dir.create(trial.dir)

        #=======================================================================
        ##### TEST WRAPPER FUNCTION #####
        output <- NULL
        output <- wrapper_fxn(s=s, n_x=n_x, bias.correct=bias.correct, Version=Version)

        #=======================================================================
        ##### TEST WRAPPER FUNCTION #####
        # output <- wrapper_fxn(s=1, n_x=n_x, bias.correct=bias.correct)
      
        #=======================================================================
        ##### SNOWFALL CODE FOR PARALLEL #####
        # sfInit(parallel=TRUE, cpus=n.cores, type='SOCK')
        # sfExportAll() #Exportas all global variables to cores
        # sfLibrary(TMB)  #Loads a package on all nodes
        # sfLibrary(VAST)
        # output <- sfLapply(species.series, fun=wrapper_fxn, n_x=n_x, bias.correct=bias.correct)
        # sfStop()
        # 
        # vast_est.output[[counter]] <- output
      
        #For Update
        # output <- vast_est.output[[counter]]
        # save(output, file=paste0(output.dir, "/testVAST_output_",counter,".RData"), compression_level=9)
        saveRDS(output, file=paste0(output.dir, "/VAST_output_",counter,".rds"))
      
        counter <- counter+1
      }#next r
    }#next t
  }#next s  
  #Create output directory
  #Also save specifications
  vast_name <- paste0(species.list$survey[vast_species],"_",
                      species.list$name[vast_species])
  vast_specs <- data.frame(vast_species, vast_knots, vast_bias.correct,
                             vast_name)
  
  write.csv(vast_specs, file=paste0(output.dir,"/vast_specs.csv"))
  
  #=======================================================================
  ##### DELETE UNNECESSARY FILE STRUCTURE #####
  #Must reset working directory
  setwd(working.dir)
  t <- 1
  for(t in 1:n.trial.knots) {
    r <- 1
    for(r in 1:n.trial.bias.correct) {
      unlink(paste0(working.dir,"/",trial.knots[t],"_bias.corr_",trial.bias.correct[r]), recursive=TRUE)
    }#next r
  }#next t
  
  time.2 <- date()
  
  print(paste('### START:', time.1))
  print(paste('### END:', time.2))
  
}else {
  
  
  #Old
  # load(paste0(output.dir,"/vast_est.output.RData"))
  
  specs <- read.csv(paste0(output.dir,"/vast_specs.csv"), header=TRUE, stringsAsFactors=FALSE)
  n.specs <- nrow(specs)
  
  for(i in 1:n.specs) {
    print(i)
    vast_est.output[[i]] <- readRDS(file=paste0(output.dir, "/VAST_output_",i,".rds"))
    vast_knots[i] <- specs$vast_knots[i]
    vast_bias.correct[i] <- specs$vast_bias.correct[i]
  }#next i
}

#=====================================================
# Gather Data
#  Note: This is slightly difference because I wasn't able to run this in parallel,
#          so specs and data structure are not the same form as previous examples. 
vast.list <- NULL
aic.list <- NULL
aic.vect <- vector(length=0)
converge.vect <- vector(length=0)

#Load dataset to determine which years to include
goa.yrs <- sort(unique(load_RACE_data(species.codes=30420,
                                      combineSpecies=FALSE, survey='GOA')$Year))
ai.yrs <- sort(unique(load_RACE_data(species.codes=30420,
                                     combineSpecies=FALSE, survey='AI')$Year))


i <- 1
for(i in 1:n.specs) {
  # print(paste('i:',i))
    #Species Information
    temp.species <- species.list$name[specs$vast_species[i]]  #species.list$name[s]
    temp.survey <- species.list$survey[specs$vast_species[i]]  #species.list$survey[s]
    temp.name <- paste0(temp.survey,": ",temp.species)

    #Determine Survey years (Currently only GOA and AI)
    if(temp.survey=='GOA') {
      temp.yrs <- goa.yrs
    }else {
      temp.yrs <- ai.yrs
    }

    #Get VAST model index
    temp.list <- vast_est.output[[i]]$vast_est[c(1,4,6)]

    #Calculate CV
    CV <- temp.list$SD_mt/temp.list$Estimate_metric_tons

    #Determine which are survey years
    survey.year <- temp.list$Year %in% temp.yrs

    #Bind it
    temp.list <- cbind(temp.list, CV, temp.survey, temp.species, temp.name, 'VAST', 
                         vast_knots[i], vast_bias.correct[i], survey.year)#, specs$vast_name[i])


    #AIC and convergence
    #Get AIC and convergence
    temp.aic <- cbind(temp.survey, temp.species, temp.name, 'VAST', vast_knots[i], vast_bias.correct[i])#,
                      # specs$vast_name[i])

    aic.vect <- append(aic.vect, vast_est.output[[i]]$Opt$AIC)
    converge.vect <- append(converge.vect, vast_est.output[[i]]$Opt$converge)

    #Combine to larger lists
    vast.list <- rbind(vast.list, temp.list)
    aic.list <- rbind(aic.list, temp.aic)

}#next model configuration i


#Add Design-based estimates
db.list <- NULL
s <- 1
for(s in 1:n.species) {
  #Species Information
  temp.species <- species.list$name[s]
  temp.survey <- species.list$survey[s]
  temp.name <- paste0(temp.survey,": ",temp.species)

  #Determine Survey years (Currently only GOA and AI)
  if(temp.survey=='GOA') {
    temp.yrs <- goa.yrs
  }else {
    temp.yrs <- ai.yrs
  }

  #Get design-based estimate
  db_est <- calc_design_based_index(species.codes=species.list$species.code[s], survey=temp.survey)

  #Determine which are survey years
  survey.year <- db_est$YEAR %in% temp.yrs  #All TRUE

  temp.name <- paste0(temp.survey,": ",temp.species)
  temp.list <- cbind(db_est[,c(1,2,4,5)], temp.survey, temp.species, temp.name, "Design-based", 'Design-based','Design-based', survey.year)
  #Add it
  db.list <- rbind(db.list, temp.list)
}#next s
db.df <- data.frame(db.list)
names(db.df) <- c('Year','Biomass','SD','CV','Survey','Species', 'Name','Model','Knots','bias.correct','SurveyYear')


#Add names
vast.df <- data.frame(vast.list)
names(vast.df) <- c('Year','Biomass','SD','CV','Survey','Species', 'Name','Model','Knots','bias.correct','SurveyYear')

aic.df <- data.frame(aic.list, aic.vect, converge.vect)
names(aic.df) <- c('Survey','Species','Name','Model','Knots','bias.correct','AIC','Converge')
aic.df$Converge <- as.factor(aic.df$Converge)

#Combine the lists
survey.df <- rbind(vast.df,db.df)
# survey.df$Knots <- factor(survey.df$Knots, ordered=TRUE)

#Add 95% CI
survey.df$low.95 <- survey.df$Biomass - 1.96*survey.df$SD
survey.df$up.95 <- survey.df$Biomass + 1.96*survey.df$SD

# #=====================================================
#PLOT IT OUT
# c('Walleye pollock','Pacific cod')
# -which(output.df$Species %in% c('Walleye pollock','Pacific cod',rockfish))


###### GOA Rockfish #####
# rockfish <- c('Pacific ocean perch','Northern rockfish','Harlequin rockfish')
# survey <- 'GOA'
# 
# plot.list <- survey.df[survey.df$Survey==survey &
#                          survey.df$Species %in% rockfish &
#                          survey.df$SurveyYear==TRUE,]
# yrs.surv <- sort(unique(plot.list$Year[plot.list$Model=='Design-based']))
# plot.list <- plot.list[plot.list$Year %in% yrs.surv,]

#Remove 2001 from design-based results because of incomplete sampling
# if(survey=='GOA') { plot.list <- plot.list[-which(plot.list$Year==2001 & plot.list$Model=='Design-based'),] }


#===============================================
# g <- ggplot(plot.list[plot.list$Model=='VAST',], aes(x=Year, y=Biomass/1e3, 
#                                                        color=Knots, lty=bias.correct, ymin=0)) +
#   theme_gray() +
#   # theme_economist() +
#   theme(legend.position='right') +
#   geom_line() +
#   facet_wrap(~Species, scales='free', ncol=2) +
#   labs(list(y='Biomass (thousands of metric tonnes)')) +
#   # ggtitle('Survey:', subtitle='Gulf of Alaska') +
#   ggtitle(paste(survey, 'Survey')) +
#   scale_color_viridis(discrete=TRUE) +
#   # scale_color_brewer(type='seq', palette=1)
#   geom_line(data=plot.list[plot.list$Model=='Design-based',], color='black') +
#   geom_point(data=plot.list[plot.list$Model=='Design-based',], show.legend=FALSE, colour='black')
# 
# g

#========================================================================

# plot.list.v <- data.frame(plot.list[plot.list$Model!='Design-based',])
# plot.list.db <-  plot.list %>% subset(select=-bias.correct, Model=='Design-based')

# g <- ggplot(plot.list[plot.list$Model!='Design-based',],
#               aes(x=Year, y=Biomass/1e3, color=bias.correct, fill=bias.correct, ymin=0)) +
#   theme_bw() +
#   theme(legend.position='right') +
#   geom_ribbon(aes(ymin=low.95/1e3, ymax=up.95/1e3), alpha=0.25, lwd=1e-6) +
#   geom_line() +
#   facet_grid(Species ~ Knots, scales='free') +
#   labs(list(y='Biomass (thousands of metric tonnes)')) +
#   ggtitle(paste(survey, 'Survey')) +
#   scale_color_colorblind() +
#   scale_fill_colorblind() +
#   geom_line(data=plot.list[plot.list$Model=='Design-based',], show.legend=FALSE) +
#   geom_point(data=plot.list[plot.list$Model=='Design-based',], show.legend=FALSE)
# g

# names(plot.list)

# temp <- plot.list %>% subset(select=-Knots)

#====================================
#GOA: Rockfish

rockfish <- c('Pacific ocean perch','Northern rockfish','Harlequin rockfish')
survey <- 'GOA'

plot.list <- survey.df[survey.df$Survey==survey &
                         survey.df$Species %in% rockfish &
                         survey.df$SurveyYear==TRUE,]
#Plot
g <- ggplot(plot.list[plot.list$Model!='Design-based',],
            aes(x=Year, y=Biomass/1e3, color=Knots, fill=Knots, lty=bias.correct, ymin=0)) +
        theme_bw() +
        theme(legend.position='right') +
        geom_line() +
        facet_wrap(~Species, scale='free') +
        labs(list(y='Biomass (thousands of metric tonnes)')) +
        ggtitle(paste(survey, 'Survey')) +
        scale_color_colorblind()
# g
ggsave(paste0(working.dir,"/GOA Rockfish_1.png"), plot=g, height=height, width=width, units='in', dpi=600)

g2 <- ggplot(plot.list[plot.list$Model!='Design-based',],
            aes(x=Year, y=Biomass/1e3, color=bias.correct, fill=bias.correct, ymin=0)) +
        theme_bw() +
        theme(legend.position='right') +
        geom_ribbon(aes(ymin=low.95/1e3, ymax=up.95/1e3), alpha=0.25, lwd=1e-6) +
        geom_line() +
        facet_grid(Species ~ Knots, scales='free') +
        labs(list(y='Biomass (thousands of metric tonnes)')) +
        ggtitle(paste(survey, 'Survey')) +
        scale_color_colorblind() +
        scale_fill_colorblind() +
        geom_line(data=plot.list[plot.list$Model=='Design-based',], show.legend=FALSE) +
        geom_point(data=plot.list[plot.list$Model=='Design-based',], show.legend=FALSE)
# g2
ggsave(paste0(working.dir,"/GOA Rockfish_2.png"), plot=g2, height=height, width=width, units='in', dpi=600)

#================================
###### GOA: Pollock and Cod #####
survey <- 'GOA'
temp.species <- c('Walleye pollock','Pacific cod')

plot.list <- survey.df[survey.df$Survey==survey &
                         survey.df$Species %in% temp.species &
                         survey.df$SurveyYear==TRUE,]

#Plot
g <- ggplot(plot.list[plot.list$Model!='Design-based',],
            aes(x=Year, y=Biomass/1e3, color=Knots, fill=Knots, lty=bias.correct, ymin=0)) +
  theme_bw() +
  theme(legend.position='right') +
  geom_line() +
  facet_wrap(~Species, scale='free') +
  labs(list(y='Biomass (thousands of metric tonnes)')) +
  ggtitle(paste(survey, 'Survey')) +
  scale_color_colorblind()
# g
ggsave(paste0(working.dir,"/GOA Pollock Cod_1.png"), plot=g, height=height, width=width, units='in', dpi=600)

g2 <- ggplot(plot.list[plot.list$Model!='Design-based',],
             aes(x=Year, y=Biomass/1e3, color=bias.correct, fill=bias.correct, ymin=0)) +
  theme_bw() +
  theme(legend.position='right') +
  geom_ribbon(aes(ymin=low.95/1e3, ymax=up.95/1e3), alpha=0.25, lwd=1e-6) +
  geom_line() +
  facet_grid(Species ~ Knots, scales='free') +
  labs(list(y='Biomass (thousands of metric tonnes)')) +
  ggtitle(paste(survey, 'Survey')) +
  scale_color_colorblind() +
  scale_fill_colorblind() +
  geom_line(data=plot.list[plot.list$Model=='Design-based',], show.legend=FALSE) +
  geom_point(data=plot.list[plot.list$Model=='Design-based',], show.legend=FALSE)
# g2
ggsave(paste0(working.dir,"/GOA Pollock Cod_2.png"), plot=g2, height=height, width=width, units='in', dpi=600)

#================================
###### GOA: Others #####
survey <- 'GOA'

temp.species <- species.list$name[species.list$survey==survey &
                                    !species.list$name %in% c('Walleye pollock', 'Pacific cod',
                                                              'Pacific ocean perch', 'Northern rockfish',
                                                              'Harlequin rockfish') ]

plot.list <- survey.df[survey.df$Survey==survey &
                         survey.df$Species %in% temp.species &
                         survey.df$SurveyYear==TRUE,]

#Plot
g <- ggplot(plot.list[plot.list$Model!='Design-based',],
            aes(x=Year, y=Biomass/1e3, color=Knots, fill=Knots, lty=bias.correct, ymin=0)) +
  theme_bw() +
  theme(legend.position='right') +
  geom_line() +
  facet_wrap(~Species, scale='free') +
  labs(list(y='Biomass (thousands of metric tonnes)')) +
  ggtitle(paste(survey, 'Survey')) +
  scale_color_colorblind()
# g
ggsave(paste0(working.dir,"/GOA Others_1.png"), plot=g, height=height, width=width, units='in', dpi=600)

g2 <- ggplot(plot.list[plot.list$Model!='Design-based',],
             aes(x=Year, y=Biomass/1e3, color=bias.correct, fill=bias.correct, ymin=0)) +
  theme_bw() +
  theme(legend.position='right') +
  geom_ribbon(aes(ymin=low.95/1e3, ymax=up.95/1e3), alpha=0.25, lwd=1e-6) +
  geom_line() +
  facet_grid(Species ~ Knots, scales='free') +
  labs(list(y='Biomass (thousands of metric tonnes)')) +
  ggtitle(paste(survey, 'Survey')) +
  scale_color_colorblind() +
  scale_fill_colorblind() +
  geom_line(data=plot.list[plot.list$Model=='Design-based',], show.legend=FALSE) +
  geom_point(data=plot.list[plot.list$Model=='Design-based',], show.legend=FALSE)
# g2
ggsave(paste0(working.dir,"/GOA Others_2.png"), plot=g2, height=height, width=width, units='in', dpi=600)

#================================
###### AI: All #####
survey <- 'AI'

plot.list <- survey.df[survey.df$Survey==survey &
                         survey.df$SurveyYear==TRUE,]
#Plot
g <- ggplot(plot.list[plot.list$Model!='Design-based',],
            aes(x=Year, y=Biomass/1e3, color=Knots, fill=Knots, lty=bias.correct, ymin=0)) +
  theme_bw() +
  theme(legend.position='right') +
  geom_line() +
  facet_wrap(~Species, scale='free') +
  labs(list(y='Biomass (thousands of metric tonnes)')) +
  ggtitle(paste(survey, 'Survey')) +
  scale_color_colorblind()
# g
ggsave(paste0(working.dir,"/AI All_1.png"), plot=g, height=height, width=width, units='in', dpi=600)

g2 <- ggplot(plot.list[plot.list$Model!='Design-based',],
             aes(x=Year, y=Biomass/1e3, color=bias.correct, fill=bias.correct, ymin=0)) +
  theme_bw() +
  theme(legend.position='right') +
  geom_ribbon(aes(ymin=low.95/1e3, ymax=up.95/1e3), alpha=0.25, lwd=1e-6) +
  geom_line() +
  facet_grid(Species ~ Knots, scales='free') +
  labs(list(y='Biomass (thousands of metric tonnes)')) +
  ggtitle(paste(survey, 'Survey')) +
  scale_color_colorblind() +
  scale_fill_colorblind() +
  geom_line(data=plot.list[plot.list$Model=='Design-based',], show.legend=FALSE) +
  geom_point(data=plot.list[plot.list$Model=='Design-based',], show.legend=FALSE)
# g2
ggsave(paste0(working.dir,"/AI All_2.png"), plot=g2, height=height, width=width, units='in', dpi=600)

#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Compare VAST estimates with delta-GLMM
#
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 9.13.17
#
#Purpose: Evaluate whether differences between design-based and VAST (model-based) indices and uncertainty, is a function of the delta model structure. 
#
#
#==================================================================================================
#NOTES:

#==================================================================================================
#TIMING:
# [1] "### START: Wed Nov 29 15:35:28 2017"
# [1] "### END: Wed Nov 29 20:42:35 2017"



##==================================================================================================
require(parallel)
require(snowfall)
require(tidyverse)
require(ggthemes)
require(VAST)
require(TMB)
require(viridis)

source("R/calc-design-based-index.r")
source("R/create-VAST-input.r")
source("R/create-Data-Geostat.r")
source("R/load-RACE-data.r")
source("R/cleanup-VAST-file.r")
source("R/get-VAST-index.r")


home.dir <- getwd()
#Create working directory
working.dir <- paste0(home.dir, "/examples/Test_DeltaModel")


#Determine species list
species.list <- read.csv("data/eval_species_list.csv", stringsAsFactors=FALSE)

#Limit species included
species.list <- species.list[species.list$include=='Y',]
#Remove EBS_SHELF Arrowtooth
species.list <- species.list[species.list$survey!='EBS_SHELF',]

n.species <- nrow(species.list)

#Create
species.series <- c(1:n.species)

#=======================================================================
##### CONTROL SECTION #####
#Number of cores to use
n.cores <- detectCores()-1

#Boolean for running estimation models
do.estim <- FALSE

#Trial Knot Numbers
trial.knots <- c(100,500,1000)
n.trial.knots <- length(trial.knots)

#Trial RANDOM EFECTS SPECIFICATIONS specifications
trial.RE.names <- c('None','Spatial_Only','SpatioTemporal_Only','Full')
n.trial.RE <- length(trial.RE.names)
trial.RE <- matrix(c(0,0,0,0,
                     1,0,1,0,
                     0,1,0,1,
                     1,1,1,1), nrow=4, ncol=4, byrow=TRUE)

#Boolean for bias correction
bias.correct <- FALSE

#Plotting Sizes
height <- 6
width <- 9
#=======================================================================
##### Run VAST model  #####
Version <- "VAST_v2_8_0"
lat_lon.def <- "start"

#SPATIAL SETTINGS
Method = c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km = 25
# n_x = c(100, 250, 500, 1000, 2000)[2] # Number of stations
Kmeans_Config = list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("All_areas"))

#MODEL SETTINGS
# FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

ObsModel = c(1, 0) #Lognormal

#SPECIFY OUTPUTS
Options = c(SD_site_density = 0, SD_site_logdensity = 0,
            Calculate_Range = 1, Calculate_evenness = 0, Calculate_effective_area = 1,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)

#Output Directory Name
output.dir <- paste0(working.dir,"/Output")
dir.create(output.dir)


#=======================================================================
##### WRAPPER FUNCTION FOR RUNNING IN PARALLEL #####

s <- 1
# for(s in 1:n.species) {
wrapper_fxn <- function(s, n_x, FieldConfig) {
  
  #Define file for analyses
  DateFile <- paste0(trial.dir,"/",species.list$survey[s],"_",species.list$name[s],"/")
  
  dir.create(DateFile)
  
  #Define species.codes
  species.codes <- species.list$species.code[s]
  survey <- species.list$survey[s]
  
  #=======================================================================
  ##### READ IN DATA AND BUILD VAST INPUT #####
  #  NOTE: this will create the DateFile
  VAST_input <- create_VAST_input(species.codes=species.codes, combineSpecies=FALSE,
                                  lat_lon.def=lat_lon.def, save.Record=FALSE,
                                  Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                  Kmeans_Config=Kmeans_Config,
                                  strata.limits=strata.limits, survey=survey,
                                  DateFile=DateFile,
                                  FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                  OverdispersionConfig=OverdispersionConfig,
                                  ObsModel=ObsModel, Options=Options, Version=Version)
  
  
  
  #Unpack
  TmbData <- VAST_input$TmbData
  Data_Geostat <- VAST_input$Data_Geostat
  Spatial_List <- VAST_input$Spatial_List
  Extrapolation_List <- VAST_input$Extrapolation_List
  
  
  #=======================================================================
  ##### RUN VAST #####
  
  
  
  #Build TMB Object
  #  Compilation may take some time
  TmbList <- VAST::Build_TMB_Fn(TmbData = TmbData, RunDir = DateFile,
                                Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                                Method = Method)
  Obj <- TmbList[["Obj"]]
  
  
  Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
                             upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
                             bias.correct = bias.correct)
  #Save output
  Report = Obj$report()
  # Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
  # save(Save, file=paste0(DateFile,"Save.RData"))
  
  #Calculate index values
  # TmbData = TmbData, Sdreport = Opt[["SD"]]
  vast_est <- get_VAST_index(TmbData=TmbData, Sdreport=Opt[["SD"]], bias.correct=bias.correct, Data_Geostat=Data_Geostat)
  #========================================================================
  ##### DIAGNOSTIC AND PREDICTION PLOTS #####
  # plot_VAST_output(Opt, Report, DateFile, survey, TmbData, Data_Geostat, Extrapolation_List, Spatial_List)
  
  #========================================================================
  ##### CLEANUP VAST OUTPUT #####
  # cleanup_VAST_file(DateFile=DateFile, Version=Version) #No longer necessary as we are deleting everything at the end
  
  rm("VAST_input", "TmbData", "Data_Geostat", "Spatial_List", "Extrapolation_List", "TmbList", "Obj")#, "Save")#, "Opt", "Report")
  
  #========================================================================
  setwd(home.dir)
  ##### RETURN SECTION #####
  out <- NULL
  out$vast_est <- vast_est
  out$Opt <- Opt
  return(out)
} 


#=======================================================================
##### Loop Through Trial Knots  #####
vast_est.output <- vector('list', length=(n.trial.knots * n.trial.RE))
vast_knots <- vector(length=(n.trial.knots * n.trial.RE))
vast_RE <- vector(length=(n.trial.knots * n.trial.RE))

if(do.estim==TRUE) {
  
  
  time.1 <- date()
  
  #Counter for knots by rho
  counter <- 1
  
  t <- 1
  for(t in 1:n.trial.knots) {
    print(paste('## Trial Knot Number',t,'of',n.trial.knots))
    print(paste('# Trial Knots:',trial.knots[t]))
    #Specify trial observation model
    
    #Specify knots
    n_x <- trial.knots[t]
    
    r <- 1
    for(r in 1:n.trial.RE) {
      print(paste('#### Trial RE Scenario Number',r,'of',n.trial.RE))
      
      #Specify Spatial and Spatio-temporal RE
      FieldConfig <- trial.RE[r,]
      names(FieldConfig) <- c('Omega1','Epsilon1','Omega2','Epsilon2')
      
      #Record
      vast_RE[counter] <- trial.RE.names[r]
      vast_knots[counter] <- n_x
      
      #Setup File
      trial.dir <- paste0(working.dir,"/",n_x,"_bias.corr_",bias.correct)
      dir.create(trial.dir)
      trial.dir <- paste0(trial.dir, "/RE_",vast_RE[counter])
      dir.create(trial.dir)
      
      
      #=======================================================================
      ##### TEST WRAPPER FUNCTION #####
      # wrapper_fxn(s=1, n_x=n_x, FieldConfig=FieldConfig)
      
      #=======================================================================
      ##### SNOWFALL CODE FOR PARALLEL #####
      sfInit(parallel=TRUE, cpus=n.cores, type='SOCK')
      sfExportAll() #Exportas all global variables to cores
      sfLibrary(TMB)  #Loads a package on all nodes
      sfLibrary(VAST)
      output <- sfLapply(species.series, fun=wrapper_fxn, n_x=n_x, FieldConfig=FieldConfig)
      sfStop()

      vast_est.output[[counter]] <- output

      #For Update
      # output <- vast_est.output[[counter]]
      # save(output, file=paste0(output.dir, "/testVAST_output_",counter,".RData"), compression_level=9)
      saveRDS(output, file=paste0(output.dir, "/VAST_output_",counter,".rds"))
      #Real
      
      
      counter <- counter+1
    }#next r
  }#next t
  
  #Create output directory
  #Also save specifications
  vast_specs <- data.frame(vast_knots, vast_RE)
  write.csv(vast_specs, file=paste0(output.dir,"/vast_specs.csv"))
  
  #=======================================================================
  ##### DELETE UNNECESSARY FILE STRUCTURE #####
  #Must reset working directory
  setwd(working.dir)
  t <- 1
  for(t in 1:n.trial.knots) {
    unlink(paste0(working.dir,"/",trial.knots[t],"_bias.corr_",bias.correct), recursive=TRUE)
  }#next t
  
  time.2 <- date()
  
  print(paste('### START:', time.1))
  print(paste('### END:', time.2))
  
}else {
  
  
  #Old
  # load(paste0(output.dir,"/vast_est.output.RData"))
  
  specs <- read.csv(paste0(output.dir,"/vast_specs.csv"), header=TRUE, stringsAsFactors=FALSE)
  n.specs <- nrow(specs)
  
  for(i in 1:n.specs) {
    print(i)
    vast_est.output[[i]] <- readRDS(file=paste0(output.dir, "/VAST_output_",i,".rds"))
    vast_knots[i] <- specs$vast_knots[i]
    vast_RE[i] <- specs$vast_RE[i]
  }#next i
}

#=====================================================
# Gather Data
vast.list <- NULL
aic.list <- NULL
aic.vect <- vector(length=0)
converge.vect <- vector(length=0)

#Load dataset to determine which years to include
goa.yrs <- sort(unique(load_RACE_data(species.codes=30420,
                                      combineSpecies=FALSE, survey='GOA')$Year))
ai.yrs <- sort(unique(load_RACE_data(species.codes=30420,
                                     combineSpecies=FALSE, survey='AI')$Year))


i <- 1
for(i in 1:n.specs) {
  s <- 1
  for(s in 1:n.species) {
    #Species Information
    temp.species <- species.list$name[s]
    temp.survey <- species.list$survey[s]
    temp.name <- paste0(temp.survey,": ",temp.species)
    
    #Determine Survey years (Currently only GOA and AI)
    if(temp.survey=='GOA') {
      temp.yrs <- goa.yrs
    }else {
      temp.yrs <- ai.yrs
    }
    
    #Get VAST model index
    temp.list <- vast_est.output[[i]][[s]]$vast_est[c(1,4,6)]
    
    #Calculate CV
    CV <- temp.list$SD_mt/temp.list$Estimate_metric_tons
    
    #Determine which are survey years
    survey.year <- temp.list$Year %in% temp.yrs
    
    #Bind it
    temp.list <- cbind(temp.list, CV, temp.survey, temp.species, temp.name, 'VAST', vast_knots[i], vast_RE[i], survey.year)
    
    
    #AIC and convergence
    #Get AIC and convergence
    temp.aic <- cbind(temp.survey, temp.species, temp.name, 'VAST', vast_knots[i], vast_RE[i])
    
    aic.vect <- append(aic.vect, vast_est.output[[i]][[s]]$Opt$AIC)
    converge.vect <- append(converge.vect, vast_est.output[[i]][[s]]$Opt$converge)
    
    #Combine to larger lists
    vast.list <- rbind(vast.list, temp.list)
    aic.list <- rbind(aic.list, temp.aic) 
    
    
  }#Next species
  
}#next model configuration i


#Add Design-based estimates
db.list <- NULL
s <- 1
for(s in 1:n.species) {
  #Species Information
  temp.species <- species.list$name[s]
  temp.survey <- species.list$survey[s]
  temp.name <- paste0(temp.survey,": ",temp.species)
  
  #Determine Survey years (Currently only GOA and AI)
  if(temp.survey=='GOA') {
    temp.yrs <- goa.yrs
  }else {
    temp.yrs <- ai.yrs
  }
  
  #Get design-based estimate
  db_est <- calc_design_based_index(species.codes=species.list$species.code[s], survey=temp.survey)
  
  #Determine which are survey years
  survey.year <- db_est$YEAR %in% temp.yrs  #All TRUE
  
  temp.name <- paste0(temp.survey,": ",temp.species)
  temp.list <- cbind(db_est[,c(1,2,4,5)], temp.survey, temp.species, temp.name, "Design-based", 'Design-based','Design-based', survey.year)
  #Add it
  db.list <- rbind(db.list, temp.list)
}#next s
db.df <- data.frame(db.list)
names(db.df) <- c('Year','Biomass','SD','CV','Survey','Species', 'Name','Model','Knots','RE','SurveyYear')


#Add names
vast.df <- data.frame(vast.list)
names(vast.df) <- c('Year','Biomass','SD','CV','Survey','Species', 'Name','Model','Knots','RE','SurveyYear')

aic.df <- data.frame(aic.list, aic.vect, converge.vect)
names(aic.df) <- c('Survey','Species','Name','Model','Knots','RE','AIC','Converge')
aic.df$Converge <- as.factor(aic.df$Converge)

#Combine the lists
#  And order the factors
survey.df <- rbind(vast.df,db.df)
survey.df$Knots <- factor(survey.df$Knots, ordered=TRUE, levels=c(trial.knots, 'Design-based'))
# survey.df$Model <- factor(survey.df$Model, ordered=TRUE, levels=c('VAST','Design-based'))# levels=c('Design-based', 'VAST'))

survey.df$Biomass <- survey.df$Biomass/1e3

#=====================================================
#PLOT IT OUT
# c('Walleye pollock','Pacific cod')
# -which(output.df$Species %in% c('Walleye pollock','Pacific cod',rockfish))
lty.db <- 2 #Line type for design.based estimator
#================================
###### GOA: Rockfish #####
survey <- 'GOA'
temp.species <- c('Pacific ocean perch','Northern rockfish','Harlequin rockfish')

plot.list <- survey.df[survey.df$Survey==survey &
                         survey.df$Species %in% temp.species &
                         survey.df$SurveyYear==TRUE,]

#Remove 2001 from design-based results because of incomplete sampling
# if(survey=='GOA') { plot.list <- plot.list[-which(plot.list$Year==2001 & plot.list$Model=='Design-based'),] }

# Species ~ RE
g <- ggplot(plot.list[plot.list$Model!='Design-based',], 
              aes(x=Year, y=Biomass, color=Knots, ymin=0)) +
       theme_gray() +
       theme(legend.position='right') +
       # geom_point() +
       geom_line() +
    
       geom_line(data=plot.list[plot.list$Model=='Design-based',
                             -which(names(plot.list) %in% c('RE')),],
                   color='black', lty=lty.db) +
       geom_point(data=plot.list[plot.list$Model=='Design-based',
                              -which(names(plot.list) %in% c('RE')),], show.legend=FALSE,
                   colour='black') +
  
      facet_grid(Species ~ RE, scales='free') +
      labs(list(y='Biomass (thousands of metric tonnes)')) +
      ggtitle(paste(survey, 'Survey')) +
      # scale_color_viridis(discrete=TRUE) 
      scale_color_brewer(type='sequential', palette='Set1')
g

ggsave(paste0(working.dir,"/GOA Rockfish Compare.png"), plot=g, height=height, width=width, units='in', dpi=600)


#================================
###### GOA: Pollock and Cod #####
survey <- 'GOA'
temp.species <- c('Walleye pollock','Pacific cod')

plot.list <- survey.df[survey.df$Survey==survey &
                         survey.df$Species %in% temp.species &
                         survey.df$SurveyYear==TRUE,]

#Remove 2001 from design-based results because of incomplete sampling
# if(survey=='GOA') { plot.list <- plot.list[-which(plot.list$Year==2001 & plot.list$Model=='Design-based'),] }

# Species ~ RE
g <- ggplot(plot.list[plot.list$Model!='Design-based',], 
            aes(x=Year, y=Biomass, color=Knots, ymin=0)) +
  theme_gray() +
  theme(legend.position='right') +
  # geom_point() +
  geom_line() +
  
  geom_line(data=plot.list[plot.list$Model=='Design-based',
                           -which(names(plot.list) %in% c('RE')),],
            color='black', lty=lty.db) +
  geom_point(data=plot.list[plot.list$Model=='Design-based',
                            -which(names(plot.list) %in% c('RE')),], show.legend=FALSE,
             colour='black') +
  
  facet_grid(Species ~ RE, scales='free') +
  labs(list(y='Biomass (thousands of metric tonnes)')) +
  ggtitle(paste(survey, 'Survey')) +
  # scale_color_viridis(discrete=TRUE) 
  scale_color_brewer(type='sequential', palette='Set1')
g

ggsave(paste0(working.dir,"/GOA Pollock and Cod Compare.png"), plot=g, height=height, width=width, units='in', dpi=600)


#================================
###### GOA: Others #####
survey <- 'GOA'

temp.species <- species.list$name[species.list$survey==survey &
                                    !species.list$name %in% c('Walleye pollock', 'Pacific cod',
                                                                'Pacific ocean perch', 'Northern rockfish',
                                                                'Harlequin rockfish') ]

plot.list <- survey.df[survey.df$Survey==survey &
                         survey.df$Species %in% temp.species &
                         survey.df$SurveyYear==TRUE,]


#Remove 2001 from design-based results because of incomplete sampling
# if(survey=='GOA') { plot.list <- plot.list[-which(plot.list$Year==2001 & plot.list$Model=='Design-based'),] }

# Species ~ RE
g <- ggplot(plot.list[plot.list$Model!='Design-based',], 
            aes(x=Year, y=Biomass, color=Knots, ymin=0)) +
  theme_gray() +
  theme(legend.position='right') +
  # geom_point() +
  geom_line() +
  
  geom_line(data=plot.list[plot.list$Model=='Design-based',
                           -which(names(plot.list) %in% c('RE')),],
            color='black', lty=lty.db) +
  geom_point(data=plot.list[plot.list$Model=='Design-based',
                            -which(names(plot.list) %in% c('RE')),], show.legend=FALSE,
             colour='black') +
  
  facet_grid(Species ~ RE, scales='free') +
  labs(list(y='Biomass (thousands of metric tonnes)')) +
  ggtitle(paste(survey, 'Survey')) +
  # scale_color_viridis(discrete=TRUE) 
  scale_color_brewer(type='sequential', palette='Set1')
g

ggsave(paste0(working.dir,"/GOA Others.png"), plot=g, height=height, width=width, units='in', dpi=600)


#================================
###### AI: All #####
survey <- 'AI'

plot.list <- survey.df[survey.df$Survey==survey &
                         survey.df$SurveyYear==TRUE,]


#Remove 2001 from design-based results because of incomplete sampling
# if(survey=='GOA') { plot.list <- plot.list[-which(plot.list$Year==2001 & plot.list$Model=='Design-based'),] }

# Species ~ RE
g <- ggplot(plot.list[plot.list$Model!='Design-based',], 
            aes(x=Year, y=Biomass, color=Knots, ymin=0)) +
  theme_gray() +
  theme(legend.position='right') +
  # geom_point() +
  geom_line() +
  
  geom_line(data=plot.list[plot.list$Model=='Design-based',
                           -which(names(plot.list) %in% c('RE')),],
            color='black', lty=lty.db) +
  geom_point(data=plot.list[plot.list$Model=='Design-based',
                            -which(names(plot.list) %in% c('RE')),], show.legend=FALSE,
             colour='black') +
  
  facet_grid(Species ~ RE, scales='free') +
  labs(list(y='Biomass (thousands of metric tonnes)')) +
  ggtitle(paste(survey, 'Survey')) +
  # scale_color_viridis(discrete=TRUE) 
  scale_color_brewer(type='sequential', palette='Set1')
g

ggsave(paste0(working.dir,"/AI All Species.png"), plot=g, height=height, width=width, units='in', dpi=600)



#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Print Design-Based Estimates to Ensure Correct
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 4.6.17
#
#Purpose: To save design-based estimates to .csv for sharing with assessment authors for confirmation.. OK
#
#
#
#==================================================================================================
#NOTES:
#
#
#==================================================================================================
require(xlsx)
source("R/calc-design-based-index.r")



#Determine species list
species.list <- read.csv("data/eval_species_list.csv", stringsAsFactors=FALSE)
#Limit to those included
species.list <- species.list[species.list$include=='Y',]

n.species <- nrow(species.list)
species.series <- c(1:n.species)



s <- 1
for(s in 1:n.species) {
  print(paste(s, 'of', n.species))
  species.codes <- species.list$species.code[s]
  survey <- species.list$survey[s]
  spec.name <- species.list$name[s]
  
  db_est <- calc_design_based_index(species.codes=species.codes, survey=survey)
  write.xlsx(db_est, file=paste0(getwd(), "/examples/Test_DesignBased_Estimator/Design Based Estimates.xlsx"),
                                   sheetName=paste0(survey,"_",spec.name),
               append=ifelse(s==1,FALSE,TRUE))
  
}
  


#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Generating design-based estimates for comparison
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 3.30.17
#
#Purpose: To explore efficient estimation of design-based index for RACE bottom trawl data.
#
#
#
#==================================================================================================
#NOTES:
#
#  a) Should be implemented as a function which calls RACE data read fxn.
#  b) Should return point estimate and SD by year.
#  c) Selection of strata should be clear


#TIMINGS:
# [1] "START: Thu May 04 11:28:52 2017" No EBS stocks
# [1] "END: Thu May 04 11:44:49 2017"

#==================================================================================================
require(VAST)
require(TMB)

source("R/calc-design-based-index.r")
source("R/create-VAST-input.r")
source("R/create-Data-Geostat.r")
source("R/load-RACE-data.r")

source("R/cleanup-VAST-file.r")
source("R/get-VAST-index.r")
#=======================================================================
##### CONTROL SECTION #####


working.dir <- getwd()


#NEW: Run all species... to lazy for parallel right now.


#Determine species list
species.list <- read.csv("data/eval_species_list.csv", stringsAsFactors=FALSE)
#Limit to those included
species.list <- species.list[species.list$include=='Y',]

n.species <- nrow(species.list)
species.series <- c(1:n.species)



#=======================================================================
##### Run VAST model  #####
lat_lon.def <- "start" 

#SPATIAL SETTINGS
Method = c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km = 25
n_x =5e3#c(100, 250, 500, 1000, 2000)[1] # Number of stations
Kmeans_Config = list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("All_areas"))


#DERIVED OBJECTS
Version <-  "VAST_v2_4_0"

#Bias correction
bias.correct <- FALSE

#MODEL SETTINGS
FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

ObsModel = c(1, 0) #Lognormal

#SPECIFY OUTPUTS
Options = c(SD_site_density = 0, SD_site_logdensity = 0,
            Calculate_Range = 1, Calculate_evenness = 0, Calculate_effective_area = 1,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)

start.time <- date()

s <- 1
for(s in 1:n.species) {
  print(paste(s, 'of', n.species))
  species.codes <- species.list$species.code[s]
  survey <- species.list$survey[s]
  spec.name <- species.list$name[s]
  
  #=======================================================================
  ##### Calculate design-based estimate  #####
  db_est <- calc_design_based_index(species.codes=species.codes, survey=survey)
  
  ###########################
  DateFile=paste0(getwd(),'/examples/Test_DesignBased_Estimator/')
  #Create input
  VAST_input <- create_VAST_input(species.codes=species.codes, lat_lon.def=lat_lon.def, save.Record=TRUE,
                                  Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                  Kmeans_Config=Kmeans_Config,
                                  strata.limits=NULL, survey=survey,
                                  DateFile=DateFile,
                                  FieldConfig, RhoConfig, OverdispersionConfig,
                                  ObsModel, Options)

  #Unpack
  TmbData <- VAST_input$TmbData
  Data_Geostat <- VAST_input$Data_Geostat
  Spatial_List <- VAST_input$Spatial_List
  Extrapolation_List <- VAST_input$Extrapolation_List

  unique(Extrapolation_List$a_el)
  unique(Extrapolation_List$Data_Extrap$Include)
  unique(Extrapolation_List$Data_Extrap$Area_in_survey_km2)
  #Build TMB Object
  #  Compilation may take some time
  TmbList <- VAST::Build_TMB_Fn(TmbData = TmbData, RunDir = DateFile,
                                Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                                Method = Method)
  Obj <- TmbList[["Obj"]]


  Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
                             upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
                             bias.correct = bias.correct)

  #=======================================================================
  ##### Plot comparison of design-based and model-based estimates #####

  Year_Set <- seq(min(Data_Geostat[,'Year']),max(Data_Geostat[,'Year']))
  Years2Include <- which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))
  years <- Year_Set[Years2Include]

  vast_est <- get_VAST_index(TmbData=TmbData, Sdreport=Opt[["SD"]], bias.correct=bias.correct, Data_Geostat=Data_Geostat)
  #Limit to years with observations
  vast_est <- vast_est[Years2Include,]
  
  pdf(paste0(DateFile, survey, "_", spec.name, "_", TmbData$n_x, " knots.pdf"), height=8, width=8)
  
  par(mfrow=c(1,1), oma=c(0,0,0,0), mar=c(4,4,3,1))
  
  
  y.lim <- c(0, max(vast_est$Estimate_metric_tons+2*vast_est$SD_mt,
                    db_est$Biomass+2*db_est$SD))
  x.lim <- c(min(Year_Set), max(Year_Set))

  #Plot it out
  plot(x=NULL, y=NULL, xlim=x.lim, ylim=y.lim, xlab='Year', ylab='Estimator',
         main=paste0(spec.name, ' n=', TmbData$n_x, ' knots'))
  grid(col='black')

  legend('bottomleft', legend=c('Design-based', 'VAST'), fill=c('blue', 'red'), ncol=2, bg='white')

  #Design-based
  polygon(x=c(years, rev(years)), y=c(db_est$Biomass+2*db_est$SD, rev(db_est$Biomass-2*db_est$SD)),
          border=FALSE, col=rgb(0,0,1, alpha=0.25))

  polygon(x=c(years, rev(years)), y=c(db_est$Biomass+1*db_est$SD, rev(db_est$Biomass-1*db_est$SD)),
          border=FALSE, col=rgb(0,0,1, alpha=0.25))

  lines(x=years, y=db_est$Biomass, lwd=2, col='blue')
  points(x=years, y=db_est$Biomass, pch=21, bg='blue')

  #VAST
  polygon(x=c(years, rev(years)), y=c(vast_est$Estimate_metric_tons+2*vast_est$SD_mt,
                                      rev(vast_est$Estimate_metric_tons-2*vast_est$SD_mt)),
          border=FALSE, col=rgb(1,0,0, alpha=0.25))

  polygon(x=c(years, rev(years)), y=c(vast_est$Estimate_metric_tons+1*vast_est$SD_mt, 
                                      rev(vast_est$Estimate_metric_tons-1*vast_est$SD_mt)),
          border=FALSE, col=rgb(1,0,0, alpha=0.25))

  lines(x=years, y=vast_est$Estimate_metric_tons, lwd=2, col='red')
  points(x=years, y=vast_est$Estimate_metric_tons, pch=21, bg='red')

  dev.off()
  
  #Cleanup unnecessary VAST Program Files after minimization
  if(s==n.species) { cleanup_VAST_file(DateFile=DateFile, Version=Version) }
  
  #Need to reset working director PlotIndex_Fn seems to change to DateFile
  setwd(working.dir)

} #end wrapper function

# dev.off()


end.time <- date()

print(paste('START:',start.time))
print(paste('END:',end.time))


#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Compare Knot Number --- UPDATED FOR LOWER STORAGE REQUIREMENTS
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 4.10.17
#
#Purpose: To explore sensitivity of model-based index estimates to different knot number specification
#             THIS SCRIPT IS AN UPDATE FROM EARLIER VERSION TO REDUCE STORAGE REQUIREMENTS.
#
#
#
#==================================================================================================
#NOTES:
#
#  a) Could calculate design-based estiamtes within wrapper function or at the end while plotting.
#  b) Trouble with bias.corr=TRUE, Memory allocation fail, 200 kts, unknown species, during parallel. 
#  b.1) Problem is with EBS Arrowtooth. for bias.corr=TRUE, this species will be remove and knot numbers limited. 
#==================================================================================================
#TIMING:
# For 100-1000, by 100 knots
# [1] "### START: Mon Apr 10 16:17:08 2017"
# [1] "### END: Mon Apr 10 22:54:04 2017"

#11 cores NEW knot range
# [1] "### START: Fri Nov 17 13:14:57 2017"
# [1] "### END: Fri Nov 17 20:59:03 2017"

#bias.correction=TRUE - Removed EBS Shelf and Spiny dogfish
# [1] "## Trial Knot Number 7 of 7"
# [1] "# Trial Knots: 1000"
# snowfall 1.84-6.1 initialized (using snow 0.4-2): parallel execution on 11 CPUs.
# 
# Error in checkForRemoteErrors(val) : 
#   one node produced an error: Memory allocation fail in function 'MakeADHessObject2'
# In addition: Warning message:
#   In dir.create(trial.dir) :

#Failed on s=13, arrowtooth flounder

#==================================================================================================

require(VAST)
require(TMB)
require(parallel)
require(snowfall)
require(tidyverse)
require(cowplot)
require(xlsx)
require(ggthemes)
require(FishData)



source("R/calc-design-based-index.r")
source("R/create-VAST-input.r")
source("R/create-Data-Geostat.r")
source("R/load-RACE-data.r")

source("R/cleanup-VAST-file.r")
source("R/get-VAST-index.r")


home.dir <- getwd()
#Create working directory
working.dir <- paste0(home.dir, "/examples/Test_Knot_Number")

#Determine species list
species.list <- read.csv("data/eval_species_list.csv", stringsAsFactors=FALSE)

#Limit species included
species.list <- species.list[species.list$include=='Y',]
n.species <- nrow(species.list)

#Create
species.series <- c(1:n.species)

#=======================================================================
##### CONTROL SECTION #####
#Number of cores to use
n.cores <- detectCores()-1

#Boolean for running estimation models
do.estim <- FALSE

#Trial Knot Numbers
trial.knots <- c(100,200,300,400,500,750,1000)#seq(100, 1000, by=100)
n.trial.knots <- length(trial.knots)

#Boolean for bias correction
bias.correct <- TRUE

#Update if
if(bias.correct==TRUE) {
  # species.list <- species.list[-which(species.list$survey=='EBS_SHELF'),]
  
  # species.list <- species.list[-which(species.list$name=='Spiny dogfish'),]
  # 
  # n.species <- nrow(species.list)
  # species.series <- c(1:n.species)
#   
#   
#   # trial.knots <- c(100,200,300,400,500)
#   # n.trial.knots <- length(trial.knots)
}
#=======================================================================
##### Run VAST model  #####
Version <- "VAST_v4_0_0"
lat_lon.def <- "start"

#SPATIAL SETTINGS
Method = c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km = 25
# n_x = c(100, 250, 500, 1000, 2000)[2] # Number of stations
Kmeans_Config = list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("All_areas"))

#MODEL SETTINGS
FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

ObsModel = c(1, 0) #Lognormal

#SPECIFY OUTPUTS
Options = c(SD_site_density = 0, SD_site_logdensity = 0,
            Calculate_Range = 1, Calculate_evenness = 0, Calculate_effective_area = 1,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)

#Output Directory Name
output.dir <- paste0(working.dir,"/output_bias.correct_",bias.correct)



#=======================================================================
##### WRAPPER FUNCTION FOR RUNNING IN PARALLEL #####


s <- 1
# for(s in 1:n.species) {
species_wrapper_fxn_knots <- function(s, n_x, bias.correct) {
  # require(VAST)
  # require(TMB)
  #Define file for analyses
  DateFile <- paste0(trial.dir,"/",species.list$survey[s],"_",species.list$name[s],"/")
  
  dir.create(DateFile)
  
  #Define species.codes
  species.codes <- species.list$species.code[s]
  survey <- species.list$survey[s]
  
  #=======================================================================
  ##### READ IN DATA AND BUILD VAST INPUT #####
  #  NOTE: this will create the DateFile
  
  # VAST_input <- create_VAST_input(species.codes=species.codes, lat_lon.def=lat_lon.def, save.Record=FALSE,
  #                                 Method=Method, grid_size_km=grid_size_km, n_x=n_x,
  #                                 Kmeans_Config=Kmeans_Config,
  #                                 strata.limits=strata.limits, survey=survey,
  #                                 DateFile=DateFile,
  #                                 FieldConfig, RhoConfig, OverdispersionConfig,
  #                                 ObsModel, Options)
  
  VAST_input <- create_VAST_input(species.codes=species.codes, combineSpecies=FALSE,
                                  lat_lon.def=lat_lon.def, save.Record=FALSE,
                                  Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                  Kmeans_Config=Kmeans_Config,
                                  strata.limits=strata.limits, survey=survey,
                                  DateFile=DateFile,
                                  FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                  OverdispersionConfig=OverdispersionConfig,
                                  ObsModel=ObsModel, Options=Options, Version=Version)
  
  #Unpack
  TmbData <- VAST_input$TmbData
  Data_Geostat <- VAST_input$Data_Geostat
  Spatial_List <- VAST_input$Spatial_List
  Extrapolation_List <- VAST_input$Extrapolation_List
  
  
  #=======================================================================
  ##### RUN VAST #####
  
  
  
  #Build TMB Object
  #  Compilation may take some time
  TmbList <- VAST::Build_TMB_Fn(TmbData = TmbData, RunDir = DateFile,
                                Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                                Method = Method)
  Obj <- TmbList[["Obj"]]
  
  if(bias.correct==FALSE) {
    Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
                               upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
                               bias.correct = bias.correct, newtonsteps=2)
  }else {
    #NEW: Only Bias Correct Index
    Opt <- TMBhelper::Optimize(obj=Obj, lower=TmbList[["Lower"]], 
                               upper=TmbList[["Upper"]], getsd=TRUE, savedir=DateFile, 
                               bias.correct=bias.correct, newtonsteps=2,
                               bias.correct.control=list(sd=TRUE, nsplit=200, split=NULL,
                                                         vars_to_correct="Index_cyl"))
  }
  #Save output
  # Report = Obj$report()
  # Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
  # save(Save, file=paste0(DateFile,"Save.RData"))
  
  #Calculate index values
  # TmbData = TmbData, Sdreport = Opt[["SD"]]
  vast_est <- get_VAST_index(TmbData=TmbData, Sdreport=Opt[["SD"]], bias.correct=bias.correct, Data_Geostat=Data_Geostat)
  #========================================================================
  ##### DIAGNOSTIC AND PREDICTION PLOTS #####
  # plot_VAST_output(Opt, Report, DateFile, survey, TmbData, Data_Geostat, Extrapolation_List, Spatial_List)
  
  #========================================================================
  ##### CLEANUP VAST OUTPUT #####
  # cleanup_VAST_file(DateFile=DateFile, Version=Version) #No longer necessary as we are deleting everything at the end
  
  rm("VAST_input", "TmbData", "Data_Geostat", "Spatial_List", "Extrapolation_List",
     "TmbList", "Obj", "Opt", "Report", "Save")
  
  #========================================================================
  setwd(home.dir)
  ##### RETURN SECTION #####
  return(vast_est)
} 


# for(s in 1:n.species) {
#   species_wrapper_fxn_knots(s=s, n_x=n_x, bias.correct=bias.correct)
# }

#=======================================================================
##### Loop Through Trial Knots  #####
if(do.estim==TRUE) {
  vast_est.output <- vector('list', length=n.trial.knots)

  time.1 <- date()
  
  t <- 1
  for(t in 1:n.trial.knots) {
    print(paste('## Trial Knot Number',t,'of',n.trial.knots))
    print(paste('# Trial Knots:',trial.knots[t]))
    #Specify trial observation model
    
    #Specify knots
    n_x <- trial.knots[t]
    
    #Setup File
    trial.dir <- paste0(working.dir,"/",n_x,"_bias.corr_",bias.correct)
    dir.create(trial.dir)
    
    
    #=======================================================================
    ##### SNOWFALL CODE FOR PARALLEL #####
    sfInit(parallel=TRUE, cpus=n.cores, type='SOCK')
    sfExportAll() #Exportas all global variables to cores
    sfLibrary(TMB)  #Loads a package on all nodes
    sfLibrary(VAST)
    output <- sfLapply(species.series, fun=species_wrapper_fxn_knots, n_x=n_x, bias.correct=bias.correct)
    # sfRemove(Save)
    # sfRemover(VAST_input)
    sfStop()
    
    vast_est.output[[t]] <- output
    
  }# next t
  
  #Dimensions for vast_est.output are 1) Trial knots, 2) Species
  # vast_est.output[[1:n.trial.knots]][[1:n.species]]
  
  #Create output directory
  dir.create(output.dir)
  save(vast_est.output, file=paste0(output.dir,"/vast_est.output.RData"))
  
  #=======================================================================
  ##### DELETE UNNECESSARY FILE STRUCTURE #####
  #Must reset working directory
  setwd(working.dir)
  t <- 1
  for(t in 1:n.trial.knots) {
    # s <- 1
    # for(s in 1:n.species) {
      # temp.DateFile <- paste0(working.dir,"/",trial.knots[t],"_bias.corr_",bias.correct,"/",species.list$name[s],"/")
      #Remove manually
      # file.remove(paste0(temp.DateFile,"parameter_estimates.Rdata"))
      # file.remove(paste0(temp.DateFile,"parameter_estimates.txt"))
      
      # #REMOVE EVERYTHING...
      # # file.remove(paste0(working.dir,"/",trial.knots[t],"_bias.corr_",bias.correct,"/"), force=TRUE)
      # print(unlink(paste0(working.dir,"/",trial.knots[t],"_bias.corr_",bias.correct,"/"), recursive=TRUE))
      
      
      # unlink(paste0(working.dir,"/",trial.knots[t],"_bias.corr_",bias.correct,"/",species.list$name[s]), recursive=TRUE)
    # }#next s
    
    unlink(paste0(working.dir,"/",trial.knots[t],"_bias.corr_",bias.correct), recursive=TRUE)
  }#next t
  
  time.2 <- date()
  
  print(paste('### START:', time.1))
  print(paste('### END:', time.2))
  
  #Reset to the home directory
  setwd(home.dir)
}else {
  load(paste0(output.dir,"/vast_est.output.RData"))
}

#=======================================================================
##### Create Output Lists #####

#Create large list
vast.list  <- NULL
#Year, Biomass, SD, CV, Species, Model, Knots

t <- 1
for(t in 1:n.trial.knots) {
  s <- 1
  for(s in 1:n.species) {
    
    temp.list <- vast_est.output[[t]][[s]][c(1,4,6)]
    #Calculate CV
    CV <- temp.list$SD_mt/temp.list$Estimate_metric_tons
    temp.species <- species.list$name[s]
    temp.survey <- species.list$survey[s]
    temp.name <- paste0(temp.survey,": ",temp.species)
    #Bind it
    temp.list <- cbind(temp.list, CV, temp.survey, temp.species, temp.name, 'VAST', trial.knots[t])
    #Add it
    vast.list <- rbind(vast.list, temp.list)
  }#next s
}#next t
names(vast.list) <- c('Year','Biomass','SD','CV','Survey','Species','Name','Model','Knots')


#Add Design-based estimates
db.list <- NULL
s <- 1
for(s in 1:n.species ) {
  #Get design-based estimate
  db_est <- calc_design_based_index(species.codes=species.list$species.code[s], survey=species.list$survey[s])
  temp.species <- species.list$name[s]
  temp.survey <- species.list$survey[s]
  temp.name <- paste0(temp.survey,": ",temp.species)
  temp.list <- cbind(db_est[,c(1,2,4,5)], temp.survey, temp.species, temp.name, "Design-based", 'Design-based')
  #Add it
  db.list <- rbind(db.list, temp.list)
}#next s
names(db.list) <- c('Year','Biomass','SD','CV','Survey','Species', 'Name','Model','Knots')

#Combine the lists
survey.list <- rbind(vast.list,db.list)


#=======================================================================
##### Plot Comparison of Results #####
scale.hues <- c(5,250)
dpi <- 500
#Find years survey was conducted

surveys <- unique(survey.list$Survey)
n.surveys <- length(surveys)

survey.list$Knots <- factor(survey.list$Knots, levels=c(trial.knots,'Design-based'))

#========================================
#Plot All Species Across Surveys
s <- 1
for(s in 1:n.surveys) {
#GOA
survey <- surveys[s]
#Limit data set
plot.list <- survey.list[survey.list$Survey==survey,]
yrs.surv <- sort(unique(plot.list$Year[plot.list$Model=='Design-based']))
plot.list <- plot.list[plot.list$Year %in% yrs.surv,]

#Remove 2001 from design-based results because of incomplete sampling
# if(survey=='GOA') { plot.list <- plot.list[-which(plot.list$Year==2001 & plot.list$Model=='Design-based'),] }
# if(survey=='GOA') { plot.list <- plot.list[-which(plot.list$Model=='Design-based'),] }
plot.list$Biomass <- plot.list$Biomass/1e3

#PLOT Indices
g <- ggplot(plot.list, aes(x=Year, y=Biomass, color=Knots, lty=Model, ymin=0)) +
       theme_gray() +
       # theme_economist() +
       theme(legend.position='right') +
       geom_line() +
       facet_wrap(~Species, scales='free', ncol=2) +
       labs(list(y='Biomass (thousands of metric tonnes)')) +
       # ggtitle('Survey:', subtitle='Gulf of Alaska') +
       ggtitle(paste(survey, 'Survey')) +
       scale_color_hue(h=scale.hues) +
       # scale_color_brewer(type='seq', palette=1)
       geom_line(data=plot.list[plot.list$Model=='Design-based',], color='black') +
       geom_point(data=plot.list[plot.list$Model=='Design-based',], show.legend=FALSE, colour='black')
       
g
ggsave(paste0(output.dir,"/", survey," VAST Index Compare v DB.png"), g, height=9, width=8, units='in', dpi=dpi)

#Vast Models only
g2 <- ggplot(plot.list[plot.list$Model=='VAST',], aes(x=Year, y=Biomass, color=Knots, ymin=0)) +
        theme_gray() +
        geom_line() +
        theme(legend.position='right') +
        facet_wrap(~Species, scales='free', ncol=2) +
        labs(list(y='Biomass (thousands of metric tonnes)')) +
        # ggtitle('Survey:', subtitle='Gulf of Alaska') +
        ggtitle(paste(survey, 'Survey')) +
        scale_color_hue(h=scale.hues)

g2
ggsave(paste0(output.dir,"/", survey," VAST Index Compare.png"), g2, height=9, width=8, units='in', dpi=dpi)

#Plot Survey Variance Measures

g3 <- ggplot(plot.list, aes(x=Species, y=CV, fill=Knots)) +
        theme_gray() +
        theme(legend.position='right') +
        # geom_boxplot(aes(lty=Model)) +
        geom_boxplot(aes(color=Model)) +
        scale_color_colorblind() +
        # scale_color_hue() +
        labs(list(y=paste('Annual Survey CV'))) +
        theme(axis.text.x = element_text(angle = 45, hjust = 1, vjust=1, debug=FALSE)) +
        scale_fill_hue(h=scale.hues) +
        ggtitle(paste(survey, 'Survey'))
  
        
g3
ggsave(paste0(output.dir,"/", survey," CV Compare.png"), g3, height=5, width=7, units='in', dpi=dpi)
ggsave(paste0(getwd(),"/Output/Figs for Sept_2017 GPT/", survey," CV Compare.png"), g3, height=7, width=8, units='in', dpi=1e3)

#Separate boxes
g4 <- ggplot(plot.list, aes(x=Species, y=CV, fill=Knots)) +
  theme_gray() +
  theme(legend.position='bottom') +
  # geom_boxplot(aes(lty=Model)) +
  geom_boxplot(aes(color=Model)) +
  scale_color_colorblind() +
  # scale_color_hue() +
  labs(list(y=paste('Annual Survey CV'))) +
  theme(axis.title.x=element_blank(),
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank()) +
  scale_fill_hue(h=scale.hues) +
  # ggtitle(paste(survey, 'Survey')) +
  facet_wrap(~Species, scales='free', nrow=2)
  


g4
ggsave(paste0(output.dir,"/", survey," CV Compare.png"), g4 + ggtitle(paste(survey, 'Survey')), 
         height=6, width=8, units='in', dpi=dpi)
ggsave(paste0(getwd(),"/Output/Figs for Sept_2017 GPT/", survey, " CV Compare_2.png"), g4, height=7, width=8, units='in', dpi=1000)

#Plotting for GPT presentation
g5 <- ggplot(plot.list, aes(x=Year, y=Biomass, color=Knots, lty=Model, ymin=0)) +
        theme_gray() +
        # theme_wsj() +
        # theme_solarized() +
        theme(legend.position='right') +
        geom_line() +
        facet_wrap(~Species, scales='free', ncol=3) +
        labs(list(y='Biomass (thousands of metric tonnes)')) +
        # ggtitle('Survey:', subtitle='Gulf of Alaska') +
        ggtitle(paste(survey, 'Survey')) +
        scale_color_hue(h=scale.hues) +
        geom_line(data=plot.list[plot.list$Model=='Design-based',], color='black') +
        geom_point(data=plot.list[plot.list$Model=='Design-based',], show.legend=FALSE, colour='black')

g5

ggsave(paste0(getwd(),"/Output/Figs for Sept_2017 GPT/", survey, " Index Compare.png"), g5, height=7, width=10, units='in', dpi=1000)
# png(paste0(output.dir,'/', survey, ' Figures.png'), height=7, width=8, units='in', res=500)
# dev.off()
}

#==================================================================================
#GENERATE TABLES OF % DIFFERENCE IN CV'S BETWEEN METHODS

#Gulf of Alaska
temp.survey <- 'GOA'
temp.db <- db.list[db.list$Survey==temp.survey,]
temp.vast <- vast.list[vast.list$Survey==temp.survey,]

temp.species <- unique(temp.db$Species)
n.temp.species <- length(temp.species)

temp.years <- sort(unique(temp.db$Year))
n.temp.years <- length(temp.years)

pct.diff.cv.goa <- array(dim=c(n.temp.species,n.trial.knots,n.temp.years), dimnames=list(temp.species,trial.knots,temp.years))
pct.diff.bio.goa <- array(dim=c(n.temp.species,n.trial.knots,n.temp.years), dimnames=list(temp.species,trial.knots,temp.years))
s <- 1
for(s in 1:n.temp.species) {
  t <- 1
  for(t in 1:n.trial.knots) {
    y <- 1
    for(y in 1:n.temp.years) {
      #CV
      pct.diff.cv.goa[s,t,y] <- (temp.vast$CV[temp.vast$Species==temp.species[s] & temp.vast$Knots==trial.knots[t] & temp.vast$Year==temp.years[y]] - 
                                   temp.db$CV[temp.db$Species==temp.species[s] & temp.db$Year==temp.years[y]])/
                                   temp.db$CV[temp.db$Species==temp.species[s] & temp.db$Year==temp.years[y]]
      #Biomass Estimate
      pct.diff.bio.goa[s,t,y] <- (temp.vast$Biomass[temp.vast$Species==temp.species[s] & temp.vast$Knots==trial.knots[t] & temp.vast$Year==temp.years[y]] - 
                                    temp.db$Biomass[temp.db$Species==temp.species[s] & temp.db$Year==temp.years[y]])/
                                    temp.db$Biomass[temp.db$Species==temp.species[s] & temp.db$Year==temp.years[y]]
    }#next y
  }#next t
}#next s

#Gulf of Alaska
temp.survey <- 'AI'
temp.db <- db.list[db.list$Survey==temp.survey,]
temp.vast <- vast.list[vast.list$Survey==temp.survey,]

temp.species <- unique(temp.db$Species)
n.temp.species <- length(temp.species)

temp.years <- sort(unique(temp.db$Year))
n.temp.years <- length(temp.years)

pct.diff.cv.ai <- array(dim=c(n.temp.species,n.trial.knots,n.temp.years), dimnames=list(temp.species,trial.knots,temp.years))
pct.diff.bio.ai <- array(dim=c(n.temp.species,n.trial.knots,n.temp.years), dimnames=list(temp.species,trial.knots,temp.years))
s <- 1
for(s in 1:n.temp.species) {
  t <- 1
  for(t in 1:n.trial.knots) {
    y <- 1
    for(y in 1:n.temp.years) {
      #CV
      pct.diff.cv.ai[s,t,y] <- (temp.vast$CV[temp.vast$Species==temp.species[s] & temp.vast$Knots==trial.knots[t] & temp.vast$Year==temp.years[y]] - 
                                  temp.db$CV[temp.db$Species==temp.species[s] & temp.db$Year==temp.years[y]])/
                                  temp.db$CV[temp.db$Species==temp.species[s] & temp.db$Year==temp.years[y]]
      #Biomass Estimate
      pct.diff.bio.ai[s,t,y] <- (temp.vast$Biomass[temp.vast$Species==temp.species[s] & temp.vast$Knots==trial.knots[t] & temp.vast$Year==temp.years[y]] - 
                                   temp.db$Biomass[temp.db$Species==temp.species[s] & temp.db$Year==temp.years[y]])/
                                   temp.db$Biomass[temp.db$Species==temp.species[s] & temp.db$Year==temp.years[y]]
    }#next y
  }#next t
}#next s

#PRINT TABLE

write.csv(apply(pct.diff.cv.goa, c(1,2), mean), file=paste0(output.dir, "/GOA Mean Pct Diff CV.csv"))
write.csv(apply(pct.diff.cv.ai, c(1,2), mean), file=paste0(output.dir, "/AI Mean Pct Diff CV.csv"))

#==================================================================================
#Plot GOA Pollock
survey <- 'Gulf of Alaska'
plot.list <- survey.list[survey.list$Survey=='GOA' & survey.list$Species=='Walleye pollock',]
yrs.surv <- sort(unique(plot.list$Year[plot.list$Model=='Design-based']))
plot.list <- plot.list[plot.list$Year %in% yrs.surv,]

#Remove 2001 from design-based results because of incomplete sampling
# if(survey=='GOA') { plot.list <- plot.list[-which(plot.list$Year==2001 & plot.list$Model=='Design-based'),] }
plot.list$Biomass <- plot.list$Biomass/1e3

#PLOT Indices
g.idx <- ggplot(plot.list, aes(x=Year, y=Biomass, color=Knots, lty=Model)) +
           theme_gray() +
            # theme(legend.position='bottom') +
           geom_line() +
           facet_wrap(~Species, scales='free') +
           labs(list(y='Biomass (thousands of metric tonnes)')) +
           # ggtitle('Survey:', subtitle='Gulf of Alaska') +
           # ggtitle(paste(survey, 'Survey')) +
           scale_color_hue(h=scale.hues)
g.idx
ggsave(paste0(output.dir,"/GOA Pollock Idx.png"), g.idx, height=6, width=8, units='in', dpi=dpi)

g.cv <- ggplot(plot.list, aes(x=Knots, y=CV, fill=Knots)) +
  theme_gray() +
  # geom_boxplot(aes(lty=Model)) +
  geom_boxplot() +
  facet_wrap(~Species, scales='free') +
  labs(list(y=paste('Annual Survey CV'))) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1, vjust=1, debug=FALSE)) +
  scale_fill_hue(h=scale.hues) +
  # ggtitle(paste(survey, 'Survey')) +
  
  theme(legend.position='none')
  
# g.cv


#Combine plots with gridExtra
g.both <- plot_grid(g.idx, g.cv, nrow=1, ncol=2, rel_widths=c(3.5,1))
g.both

ggsave(paste0(output.dir,"/GOA Pollock Idx and CV.png"), g.both, height=5, width=8, units='in', dpi=dpi)


#==================================================================================
#Plot GOA Pollock
survey <- 'Gulf of Alaska'
plot.list <- survey.list[survey.list$Survey=='GOA' & survey.list$Species=='Walleye pollock',]
yrs.surv <- sort(unique(plot.list$Year[plot.list$Model=='Design-based']))
plot.list <- plot.list[plot.list$Year %in% yrs.surv,]

#Remove 2001 from design-based results because of incomplete sampling
# if(survey=='GOA') { plot.list <- plot.list[-which(plot.list$Year==2001 & plot.list$Model=='Design-based'),] }
plot.list$Biomass <- plot.list$Biomass/1e3

#PLOT Indices
g.idx <- ggplot(plot.list, aes(x=Year, y=Biomass, color=Knots, lty=Model)) +
  theme_gray() +
  # theme(legend.position='bottom') +
  geom_line() +
  facet_wrap(~Species, scales='free') +
  labs(list(y='Biomass (thousands of metric tonnes)')) +
  # ggtitle('Survey:', subtitle='Gulf of Alaska') +
  # ggtitle(paste(survey, 'Survey')) +
  scale_color_hue(h=scale.hues)
g.idx
ggsave(paste0(output.dir,"/GOA Pollock Idx.png"), g.idx, height=6, width=8, units='in', dpi=dpi)

g.cv <- ggplot(plot.list, aes(x=Knots, y=CV, fill=Knots)) +
  theme_gray() +
  # geom_boxplot(aes(lty=Model)) +
  geom_boxplot() +
  facet_wrap(~Species, scales='free') +
  labs(list(y=paste('Annual Survey CV'))) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1, vjust=1, debug=FALSE)) +
  scale_fill_hue(h=scale.hues) +
  # ggtitle(paste(survey, 'Survey')) +
  
  theme(legend.position='none')

# g.cv


#Combine plots with gridExtra
g.both <- plot_grid(g.idx, g.cv, nrow=1, ncol=2, rel_widths=c(3.5,1))
g.both

ggsave(paste0(output.dir,"/GOA Pollock Idx and CV.png"), g.both, height=5, width=8, units='in', dpi=dpi)



#==================================================================================
#Plot GOA POP
survey <- 'Gulf of Alaska'
plot.list <- survey.list[survey.list$Survey=='GOA' & survey.list$Species=='Pacific ocean perch',]
yrs.surv <- sort(unique(plot.list$Year[plot.list$Model=='Design-based']))
plot.list <- plot.list[plot.list$Year %in% yrs.surv,]

#Remove 2001 from design-based results because of incomplete sampling
# if(survey=='GOA') { plot.list <- plot.list[-which(plot.list$Year==2001 & plot.list$Model=='Design-based'),] }
plot.list$Biomass <- plot.list$Biomass/1e3

#PLOT Indices
g.idx <- ggplot(plot.list, aes(x=Year, y=Biomass, color=Knots, lty=Model)) +
  theme_gray() +
  # theme(legend.position='bottom') +
  geom_line() +
  facet_wrap(~Species, scales='free') +
  labs(list(y='Biomass (thousands of metric tonnes)')) +
  # ggtitle('Survey:', subtitle='Gulf of Alaska') +
  # ggtitle(paste(survey, 'Survey')) +
  scale_color_hue(h=scale.hues)
g.idx
ggsave(paste0(output.dir,"/GOA POP Idx.png"), g.idx, height=6, width=8, units='in', dpi=dpi)

g.cv <- ggplot(plot.list, aes(x=Knots, y=CV, fill=Knots)) +
  theme_gray() +
  # geom_boxplot(aes(lty=Model)) +
  geom_boxplot() +
  facet_wrap(~Species, scales='free') +
  labs(list(y=paste('Annual Survey CV'))) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1, vjust=1, debug=FALSE)) +
  scale_fill_hue(h=scale.hues) +
  # ggtitle(paste(survey, 'Survey')) +
  
  theme(legend.position='none')

# g.cv


#Combine plots with gridExtra
g.both <- plot_grid(g.idx, g.cv, nrow=1, ncol=2, rel_widths=c(3.5,1))
g.both

ggsave(paste0(output.dir,"/GOA POP Idx and CV.png"), g.both, height=5, width=8, units='in', dpi=dpi)

#GENERATE WORKSHEET FOR WITH ALL OF THE GOA POP INDICES
write.xlsx(survey.list[survey.list$Survey=='GOA' & survey.list$Species=='Pacific ocean perch' &
                         survey.list$Year %in% yrs.surv,],
             file=paste0(output.dir,'/GOA POP Indices.xlsx'), sheetName='plot.list')



#==================================================================================
#Plot GOA Arrowtooth for Ingrid's Assessment
survey <- 'Gulf of Alaska'
plot.list <- survey.list[survey.list$Survey=='GOA' & survey.list$Species=='Arrowtooth flounder',]
yrs.surv <- sort(unique(plot.list$Year[plot.list$Model=='Design-based']))
plot.list <- plot.list[plot.list$Year %in% yrs.surv,]

#Remove 2001 from design-based results because of incomplete sampling
# plot.list <- plot.list[-which(plot.list$Year==2001 & plot.list$Model=='Design-based'),]
# plot.list <- plot.list[-which(plot.list$Year==2017 & plot.list$Model=='Design-based'),]

plot.list$Biomass <- plot.list$Biomass/1e3

#PLOT Indices
g.idx <- ggplot(plot.list, aes(x=Year, y=Biomass, color=Knots, lty=Model)) +
  # theme_gray() +
  # theme(legend.position='bottom') +
  # geom_line() +
  # facet_wrap(~Species, scales='free') +
  # labs(list(y='Biomass (thousands of metric tonnes)')) +
  # # ggtitle('Survey:', subtitle='Gulf of Alaska') +
  # # ggtitle(paste(survey, 'Survey')) +
  # scale_color_hue(h=scale.hues)




  theme_gray() +
  # theme_wsj() +
  # theme_solarized() +
  geom_line() +
  facet_wrap(~Species, scales='free', ncol=3) +
  labs(list(y='Biomass (thousands of metric tonnes)')) +
  # ggtitle('Survey:', subtitle='Gulf of Alaska') +
  ggtitle(paste(survey, 'Survey')) +
  scale_color_hue(h=scale.hues) +
  geom_line(data=plot.list[plot.list$Model=='Design-based',], color='black') +
  geom_point(data=plot.list[plot.list$Model=='Design-based',], show.legend=FALSE, colour='black')

# g.idx

g.cv <- ggplot(plot.list, aes(x=Knots, y=CV, fill=Knots)) +
  theme_gray() +
  # geom_boxplot(aes(lty=Model)) +
  geom_boxplot() +
  facet_wrap(~Species, scales='free') +
  labs(list(y=paste('Annual Survey CV'))) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1, vjust=1, debug=FALSE)) +
  scale_fill_hue(h=scale.hues) +
  # ggtitle(paste(survey, 'Survey')) +
  
  theme(legend.position='none')

  # theme_gray() +
  # theme(legend.position='right') +
  # # geom_boxplot(aes(lty=Model)) +
  # geom_boxplot(aes(color=Model)) +
  # scale_color_colorblind() +
  # # scale_color_hue() +
  # labs(list(y=paste('Annual Survey CV'))) +
  # theme(axis.text.x = element_text(angle = 45, hjust = 1, vjust=1, debug=FALSE)) +
  # scale_fill_hue(h=scale.hues) +
  # ggtitle(paste(survey, 'Survey'))
# g.cv


#Combine plots with gridExtra
g.both <- plot_grid(g.idx, g.cv, nrow=1, ncol=2, rel_widths=c(3.5,1))
g.both

ggsave(paste0(output.dir,"/GOA Arrowtooth Index and CV.png"), g.both, height=5, width=8, units='in', dpi=dpi)

#==================================================================================
#Plot GOA Arrowtooth for Ingrid's Assessment
survey <- 'Gulf of Alaska'
plot.list <- survey.list[survey.list$Survey=='GOA' & survey.list$Species=='Northern rockfish',]
yrs.surv <- sort(unique(plot.list$Year[plot.list$Model=='Design-based']))
plot.list <- plot.list[plot.list$Year %in% yrs.surv,]

#Remove 2001 from design-based results because of incomplete sampling
# plot.list <- plot.list[-which(plot.list$Year==2001 & plot.list$Model=='Design-based'),]
# plot.list <- plot.list[-which(plot.list$Year==2017 & plot.list$Model=='Design-based'),]

plot.list$Biomass <- plot.list$Biomass/1e3

#PLOT Indices
g.idx <- ggplot(plot.list, aes(x=Year, y=Biomass, color=Knots, lty=Model)) +
  theme_gray() +
  # theme_wsj() +
  # theme_solarized() +
  geom_line() +
  facet_wrap(~Species, scales='free', ncol=3) +
  labs(list(y='Biomass (thousands of metric tonnes)')) +
  # ggtitle('Survey:', subtitle='Gulf of Alaska') +
  ggtitle(paste(survey, 'Survey')) +
  scale_color_hue(h=scale.hues) +
  geom_line(data=plot.list[plot.list$Model=='Design-based',], color='black') +
  geom_point(data=plot.list[plot.list$Model=='Design-based',], show.legend=FALSE, colour='black')

# g.idx

g.cv <- ggplot(plot.list, aes(x=Knots, y=CV, fill=Knots)) +
  theme_gray() +
  # geom_boxplot(aes(lty=Model)) +
  geom_boxplot() +
  facet_wrap(~Species, scales='free') +
  labs(list(y=paste('Annual Survey CV'))) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1, vjust=1, debug=FALSE)) +
  scale_fill_hue(h=scale.hues) +
  # ggtitle(paste(survey, 'Survey')) +
  
  theme(legend.position='none')

#Combine plots with gridExtra
g.both <- plot_grid(g.idx, g.cv, nrow=1, ncol=2, rel_widths=c(3.5,1))
g.both

ggsave(paste0(output.dir,"/GOA Northern Rockfish Index and CV.png"), g.both, height=5, width=8, units='in', dpi=dpi)

#Facet
# g.multi <- vector('list', length=n.species)
# 
# s <- 1
# for(s in 1:n.species) {
#   g.multi[[s]] <- ggplot(plot.list[plot.list$Species=='Big skate',], aes(x=Species, y=CV, fill=Knots)) +
#     theme_gray() +
#     geom_boxplot() +
#     labs(list(y='Annual Survey CV')) +
#     facet_wrap(~Species, ncol=5, drop=TRUE)
#     
#     if(s > 1) { +  }
#     + theme(legend.position="none")
# }
# 
# 
# require(gridExtra)
# 
# do.call("grid.arrange", c(g.multi, ncol=5))

# 
# ggsave(paste0(output.dir, "/VAST Index Compare.png"), g2, height=6, width=9, units='in')
# 









#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Testing Observation Model Specification
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 3.30.17
#
#Purpose: To test impact of changing distribution on observation model for positive catch rates,
#           across species.
#  1) Loop through obs model specifications for positive catch rate distribution.
#  2) Run vAST model in parallel across species. 
#  3) Initially run with 100 knots.
#
#
#==================================================================================================
#NOTES:
#  a) Originally included Normal distribution for positive catch rates,
#       but produced unknown errors for some species.
#  b) Obs models that returned errors for some species:
#       [0] Normal
#       [5] Negative binomial
#       [7] Poisson
#  c) Conway-Maxwell Poisson seems to run without error, but is EXTREMELY SLOW
#  d) QQ_Fn() from SpatialDeltaGLMM was VERY SLOW.
#       Below code is implemented to generate the QQplot more efficiently, but 
#         currently only for gamma and lognormal obs models. 
#
#
#==================================================================================================
require(snowfall)
require(parallel)
require(ggplot2)
require(TMB)
require(TMBhelper)
require(VAST)
require(reshape)
require(foreach)

#Source necessary files
source("R/create-VAST-input.r")
source("R/cleanup-VAST-file.r")

#Create working directory
working.dir <- paste0(getwd(),'/examples/Test_Obs_Model')

#Determine species list
species.list <- read.csv("data/eval_species_list.csv", stringsAsFactors=FALSE)

#Limit species included
species.list <- species.list[species.list$include=='Y',]
n.species <- nrow(species.list)
#Create
species.series <- c(1:n.species)

#=======================================================================
##### CONTROL SECTION #####
#Number of cores to use
n.cores <- detectCores()-1

#Boolean for running estimation models
do.estim <- FALSE

#Setup observation model trials
# trial.obs <- c(0:2,5:7)
trial.obs <- c(0:2,5,7)
n.trial.obs <- length(trial.obs)

#Names for trial Obs models
# names.trial.obs <- c('Normal','Lognormal','Gamma','Negative_binomial','Conway-Maxwell_Poisson','Poisson')
names.trial.obs <- c('Normal','Lognormal','Gamma','Negative_binomial','Poisson')
#=======================================================================
##### VAST MODEL SPECIFICATIONS #####

Version <- "VAST_v2_4_0"

lat_lon.def <- "mean"

#SPATIAL SETTINGS
Method <- c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km <- 25
n_x <- c(100, 250, 500, 1000, 2000)[1] # Number of stations
Kmeans_Config <- list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("All_areas"),
                            west_border = c(-Inf),
                            east_border = c(Inf))


#DERIVED OBJECTS
Region <- "Gulf_of_Alaska"
###########################
# DateFile=paste0(getwd(),'/examples/VAST_output/')

#MODEL SETTINGS
FieldConfig <- c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
RhoConfig <- c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig <- c(Delta1 = 0, Delta2 = 0)


#SPECIFY OUTPUTS
Options <- c(SD_site_density = 0, SD_site_logdensity = 0,
            Calculate_Range = 1, Calculate_evenness = 0, Calculate_effective_area = 1,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)


#=======================================================================
##### WRAPPER FUNCTION FOR RUNNING IN PARALLEL #####


s <- 1
# for(s in 1:n.species) {
species_wrapper_fxn <- function(s) {
  
  #Define file for analyses
  DateFile <- paste0(trial.dir,"/",species.list$name[s],"/")
  
  #Define species.codes
  species.codes <- species.list$species.code[s]
  
  #=======================================================================
  ##### READ IN DATA AND BUILD VAST INPUT #####
  #  NOTE: this will create the DateFile
  
  VAST_input <- create_VAST_input(species.codes=species.codes, lat_lon.def=lat_lon.def, save.Record=TRUE,
                                  Method=Method, grid_size_km=grid_size_km, n_X=n_x,
                                  Kmeans_Config=Kmeans_Config,
                                  strata.limits=strata.limits, Region=Region,
                                  DateFile=DateFile,
                                  FieldConfig, RhoConfig, OverdispersionConfig,
                                  ObsModel, Options)
  
  
  
  #Unpack
  TmbData <- VAST_input$TmbData
  Data_Geostat <- VAST_input$Data_Geostat
  Spatial_List <- VAST_input$Spatial_List
  Extrapolation_List <- VAST_input$Extrapolation_List
  
  
  #=======================================================================
  ##### RUN VAST #####
  
  
  
  #Build TMB Object
  #  Compilation may take some time
  TmbList <- VAST::Build_TMB_Fn(TmbData = TmbData, RunDir = DateFile,
                                Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                                Method = Method)
  Obj <- TmbList[["Obj"]]
  
  
  Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
                             upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
                             bias.correct = FALSE)
  #Save output
  Report = Obj$report()
  Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
  save(Save, file=paste0(DateFile,"Save.RData"))
  
  #========================================================================
  ##### DIAGNOSTIC AND PREDICTION PLOTS #####
  # plot_VAST_output(Opt, Report, DateFile, Region, TmbData, Data_Geostat, Extrapolation_List, Spatial_List)
  
  #========================================================================
  ##### CLEANUP VAST OUTPUT #####
  cleanup_VAST_file(DateFile=DateFile, Version=Version)
  
  rm("VAST_input", "TmbData", "Data_Geosta", "Spatial_List", "Extrapolation_List",
     "TmbList", "Obj", "Opt", "Report", "Save")
  
  #========================================================================
  ##### RETURN SECTION #####
  # return(Opt$AIC)
  
} 

if(do.estim==TRUE) {
  

#Create a fun output AIC object
AICs <- array(dim=c(n.trial.obs, n.species), dimnames=list(names.trial.obs,species.list$name))


  t <- 1
  for(t in 1:n.trial.obs) {
    print(paste('## Trial Observation Model',t,'of',n.trial.obs))
    print(paste('# Positive Catch Rate Model is:',names.trial.obs[t]))
    #Specify trial observation model
    ObsModel <- c(trial.obs[t], 0)
    #Setup File
    trial.dir <- paste0(working.dir,"/",names.trial.obs[t])
    dir.create(trial.dir)
  
    #=======================================================================
    ##### SNOWFALL CODE FOR PARALLEL #####
    sfInit(parallel=TRUE, cpus=n.cores, type='SOCK')
    sfExportAll() #Exportas all global variables to cores
    sfLibrary(TMB)  #Loads a package on all nodes
    sfLibrary(VAST)
    output <- sfLapply(species.series, fun=species_wrapper_fxn)
    # sfRemove(Save)
    # sfRemover(VAST_input)
    sfStop()
  
    #Output
    AICs[t,] <- unlist(rbind(output))
  
  }# next t
}

#=======================================================================
##### SUMMARY AND PLOTTING SECTION #####
#NOTE: Only Gamma and Lognormal worked for all stocks.

names.species <- species.list$name

#Compare AIC across all potential models
temp.obs <- c('Normal','Lognormal','Gamma','Negative_binomial','Poisson')
n.temp.obs <- length(temp.obs)

#Loop through and determine if model ran... and if so grab AIC
ran.mat <- array(dim=c(n.temp.obs, n.species), dimnames=list(temp.obs,names.species)) #Boolean matrix for whether model ran
aic.mat <- array(dim=c(n.temp.obs, n.species), dimnames=list(temp.obs,names.species))

i <- 1
for(i in 1:n.temp.obs) {
  j <- 1
  for(j in 1:n.species) {
    obs <- temp.obs[i]
    spec <- names.species[j]
    
    temp.dir <- paste0(working.dir,"/",obs,"/",spec)
    
    #Determine if Model ran
    if(file.exists(paste0(temp.dir,"/parameter_estimates.txt"))) {
      ran.mat[i,j] <- TRUE
      load(paste0(temp.dir,"/parameter_estimates.RData"))
      aic.mat[i,j] <- parameter_estimates$AIC
    }else {
      ran.mat[i,j] <- FALSE
      aic.mat[i,j] <- NA
    }
  }#next j
}#next i

#Plot as AIC bars
ran.list <- melt(ran.mat)
names(ran.list) <- c('Obs','Species','value')
aic.list <- melt(aic.mat)
names(aic.list) <- c('Obs','Species','value')

g <- ggplot(aic.list, aes(x=Species, y=value, fill=Obs)) +
       theme_gray() +
       geom_col(position='dodge') +
       theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
       labs(list(y='AIC', fill='Obs. Error\nDist.'))
# g
ggsave(paste0(working.dir,"/figs/AIC Compare at 100 knots.png"), plot=g, height=7, width=7)

#COMPARE LOGNORMAL VS GAMMA
temp.obs <- c('Lognormal','Gamma')
n.temp.obs <- length(temp.obs)

#Create directory for qqplot output\

qq.dir <- paste0(working.dir,"/figs/QQplot")
dir.create(qq.dir)

pdf(paste0(working.dir, "/figs/QQ Plots.pdf"), height=8, width=6)

pow = function(a,b) a^b

par(mfrow=c(n.species/2,n.temp.obs), mar=c(2,2,2,0), oma=c(3,3,0,0))

j <- 1
for(j in 1:n.species) {
  i <- 1
  for(i in 1:n.temp.obs) {
    obs <- temp.obs[i]
    spec <- names.species[j]
    temp.dir <- paste0(working.dir,"/",obs,"/",spec)

    #Load Results
    load(paste0(temp.dir,"/Save.RData"))

    #Plot
    TmbData <- Save$TmbData
    Report <- Save$Report


     #Jim's original function. Time consuming...  so lets speed it up
     # Q <-  SpatialDeltaGLMM::QQ_Fn(TmbData = TmbData, Report = Report,
     #                               FileName_QQ = paste0(qq.dir, "/", spec, "_", obs, ".jpg"))
    
    
    #Taken from Jim's QQ_Fn code, simplified by Curry for lognorm/gamma and
    #  reduced processing time with vectorized format and foreach call. 
  
    Which = which(TmbData$b_i>0)
    Q = rep(NA, length(Which) ) # vector to track quantiles for each observation
    y = array(NA, dim=c(length(Which),1000))
    pred_y = var_y = rep(NA, length(Which) ) # vector to track quantiles for each observation

    #  Simulate quantiles for different distributions
    if(TmbData$ObsModel[1]==1){
      pred_y <- TmbData$a_i[Which] *exp(Report$P2_i[Which])
      
      # set.seed(1)
      # for(ObsI in 1:length(Which)){
      #   # pred_y[ObsI] = TmbData$a_i[Which[ObsI]] * exp(Report$P2_i[Which[ObsI]])
      #   y[ObsI,] = rlnorm(n=ncol(y), meanlog=log(pred_y[ObsI])-pow(Report$SigmaM[1],2)/2, sdlog=Report$SigmaM[1])   # Plotting in log-space
      #   # Q[ObsI] = plnorm(q=TmbData$b_i[Which[ObsI]], meanlog=log(pred_y[ObsI])-pow(Report$SigmaM[1],2)/2, sdlog=Report$SigmaM[1])
      # }
      # set.seed(1)
      #Run Parallel
      y <- foreach(ObsI=c(1:length(Which)), .combine='rbind') %do% {
        rlnorm(n=ncol(y), meanlog=log(pred_y[ObsI])-pow(Report$SigmaM[1],2)/2, sdlog=Report$SigmaM[1])
      }
  
      #Alternative in vector format
      Q <- plnorm(q=TmbData$b_i[Which], meanlog=log(pred_y)-pow(Report$SigmaM[1],2)/2, sdlog=Report$SigmaM[1])
      
      
    }
    if(TmbData$ObsModel[1]==2){
      pred_y <- TmbData$a_i[Which] * exp(Report$P2_i[Which])
      b <- pow(Report$SigmaM[1],2) * pred_y;
      #Loop it
      # set.seed(123)
      # for(ObsI in 1:length(Which)){
      #   y[ObsI,] = rgamma(n=ncol(y), shape=1/pow(Report$SigmaM[1],2), scale=b)
      # }
      # set.seed(123)
      #Foreach it
      t.y <- foreach(ObsI=c(1:length(Which)), .combine='rbind') %do% {
        rgamma(n=ncol(y), shape=1/pow(Report$SigmaM[1],2), scale=b)
      }
      
      Q <- pgamma(q=TmbData$b_i[Which], shape=1/pow(Report$SigmaM[1],2), scale=b)
    }
    #Plot it out
    Qtemp = na.omit(Q)
    Order = order(Qtemp)
    plot(x=seq(0,1,length=length(Order)), y=Qtemp[Order], main=paste0(obs, ": ", spec), xlab="", ylab="", type="l", lwd=3)
    abline(a=0,b=1)

  }#next i
  mtext('Empirical', side=2, outer=TRUE, font=2)
  mtext('Uniform', side=1, outer=TRUE, font=2)
}#next j

dev.off()











#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // Turn off
  if(n_f<0){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IVECTOR(ObsModel);    // Observation model
  // Slot 0: Distribution for positive catches
  // Slot 1: Link function for encounter probabilities
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IVECTOR(c_i);         // Category for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  //  -- presence/absence
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation
  //  -- positive catch rates
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz
  PARAMETER_ARRAY(logSigmaM);   // Slots: 0=mix1 CV, 1=prob-of-mix1, 2=
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_c, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  array<Type> Omegamean1_sf(n_s, FieldConfig(0));
  Omegamean1_sf.setZero();
  array<Type> Epsilonmean1_sf(n_s, FieldConfig(1));
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
      //Epsilon1_sct.col(t) += Epsilon_rho1 * Epsilon1_sct.col(t-1);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  array<Type> Omegamean2_sf(n_s, FieldConfig(2));
  Omegamean2_sf.setZero();
  array<Type> Epsilonmean2_sf(n_s, FieldConfig(3));
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
      //Epsilon2_sct.col(t) += Epsilon_rho2 * Epsilon2_sct.col(t-1);
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  // Linear predictor (pre-link) for presence/absence component
  vector<Type> P1_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi 
  vector<Type> R1_i(n_i);   
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  vector<Type> P2_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    // Linear predictors
    P1_i(i) = Omega1_sc(s_i(i),c_i(i)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_i(i));
    P2_i(i) = Omega2_sc(s_i(i),c_i(i)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_i(i));
    for( int z=0; z<t_iz.row(0).size(); z++ ){
      if( t_iz(i,z)>=0 & t_iz(i,z)<n_t ){  // isNA doesn't seem to work for IMATRIX type
        P1_i(i) += beta1_ct(c_i(i),t_iz(i,z)) + Epsilon1_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(s_i(i),c_i(i),t_iz(i,z));
        P2_i(i) += beta2_ct(c_i(i),t_iz(i,z)) + Epsilon2_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(s_i(i),c_i(i),t_iz(i,z));
      }
    }
    // Responses
    if( ObsModel(1)==0 ){
      // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
      // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
      // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
      R1_i(i) = invlogit( P1_i(i) );
      R2_i(i) = a_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==1 ){
      // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
      // P2_i: Log-average weight;  R2_i:  Positive density prediction
      R1_i(i) = Type(1.0) - exp( -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i)) );
      R2_i(i) = a_i(i)*exp(P1_i(i)) / R1_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==2 ){
      // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Expected numbers
      // P2_i: Log-average weight;  R2_i:  Expected average weight
      R1_i(i) = a_i(i) * exp( P1_i(i) );
      R2_i(i) = exp( P2_i(i) );
    }
    // Likelihood for delta-models with continuous positive support
    if(ObsModel(0)==0 | ObsModel(0)==1 | ObsModel(0)==2){
      // Presence-absence likelihood
      if( b_i(i) > 0 ){
        LogProb1_i(i) = log( R1_i(i) );
      }else{
        LogProb1_i(i) = log( 1-R1_i(i) );
      }
      // Positive density likelihood -- models with continuous positive support
      if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
        if(ObsModel(0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(c_i(i),0), true);
        if(ObsModel(0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(c_i(i),0),2)/2, SigmaM(c_i(i),0), true); // log-space
        if(ObsModel(0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(c_i(i),0),2), R2_i(i)*pow(SigmaM(c_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
      }else{
        LogProb2_i(i) = 0;
      }
    }
    // Likelihood for Tweedie model with continuous positive support
    if(ObsModel(0)==8){
      LogProb1_i(i) = 0;
      // dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
      LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(c_i(i),0), R1_i(i), R2_i(i), diag_z, Options_vec(5), Options_vec(6), true );
      diag_iz.row(i) = diag_z;
    }
    // Likelihood for models with discrete support 
    if(ObsModel(0)==4 | ObsModel(0)==5 | ObsModel(0)==6 | ObsModel(0)==7){
      if(ObsModel(0)==5){
        // Zero-inflated negative binomial (not numerically stable!)
        var_i(i) = R2_i(i)*(1.0+SigmaM(c_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_i(i),1);
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
      }
      if(ObsModel(0)==6){
        // Conway-Maxwell-Poisson
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_i(i)), true, Options_vec(5));
      }
      if(ObsModel(0)==7){
        // Zero-inflated Poisson
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
      }
      LogProb1_i(i) = 0;
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel(4) isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel(1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -SigmaM(c,2)*exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    // Expected value for predictive distribution in a grid cell
    D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel[1]=1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_i );
  REPORT( P2_i );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // Turn off
  if(n_f<0){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IVECTOR(ObsModel);    // Observation model
  // Slot 0: Distribution for positive catches
  // Slot 1: Link function for encounter probabilities
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IVECTOR(c_i);         // Category for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  //  -- presence/absence
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation
  //  -- positive catch rates
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz
  PARAMETER_ARRAY(logSigmaM);   // Slots: 0=mix1 CV, 1=prob-of-mix1, 2=
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_c, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  array<Type> Omegamean1_sf(n_s, FieldConfig(0));
  Omegamean1_sf.setZero();
  array<Type> Epsilonmean1_sf(n_s, FieldConfig(1));
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
      //Epsilon1_sct.col(t) += Epsilon_rho1 * Epsilon1_sct.col(t-1);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  array<Type> Omegamean2_sf(n_s, FieldConfig(2));
  Omegamean2_sf.setZero();
  array<Type> Epsilonmean2_sf(n_s, FieldConfig(3));
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
      //Epsilon2_sct.col(t) += Epsilon_rho2 * Epsilon2_sct.col(t-1);
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  // Linear predictor (pre-link) for presence/absence component
  vector<Type> P1_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi 
  vector<Type> R1_i(n_i);   
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  vector<Type> P2_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    // Linear predictors
    P1_i(i) = Omega1_sc(s_i(i),c_i(i)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_i(i));
    P2_i(i) = Omega2_sc(s_i(i),c_i(i)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_i(i));
    for( int z=0; z<t_iz.row(0).size(); z++ ){
      if( t_iz(i,z)>=0 & t_iz(i,z)<n_t ){  // isNA doesn't seem to work for IMATRIX type
        P1_i(i) += beta1_ct(c_i(i),t_iz(i,z)) + Epsilon1_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(s_i(i),c_i(i),t_iz(i,z));
        P2_i(i) += beta2_ct(c_i(i),t_iz(i,z)) + Epsilon2_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(s_i(i),c_i(i),t_iz(i,z));
      }
    }
    // Responses
    if( ObsModel(1)==0 ){
      // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
      // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
      // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
      R1_i(i) = invlogit( P1_i(i) );
      R2_i(i) = a_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==1 ){
      // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
      // P2_i: Log-average weight;  R2_i:  Positive density prediction
      R1_i(i) = Type(1.0) - exp( -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i)) );
      R2_i(i) = a_i(i)*exp(P1_i(i)) / R1_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==2 ){
      // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Expected numbers
      // P2_i: Log-average weight;  R2_i:  Expected average weight
      R1_i(i) = a_i(i) * exp( P1_i(i) );
      R2_i(i) = exp( P2_i(i) );
    }
    // Likelihood for delta-models with continuous positive support
    if(ObsModel(0)==0 | ObsModel(0)==1 | ObsModel(0)==2){
      // Presence-absence likelihood
      if( b_i(i) > 0 ){
        LogProb1_i(i) = log( R1_i(i) );
      }else{
        LogProb1_i(i) = log( 1-R1_i(i) );
      }
      // Positive density likelihood -- models with continuous positive support
      if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
        if(ObsModel(0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(c_i(i),0), true);
        if(ObsModel(0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(c_i(i),0),2)/2, SigmaM(c_i(i),0), true); // log-space
        if(ObsModel(0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(c_i(i),0),2), R2_i(i)*pow(SigmaM(c_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
      }else{
        LogProb2_i(i) = 0;
      }
    }
    // Likelihood for Tweedie model with continuous positive support
    if(ObsModel(0)==8){
      LogProb1_i(i) = 0;
      // dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
      LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(c_i(i),0), R1_i(i), R2_i(i), diag_z, Options_vec(5), Options_vec(6), true );
      diag_iz.row(i) = diag_z;
    }
    // Likelihood for models with discrete support 
    if(ObsModel(0)==4 | ObsModel(0)==5 | ObsModel(0)==6 | ObsModel(0)==7){
      if(ObsModel(0)==5){
        // Zero-inflated negative binomial (not numerically stable!)
        var_i(i) = R2_i(i)*(1.0+SigmaM(c_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_i(i),1);
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
      }
      if(ObsModel(0)==6){
        // Conway-Maxwell-Poisson
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_i(i)), true, Options_vec(5));
      }
      if(ObsModel(0)==7){
        // Zero-inflated Poisson
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
      }
      LogProb1_i(i) = 0;
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel(4) isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel(1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -SigmaM(c,2)*exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    // Expected value for predictive distribution in a grid cell
    D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel[1]=1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_i );
  REPORT( P2_i );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // Turn off
  if(n_f<0){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IVECTOR(ObsModel);    // Observation model
  // Slot 0: Distribution for positive catches
  // Slot 1: Link function for encounter probabilities
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IVECTOR(c_i);         // Category for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  //  -- presence/absence
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation
  //  -- positive catch rates
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz
  PARAMETER_ARRAY(logSigmaM);   // Slots: 0=mix1 CV, 1=prob-of-mix1, 2=
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_c, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  array<Type> Omegamean1_sf(n_s, FieldConfig(0));
  Omegamean1_sf.setZero();
  array<Type> Epsilonmean1_sf(n_s, FieldConfig(1));
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
      //Epsilon1_sct.col(t) += Epsilon_rho1 * Epsilon1_sct.col(t-1);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  array<Type> Omegamean2_sf(n_s, FieldConfig(2));
  Omegamean2_sf.setZero();
  array<Type> Epsilonmean2_sf(n_s, FieldConfig(3));
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
      //Epsilon2_sct.col(t) += Epsilon_rho2 * Epsilon2_sct.col(t-1);
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  // Linear predictor (pre-link) for presence/absence component
  vector<Type> P1_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi 
  vector<Type> R1_i(n_i);   
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  vector<Type> P2_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    // Linear predictors
    P1_i(i) = Omega1_sc(s_i(i),c_i(i)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_i(i));
    P2_i(i) = Omega2_sc(s_i(i),c_i(i)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_i(i));
    for( int z=0; z<t_iz.row(0).size(); z++ ){
      if( t_iz(i,z)>=0 & t_iz(i,z)<n_t ){  // isNA doesn't seem to work for IMATRIX type
        P1_i(i) += beta1_ct(c_i(i),t_iz(i,z)) + Epsilon1_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(s_i(i),c_i(i),t_iz(i,z));
        P2_i(i) += beta2_ct(c_i(i),t_iz(i,z)) + Epsilon2_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(s_i(i),c_i(i),t_iz(i,z));
      }
    }
    // Responses
    if( ObsModel(1)==0 ){
      // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
      // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
      // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
      R1_i(i) = invlogit( P1_i(i) );
      R2_i(i) = a_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==1 ){
      // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
      // P2_i: Log-average weight;  R2_i:  Positive density prediction
      R1_i(i) = Type(1.0) - exp( -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i)) );
      R2_i(i) = a_i(i)*exp(P1_i(i)) / R1_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==2 ){
      // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Expected numbers
      // P2_i: Log-average weight;  R2_i:  Expected average weight
      R1_i(i) = a_i(i) * exp( P1_i(i) );
      R2_i(i) = exp( P2_i(i) );
    }
    // Likelihood for delta-models with continuous positive support
    if(ObsModel(0)==0 | ObsModel(0)==1 | ObsModel(0)==2){
      // Presence-absence likelihood
      if( b_i(i) > 0 ){
        LogProb1_i(i) = log( R1_i(i) );
      }else{
        LogProb1_i(i) = log( 1-R1_i(i) );
      }
      // Positive density likelihood -- models with continuous positive support
      if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
        if(ObsModel(0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(c_i(i),0), true);
        if(ObsModel(0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(c_i(i),0),2)/2, SigmaM(c_i(i),0), true); // log-space
        if(ObsModel(0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(c_i(i),0),2), R2_i(i)*pow(SigmaM(c_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
      }else{
        LogProb2_i(i) = 0;
      }
    }
    // Likelihood for Tweedie model with continuous positive support
    if(ObsModel(0)==8){
      LogProb1_i(i) = 0;
      // dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
      LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(c_i(i),0), R1_i(i), R2_i(i), diag_z, Options_vec(5), Options_vec(6), true );
      diag_iz.row(i) = diag_z;
    }
    // Likelihood for models with discrete support 
    if(ObsModel(0)==4 | ObsModel(0)==5 | ObsModel(0)==6 | ObsModel(0)==7){
      if(ObsModel(0)==5){
        // Zero-inflated negative binomial (not numerically stable!)
        var_i(i) = R2_i(i)*(1.0+SigmaM(c_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_i(i),1);
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
      }
      if(ObsModel(0)==6){
        // Conway-Maxwell-Poisson
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_i(i)), true, Options_vec(5));
      }
      if(ObsModel(0)==7){
        // Zero-inflated Poisson
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
      }
      LogProb1_i(i) = 0;
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel(4) isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel(1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -SigmaM(c,2)*exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    // Expected value for predictive distribution in a grid cell
    D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel[1]=1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_i );
  REPORT( P2_i );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // Turn off
  if(n_f<0){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IVECTOR(ObsModel);    // Observation model
  // Slot 0: Distribution for positive catches
  // Slot 1: Link function for encounter probabilities
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IVECTOR(c_i);         // Category for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  //  -- presence/absence
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation
  //  -- positive catch rates
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz
  PARAMETER_ARRAY(logSigmaM);   // Slots: 0=mix1 CV, 1=prob-of-mix1, 2=
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_c, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  array<Type> Omegamean1_sf(n_s, FieldConfig(0));
  Omegamean1_sf.setZero();
  array<Type> Epsilonmean1_sf(n_s, FieldConfig(1));
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
      //Epsilon1_sct.col(t) += Epsilon_rho1 * Epsilon1_sct.col(t-1);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  array<Type> Omegamean2_sf(n_s, FieldConfig(2));
  Omegamean2_sf.setZero();
  array<Type> Epsilonmean2_sf(n_s, FieldConfig(3));
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
      //Epsilon2_sct.col(t) += Epsilon_rho2 * Epsilon2_sct.col(t-1);
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  // Linear predictor (pre-link) for presence/absence component
  vector<Type> P1_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi 
  vector<Type> R1_i(n_i);   
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  vector<Type> P2_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    // Linear predictors
    P1_i(i) = Omega1_sc(s_i(i),c_i(i)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_i(i));
    P2_i(i) = Omega2_sc(s_i(i),c_i(i)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_i(i));
    for( int z=0; z<t_iz.row(0).size(); z++ ){
      if( t_iz(i,z)>=0 & t_iz(i,z)<n_t ){  // isNA doesn't seem to work for IMATRIX type
        P1_i(i) += beta1_ct(c_i(i),t_iz(i,z)) + Epsilon1_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(s_i(i),c_i(i),t_iz(i,z));
        P2_i(i) += beta2_ct(c_i(i),t_iz(i,z)) + Epsilon2_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(s_i(i),c_i(i),t_iz(i,z));
      }
    }
    // Responses
    if( ObsModel(1)==0 ){
      // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
      // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
      // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
      R1_i(i) = invlogit( P1_i(i) );
      R2_i(i) = a_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==1 ){
      // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
      // P2_i: Log-average weight;  R2_i:  Positive density prediction
      R1_i(i) = Type(1.0) - exp( -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i)) );
      R2_i(i) = a_i(i)*exp(P1_i(i)) / R1_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==2 ){
      // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Expected numbers
      // P2_i: Log-average weight;  R2_i:  Expected average weight
      R1_i(i) = a_i(i) * exp( P1_i(i) );
      R2_i(i) = exp( P2_i(i) );
    }
    // Likelihood for delta-models with continuous positive support
    if(ObsModel(0)==0 | ObsModel(0)==1 | ObsModel(0)==2){
      // Presence-absence likelihood
      if( b_i(i) > 0 ){
        LogProb1_i(i) = log( R1_i(i) );
      }else{
        LogProb1_i(i) = log( 1-R1_i(i) );
      }
      // Positive density likelihood -- models with continuous positive support
      if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
        if(ObsModel(0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(c_i(i),0), true);
        if(ObsModel(0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(c_i(i),0),2)/2, SigmaM(c_i(i),0), true); // log-space
        if(ObsModel(0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(c_i(i),0),2), R2_i(i)*pow(SigmaM(c_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
      }else{
        LogProb2_i(i) = 0;
      }
    }
    // Likelihood for Tweedie model with continuous positive support
    if(ObsModel(0)==8){
      LogProb1_i(i) = 0;
      // dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
      LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(c_i(i),0), R1_i(i), R2_i(i), diag_z, Options_vec(5), Options_vec(6), true );
      diag_iz.row(i) = diag_z;
    }
    // Likelihood for models with discrete support 
    if(ObsModel(0)==4 | ObsModel(0)==5 | ObsModel(0)==6 | ObsModel(0)==7){
      if(ObsModel(0)==5){
        // Zero-inflated negative binomial (not numerically stable!)
        var_i(i) = R2_i(i)*(1.0+SigmaM(c_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_i(i),1);
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
      }
      if(ObsModel(0)==6){
        // Conway-Maxwell-Poisson
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_i(i)), true, Options_vec(5));
      }
      if(ObsModel(0)==7){
        // Zero-inflated Poisson
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
      }
      LogProb1_i(i) = 0;
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel(4) isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel(1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -SigmaM(c,2)*exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    // Expected value for predictive distribution in a grid cell
    D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel[1]=1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_i );
  REPORT( P2_i );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // Turn off
  if(n_f<0){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IVECTOR(ObsModel);    // Observation model
  // Slot 0: Distribution for positive catches
  // Slot 1: Link function for encounter probabilities
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IVECTOR(c_i);         // Category for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  //  -- presence/absence
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation
  //  -- positive catch rates
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz
  PARAMETER_ARRAY(logSigmaM);   // Slots: 0=mix1 CV, 1=prob-of-mix1, 2=
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_c, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  array<Type> Omegamean1_sf(n_s, FieldConfig(0));
  Omegamean1_sf.setZero();
  array<Type> Epsilonmean1_sf(n_s, FieldConfig(1));
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
      //Epsilon1_sct.col(t) += Epsilon_rho1 * Epsilon1_sct.col(t-1);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  array<Type> Omegamean2_sf(n_s, FieldConfig(2));
  Omegamean2_sf.setZero();
  array<Type> Epsilonmean2_sf(n_s, FieldConfig(3));
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
      //Epsilon2_sct.col(t) += Epsilon_rho2 * Epsilon2_sct.col(t-1);
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  // Linear predictor (pre-link) for presence/absence component
  vector<Type> P1_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi 
  vector<Type> R1_i(n_i);   
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  vector<Type> P2_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    // Linear predictors
    P1_i(i) = Omega1_sc(s_i(i),c_i(i)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_i(i));
    P2_i(i) = Omega2_sc(s_i(i),c_i(i)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_i(i));
    for( int z=0; z<t_iz.row(0).size(); z++ ){
      if( t_iz(i,z)>=0 & t_iz(i,z)<n_t ){  // isNA doesn't seem to work for IMATRIX type
        P1_i(i) += beta1_ct(c_i(i),t_iz(i,z)) + Epsilon1_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(s_i(i),c_i(i),t_iz(i,z));
        P2_i(i) += beta2_ct(c_i(i),t_iz(i,z)) + Epsilon2_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(s_i(i),c_i(i),t_iz(i,z));
      }
    }
    // Responses
    if( ObsModel(1)==0 ){
      // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
      // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
      // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
      R1_i(i) = invlogit( P1_i(i) );
      R2_i(i) = a_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==1 ){
      // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
      // P2_i: Log-average weight;  R2_i:  Positive density prediction
      R1_i(i) = Type(1.0) - exp( -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i)) );
      R2_i(i) = a_i(i)*exp(P1_i(i)) / R1_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==2 ){
      // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Expected numbers
      // P2_i: Log-average weight;  R2_i:  Expected average weight
      R1_i(i) = a_i(i) * exp( P1_i(i) );
      R2_i(i) = exp( P2_i(i) );
    }
    // Likelihood for delta-models with continuous positive support
    if(ObsModel(0)==0 | ObsModel(0)==1 | ObsModel(0)==2){
      // Presence-absence likelihood
      if( b_i(i) > 0 ){
        LogProb1_i(i) = log( R1_i(i) );
      }else{
        LogProb1_i(i) = log( 1-R1_i(i) );
      }
      // Positive density likelihood -- models with continuous positive support
      if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
        if(ObsModel(0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(c_i(i),0), true);
        if(ObsModel(0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(c_i(i),0),2)/2, SigmaM(c_i(i),0), true); // log-space
        if(ObsModel(0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(c_i(i),0),2), R2_i(i)*pow(SigmaM(c_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
      }else{
        LogProb2_i(i) = 0;
      }
    }
    // Likelihood for Tweedie model with continuous positive support
    if(ObsModel(0)==8){
      LogProb1_i(i) = 0;
      // dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
      LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(c_i(i),0), R1_i(i), R2_i(i), diag_z, Options_vec(5), Options_vec(6), true );
      diag_iz.row(i) = diag_z;
    }
    // Likelihood for models with discrete support 
    if(ObsModel(0)==4 | ObsModel(0)==5 | ObsModel(0)==6 | ObsModel(0)==7){
      if(ObsModel(0)==5){
        // Zero-inflated negative binomial (not numerically stable!)
        var_i(i) = R2_i(i)*(1.0+SigmaM(c_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_i(i),1);
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
      }
      if(ObsModel(0)==6){
        // Conway-Maxwell-Poisson
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_i(i)), true, Options_vec(5));
      }
      if(ObsModel(0)==7){
        // Zero-inflated Poisson
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
      }
      LogProb1_i(i) = 0;
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel(4) isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel(1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -SigmaM(c,2)*exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    // Expected value for predictive distribution in a grid cell
    D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel[1]=1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_i );
  REPORT( P2_i );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // Turn off
  if(n_f<0){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IVECTOR(ObsModel);    // Observation model
  // Slot 0: Distribution for positive catches
  // Slot 1: Link function for encounter probabilities
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IVECTOR(c_i);         // Category for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  //  -- presence/absence
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation
  //  -- positive catch rates
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz
  PARAMETER_ARRAY(logSigmaM);   // Slots: 0=mix1 CV, 1=prob-of-mix1, 2=
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_c, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  array<Type> Omegamean1_sf(n_s, FieldConfig(0));
  Omegamean1_sf.setZero();
  array<Type> Epsilonmean1_sf(n_s, FieldConfig(1));
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
      //Epsilon1_sct.col(t) += Epsilon_rho1 * Epsilon1_sct.col(t-1);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  array<Type> Omegamean2_sf(n_s, FieldConfig(2));
  Omegamean2_sf.setZero();
  array<Type> Epsilonmean2_sf(n_s, FieldConfig(3));
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
      //Epsilon2_sct.col(t) += Epsilon_rho2 * Epsilon2_sct.col(t-1);
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  // Linear predictor (pre-link) for presence/absence component
  vector<Type> P1_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi 
  vector<Type> R1_i(n_i);   
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  vector<Type> P2_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    // Linear predictors
    P1_i(i) = Omega1_sc(s_i(i),c_i(i)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_i(i));
    P2_i(i) = Omega2_sc(s_i(i),c_i(i)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_i(i));
    for( int z=0; z<t_iz.row(0).size(); z++ ){
      if( t_iz(i,z)>=0 & t_iz(i,z)<n_t ){  // isNA doesn't seem to work for IMATRIX type
        P1_i(i) += beta1_ct(c_i(i),t_iz(i,z)) + Epsilon1_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(s_i(i),c_i(i),t_iz(i,z));
        P2_i(i) += beta2_ct(c_i(i),t_iz(i,z)) + Epsilon2_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(s_i(i),c_i(i),t_iz(i,z));
      }
    }
    // Responses
    if( ObsModel(1)==0 ){
      // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
      // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
      // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
      R1_i(i) = invlogit( P1_i(i) );
      R2_i(i) = a_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==1 ){
      // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
      // P2_i: Log-average weight;  R2_i:  Positive density prediction
      R1_i(i) = Type(1.0) - exp( -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i)) );
      R2_i(i) = a_i(i)*exp(P1_i(i)) / R1_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==2 ){
      // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Expected numbers
      // P2_i: Log-average weight;  R2_i:  Expected average weight
      R1_i(i) = a_i(i) * exp( P1_i(i) );
      R2_i(i) = exp( P2_i(i) );
    }
    // Likelihood for delta-models with continuous positive support
    if(ObsModel(0)==0 | ObsModel(0)==1 | ObsModel(0)==2){
      // Presence-absence likelihood
      if( b_i(i) > 0 ){
        LogProb1_i(i) = log( R1_i(i) );
      }else{
        LogProb1_i(i) = log( 1-R1_i(i) );
      }
      // Positive density likelihood -- models with continuous positive support
      if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
        if(ObsModel(0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(c_i(i),0), true);
        if(ObsModel(0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(c_i(i),0),2)/2, SigmaM(c_i(i),0), true); // log-space
        if(ObsModel(0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(c_i(i),0),2), R2_i(i)*pow(SigmaM(c_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
      }else{
        LogProb2_i(i) = 0;
      }
    }
    // Likelihood for Tweedie model with continuous positive support
    if(ObsModel(0)==8){
      LogProb1_i(i) = 0;
      // dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
      LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(c_i(i),0), R1_i(i), R2_i(i), diag_z, Options_vec(5), Options_vec(6), true );
      diag_iz.row(i) = diag_z;
    }
    // Likelihood for models with discrete support 
    if(ObsModel(0)==4 | ObsModel(0)==5 | ObsModel(0)==6 | ObsModel(0)==7){
      if(ObsModel(0)==5){
        // Zero-inflated negative binomial (not numerically stable!)
        var_i(i) = R2_i(i)*(1.0+SigmaM(c_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_i(i),1);
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
      }
      if(ObsModel(0)==6){
        // Conway-Maxwell-Poisson
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_i(i)), true, Options_vec(5));
      }
      if(ObsModel(0)==7){
        // Zero-inflated Poisson
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
      }
      LogProb1_i(i) = 0;
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel(4) isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel(1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -SigmaM(c,2)*exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    // Expected value for predictive distribution in a grid cell
    D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel[1]=1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_i );
  REPORT( P2_i );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // Turn off
  if(n_f<0){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IVECTOR(ObsModel);    // Observation model
  // Slot 0: Distribution for positive catches
  // Slot 1: Link function for encounter probabilities
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IVECTOR(c_i);         // Category for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  //  -- presence/absence
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation
  //  -- positive catch rates
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz
  PARAMETER_ARRAY(logSigmaM);   // Slots: 0=mix1 CV, 1=prob-of-mix1, 2=
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_c, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  array<Type> Omegamean1_sf(n_s, FieldConfig(0));
  Omegamean1_sf.setZero();
  array<Type> Epsilonmean1_sf(n_s, FieldConfig(1));
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
      //Epsilon1_sct.col(t) += Epsilon_rho1 * Epsilon1_sct.col(t-1);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  array<Type> Omegamean2_sf(n_s, FieldConfig(2));
  Omegamean2_sf.setZero();
  array<Type> Epsilonmean2_sf(n_s, FieldConfig(3));
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
      //Epsilon2_sct.col(t) += Epsilon_rho2 * Epsilon2_sct.col(t-1);
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  // Linear predictor (pre-link) for presence/absence component
  vector<Type> P1_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi 
  vector<Type> R1_i(n_i);   
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  vector<Type> P2_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    // Linear predictors
    P1_i(i) = Omega1_sc(s_i(i),c_i(i)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_i(i));
    P2_i(i) = Omega2_sc(s_i(i),c_i(i)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_i(i));
    for( int z=0; z<t_iz.row(0).size(); z++ ){
      if( t_iz(i,z)>=0 & t_iz(i,z)<n_t ){  // isNA doesn't seem to work for IMATRIX type
        P1_i(i) += beta1_ct(c_i(i),t_iz(i,z)) + Epsilon1_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(s_i(i),c_i(i),t_iz(i,z));
        P2_i(i) += beta2_ct(c_i(i),t_iz(i,z)) + Epsilon2_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(s_i(i),c_i(i),t_iz(i,z));
      }
    }
    // Responses
    if( ObsModel(1)==0 ){
      // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
      // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
      // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
      R1_i(i) = invlogit( P1_i(i) );
      R2_i(i) = a_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==1 ){
      // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
      // P2_i: Log-average weight;  R2_i:  Positive density prediction
      R1_i(i) = Type(1.0) - exp( -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i)) );
      R2_i(i) = a_i(i)*exp(P1_i(i)) / R1_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==2 ){
      // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Expected numbers
      // P2_i: Log-average weight;  R2_i:  Expected average weight
      R1_i(i) = a_i(i) * exp( P1_i(i) );
      R2_i(i) = exp( P2_i(i) );
    }
    // Likelihood for delta-models with continuous positive support
    if(ObsModel(0)==0 | ObsModel(0)==1 | ObsModel(0)==2){
      // Presence-absence likelihood
      if( b_i(i) > 0 ){
        LogProb1_i(i) = log( R1_i(i) );
      }else{
        LogProb1_i(i) = log( 1-R1_i(i) );
      }
      // Positive density likelihood -- models with continuous positive support
      if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
        if(ObsModel(0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(c_i(i),0), true);
        if(ObsModel(0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(c_i(i),0),2)/2, SigmaM(c_i(i),0), true); // log-space
        if(ObsModel(0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(c_i(i),0),2), R2_i(i)*pow(SigmaM(c_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
      }else{
        LogProb2_i(i) = 0;
      }
    }
    // Likelihood for Tweedie model with continuous positive support
    if(ObsModel(0)==8){
      LogProb1_i(i) = 0;
      // dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
      LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(c_i(i),0), R1_i(i), R2_i(i), diag_z, Options_vec(5), Options_vec(6), true );
      diag_iz.row(i) = diag_z;
    }
    // Likelihood for models with discrete support 
    if(ObsModel(0)==4 | ObsModel(0)==5 | ObsModel(0)==6 | ObsModel(0)==7){
      if(ObsModel(0)==5){
        // Zero-inflated negative binomial (not numerically stable!)
        var_i(i) = R2_i(i)*(1.0+SigmaM(c_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_i(i),1);
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
      }
      if(ObsModel(0)==6){
        // Conway-Maxwell-Poisson
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_i(i)), true, Options_vec(5));
      }
      if(ObsModel(0)==7){
        // Zero-inflated Poisson
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
      }
      LogProb1_i(i) = 0;
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel(4) isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel(1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -SigmaM(c,2)*exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    // Expected value for predictive distribution in a grid cell
    D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel[1]=1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_i );
  REPORT( P2_i );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // Turn off
  if(n_f<0){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IVECTOR(ObsModel);    // Observation model
  // Slot 0: Distribution for positive catches
  // Slot 1: Link function for encounter probabilities
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IVECTOR(c_i);         // Category for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  //  -- presence/absence
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation
  //  -- positive catch rates
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz
  PARAMETER_ARRAY(logSigmaM);   // Slots: 0=mix1 CV, 1=prob-of-mix1, 2=
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_c, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  array<Type> Omegamean1_sf(n_s, FieldConfig(0));
  Omegamean1_sf.setZero();
  array<Type> Epsilonmean1_sf(n_s, FieldConfig(1));
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
      //Epsilon1_sct.col(t) += Epsilon_rho1 * Epsilon1_sct.col(t-1);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  array<Type> Omegamean2_sf(n_s, FieldConfig(2));
  Omegamean2_sf.setZero();
  array<Type> Epsilonmean2_sf(n_s, FieldConfig(3));
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
      //Epsilon2_sct.col(t) += Epsilon_rho2 * Epsilon2_sct.col(t-1);
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  // Linear predictor (pre-link) for presence/absence component
  vector<Type> P1_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi 
  vector<Type> R1_i(n_i);   
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  vector<Type> P2_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    // Linear predictors
    P1_i(i) = Omega1_sc(s_i(i),c_i(i)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_i(i));
    P2_i(i) = Omega2_sc(s_i(i),c_i(i)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_i(i));
    for( int z=0; z<t_iz.row(0).size(); z++ ){
      if( t_iz(i,z)>=0 & t_iz(i,z)<n_t ){  // isNA doesn't seem to work for IMATRIX type
        P1_i(i) += beta1_ct(c_i(i),t_iz(i,z)) + Epsilon1_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(s_i(i),c_i(i),t_iz(i,z));
        P2_i(i) += beta2_ct(c_i(i),t_iz(i,z)) + Epsilon2_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(s_i(i),c_i(i),t_iz(i,z));
      }
    }
    // Responses
    if( ObsModel(1)==0 ){
      // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
      // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
      // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
      R1_i(i) = invlogit( P1_i(i) );
      R2_i(i) = a_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==1 ){
      // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
      // P2_i: Log-average weight;  R2_i:  Positive density prediction
      R1_i(i) = Type(1.0) - exp( -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i)) );
      R2_i(i) = a_i(i)*exp(P1_i(i)) / R1_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==2 ){
      // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Expected numbers
      // P2_i: Log-average weight;  R2_i:  Expected average weight
      R1_i(i) = a_i(i) * exp( P1_i(i) );
      R2_i(i) = exp( P2_i(i) );
    }
    // Likelihood for delta-models with continuous positive support
    if(ObsModel(0)==0 | ObsModel(0)==1 | ObsModel(0)==2){
      // Presence-absence likelihood
      if( b_i(i) > 0 ){
        LogProb1_i(i) = log( R1_i(i) );
      }else{
        LogProb1_i(i) = log( 1-R1_i(i) );
      }
      // Positive density likelihood -- models with continuous positive support
      if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
        if(ObsModel(0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(c_i(i),0), true);
        if(ObsModel(0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(c_i(i),0),2)/2, SigmaM(c_i(i),0), true); // log-space
        if(ObsModel(0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(c_i(i),0),2), R2_i(i)*pow(SigmaM(c_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
      }else{
        LogProb2_i(i) = 0;
      }
    }
    // Likelihood for Tweedie model with continuous positive support
    if(ObsModel(0)==8){
      LogProb1_i(i) = 0;
      // dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
      LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(c_i(i),0), R1_i(i), R2_i(i), diag_z, Options_vec(5), Options_vec(6), true );
      diag_iz.row(i) = diag_z;
    }
    // Likelihood for models with discrete support 
    if(ObsModel(0)==4 | ObsModel(0)==5 | ObsModel(0)==6 | ObsModel(0)==7){
      if(ObsModel(0)==5){
        // Zero-inflated negative binomial (not numerically stable!)
        var_i(i) = R2_i(i)*(1.0+SigmaM(c_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_i(i),1);
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
      }
      if(ObsModel(0)==6){
        // Conway-Maxwell-Poisson
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_i(i)), true, Options_vec(5));
      }
      if(ObsModel(0)==7){
        // Zero-inflated Poisson
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
      }
      LogProb1_i(i) = 0;
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel(4) isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel(1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -SigmaM(c,2)*exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    // Expected value for predictive distribution in a grid cell
    D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel[1]=1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_i );
  REPORT( P2_i );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // Turn off
  if(n_f<0){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IVECTOR(ObsModel);    // Observation model
  // Slot 0: Distribution for positive catches
  // Slot 1: Link function for encounter probabilities
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IVECTOR(c_i);         // Category for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  //  -- presence/absence
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation
  //  -- positive catch rates
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz
  PARAMETER_ARRAY(logSigmaM);   // Slots: 0=mix1 CV, 1=prob-of-mix1, 2=
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_c, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  array<Type> Omegamean1_sf(n_s, FieldConfig(0));
  Omegamean1_sf.setZero();
  array<Type> Epsilonmean1_sf(n_s, FieldConfig(1));
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
      //Epsilon1_sct.col(t) += Epsilon_rho1 * Epsilon1_sct.col(t-1);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  array<Type> Omegamean2_sf(n_s, FieldConfig(2));
  Omegamean2_sf.setZero();
  array<Type> Epsilonmean2_sf(n_s, FieldConfig(3));
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
      //Epsilon2_sct.col(t) += Epsilon_rho2 * Epsilon2_sct.col(t-1);
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  // Linear predictor (pre-link) for presence/absence component
  vector<Type> P1_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi 
  vector<Type> R1_i(n_i);   
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  vector<Type> P2_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    // Linear predictors
    P1_i(i) = Omega1_sc(s_i(i),c_i(i)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_i(i));
    P2_i(i) = Omega2_sc(s_i(i),c_i(i)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_i(i));
    for( int z=0; z<t_iz.row(0).size(); z++ ){
      if( t_iz(i,z)>=0 & t_iz(i,z)<n_t ){  // isNA doesn't seem to work for IMATRIX type
        P1_i(i) += beta1_ct(c_i(i),t_iz(i,z)) + Epsilon1_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(s_i(i),c_i(i),t_iz(i,z));
        P2_i(i) += beta2_ct(c_i(i),t_iz(i,z)) + Epsilon2_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(s_i(i),c_i(i),t_iz(i,z));
      }
    }
    // Responses
    if( ObsModel(1)==0 ){
      // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
      // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
      // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
      R1_i(i) = invlogit( P1_i(i) );
      R2_i(i) = a_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==1 ){
      // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
      // P2_i: Log-average weight;  R2_i:  Positive density prediction
      R1_i(i) = Type(1.0) - exp( -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i)) );
      R2_i(i) = a_i(i)*exp(P1_i(i)) / R1_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==2 ){
      // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Expected numbers
      // P2_i: Log-average weight;  R2_i:  Expected average weight
      R1_i(i) = a_i(i) * exp( P1_i(i) );
      R2_i(i) = exp( P2_i(i) );
    }
    // Likelihood for delta-models with continuous positive support
    if(ObsModel(0)==0 | ObsModel(0)==1 | ObsModel(0)==2){
      // Presence-absence likelihood
      if( b_i(i) > 0 ){
        LogProb1_i(i) = log( R1_i(i) );
      }else{
        LogProb1_i(i) = log( 1-R1_i(i) );
      }
      // Positive density likelihood -- models with continuous positive support
      if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
        if(ObsModel(0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(c_i(i),0), true);
        if(ObsModel(0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(c_i(i),0),2)/2, SigmaM(c_i(i),0), true); // log-space
        if(ObsModel(0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(c_i(i),0),2), R2_i(i)*pow(SigmaM(c_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
      }else{
        LogProb2_i(i) = 0;
      }
    }
    // Likelihood for Tweedie model with continuous positive support
    if(ObsModel(0)==8){
      LogProb1_i(i) = 0;
      // dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
      LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(c_i(i),0), R1_i(i), R2_i(i), diag_z, Options_vec(5), Options_vec(6), true );
      diag_iz.row(i) = diag_z;
    }
    // Likelihood for models with discrete support 
    if(ObsModel(0)==4 | ObsModel(0)==5 | ObsModel(0)==6 | ObsModel(0)==7){
      if(ObsModel(0)==5){
        // Zero-inflated negative binomial (not numerically stable!)
        var_i(i) = R2_i(i)*(1.0+SigmaM(c_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_i(i),1);
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
      }
      if(ObsModel(0)==6){
        // Conway-Maxwell-Poisson
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_i(i)), true, Options_vec(5));
      }
      if(ObsModel(0)==7){
        // Zero-inflated Poisson
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
      }
      LogProb1_i(i) = 0;
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel(4) isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel(1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -SigmaM(c,2)*exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    // Expected value for predictive distribution in a grid cell
    D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel[1]=1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_i );
  REPORT( P2_i );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // Turn off
  if(n_f<0){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IVECTOR(ObsModel);    // Observation model
  // Slot 0: Distribution for positive catches
  // Slot 1: Link function for encounter probabilities
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IVECTOR(c_i);         // Category for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  //  -- presence/absence
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation
  //  -- positive catch rates
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz
  PARAMETER_ARRAY(logSigmaM);   // Slots: 0=mix1 CV, 1=prob-of-mix1, 2=
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_c, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  array<Type> Omegamean1_sf(n_s, FieldConfig(0));
  Omegamean1_sf.setZero();
  array<Type> Epsilonmean1_sf(n_s, FieldConfig(1));
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
      //Epsilon1_sct.col(t) += Epsilon_rho1 * Epsilon1_sct.col(t-1);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  array<Type> Omegamean2_sf(n_s, FieldConfig(2));
  Omegamean2_sf.setZero();
  array<Type> Epsilonmean2_sf(n_s, FieldConfig(3));
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
      //Epsilon2_sct.col(t) += Epsilon_rho2 * Epsilon2_sct.col(t-1);
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  // Linear predictor (pre-link) for presence/absence component
  vector<Type> P1_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi 
  vector<Type> R1_i(n_i);   
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  vector<Type> P2_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    // Linear predictors
    P1_i(i) = Omega1_sc(s_i(i),c_i(i)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_i(i));
    P2_i(i) = Omega2_sc(s_i(i),c_i(i)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_i(i));
    for( int z=0; z<t_iz.row(0).size(); z++ ){
      if( t_iz(i,z)>=0 & t_iz(i,z)<n_t ){  // isNA doesn't seem to work for IMATRIX type
        P1_i(i) += beta1_ct(c_i(i),t_iz(i,z)) + Epsilon1_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(s_i(i),c_i(i),t_iz(i,z));
        P2_i(i) += beta2_ct(c_i(i),t_iz(i,z)) + Epsilon2_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(s_i(i),c_i(i),t_iz(i,z));
      }
    }
    // Responses
    if( ObsModel(1)==0 ){
      // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
      // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
      // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
      R1_i(i) = invlogit( P1_i(i) );
      R2_i(i) = a_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==1 ){
      // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
      // P2_i: Log-average weight;  R2_i:  Positive density prediction
      R1_i(i) = Type(1.0) - exp( -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i)) );
      R2_i(i) = a_i(i)*exp(P1_i(i)) / R1_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==2 ){
      // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Expected numbers
      // P2_i: Log-average weight;  R2_i:  Expected average weight
      R1_i(i) = a_i(i) * exp( P1_i(i) );
      R2_i(i) = exp( P2_i(i) );
    }
    // Likelihood for delta-models with continuous positive support
    if(ObsModel(0)==0 | ObsModel(0)==1 | ObsModel(0)==2){
      // Presence-absence likelihood
      if( b_i(i) > 0 ){
        LogProb1_i(i) = log( R1_i(i) );
      }else{
        LogProb1_i(i) = log( 1-R1_i(i) );
      }
      // Positive density likelihood -- models with continuous positive support
      if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
        if(ObsModel(0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(c_i(i),0), true);
        if(ObsModel(0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(c_i(i),0),2)/2, SigmaM(c_i(i),0), true); // log-space
        if(ObsModel(0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(c_i(i),0),2), R2_i(i)*pow(SigmaM(c_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
      }else{
        LogProb2_i(i) = 0;
      }
    }
    // Likelihood for Tweedie model with continuous positive support
    if(ObsModel(0)==8){
      LogProb1_i(i) = 0;
      // dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
      LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(c_i(i),0), R1_i(i), R2_i(i), diag_z, Options_vec(5), Options_vec(6), true );
      diag_iz.row(i) = diag_z;
    }
    // Likelihood for models with discrete support 
    if(ObsModel(0)==4 | ObsModel(0)==5 | ObsModel(0)==6 | ObsModel(0)==7){
      if(ObsModel(0)==5){
        // Zero-inflated negative binomial (not numerically stable!)
        var_i(i) = R2_i(i)*(1.0+SigmaM(c_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_i(i),1);
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
      }
      if(ObsModel(0)==6){
        // Conway-Maxwell-Poisson
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_i(i)), true, Options_vec(5));
      }
      if(ObsModel(0)==7){
        // Zero-inflated Poisson
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
      }
      LogProb1_i(i) = 0;
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel(4) isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel(1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -SigmaM(c,2)*exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    // Expected value for predictive distribution in a grid cell
    D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel[1]=1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_i );
  REPORT( P2_i );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // Turn off
  if(n_f<0){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IVECTOR(ObsModel);    // Observation model
  // Slot 0: Distribution for positive catches
  // Slot 1: Link function for encounter probabilities
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IVECTOR(c_i);         // Category for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  //  -- presence/absence
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation
  //  -- positive catch rates
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz
  PARAMETER_ARRAY(logSigmaM);   // Slots: 0=mix1 CV, 1=prob-of-mix1, 2=
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_c, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  array<Type> Omegamean1_sf(n_s, FieldConfig(0));
  Omegamean1_sf.setZero();
  array<Type> Epsilonmean1_sf(n_s, FieldConfig(1));
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
      //Epsilon1_sct.col(t) += Epsilon_rho1 * Epsilon1_sct.col(t-1);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  array<Type> Omegamean2_sf(n_s, FieldConfig(2));
  Omegamean2_sf.setZero();
  array<Type> Epsilonmean2_sf(n_s, FieldConfig(3));
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
      //Epsilon2_sct.col(t) += Epsilon_rho2 * Epsilon2_sct.col(t-1);
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  // Linear predictor (pre-link) for presence/absence component
  vector<Type> P1_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi 
  vector<Type> R1_i(n_i);   
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  vector<Type> P2_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    // Linear predictors
    P1_i(i) = Omega1_sc(s_i(i),c_i(i)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_i(i));
    P2_i(i) = Omega2_sc(s_i(i),c_i(i)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_i(i));
    for( int z=0; z<t_iz.row(0).size(); z++ ){
      if( t_iz(i,z)>=0 & t_iz(i,z)<n_t ){  // isNA doesn't seem to work for IMATRIX type
        P1_i(i) += beta1_ct(c_i(i),t_iz(i,z)) + Epsilon1_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(s_i(i),c_i(i),t_iz(i,z));
        P2_i(i) += beta2_ct(c_i(i),t_iz(i,z)) + Epsilon2_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(s_i(i),c_i(i),t_iz(i,z));
      }
    }
    // Responses
    if( ObsModel(1)==0 ){
      // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
      // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
      // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
      R1_i(i) = invlogit( P1_i(i) );
      R2_i(i) = a_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==1 ){
      // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
      // P2_i: Log-average weight;  R2_i:  Positive density prediction
      R1_i(i) = Type(1.0) - exp( -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i)) );
      R2_i(i) = a_i(i)*exp(P1_i(i)) / R1_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==2 ){
      // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Expected numbers
      // P2_i: Log-average weight;  R2_i:  Expected average weight
      R1_i(i) = a_i(i) * exp( P1_i(i) );
      R2_i(i) = exp( P2_i(i) );
    }
    // Likelihood for delta-models with continuous positive support
    if(ObsModel(0)==0 | ObsModel(0)==1 | ObsModel(0)==2){
      // Presence-absence likelihood
      if( b_i(i) > 0 ){
        LogProb1_i(i) = log( R1_i(i) );
      }else{
        LogProb1_i(i) = log( 1-R1_i(i) );
      }
      // Positive density likelihood -- models with continuous positive support
      if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
        if(ObsModel(0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(c_i(i),0), true);
        if(ObsModel(0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(c_i(i),0),2)/2, SigmaM(c_i(i),0), true); // log-space
        if(ObsModel(0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(c_i(i),0),2), R2_i(i)*pow(SigmaM(c_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
      }else{
        LogProb2_i(i) = 0;
      }
    }
    // Likelihood for Tweedie model with continuous positive support
    if(ObsModel(0)==8){
      LogProb1_i(i) = 0;
      // dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
      LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(c_i(i),0), R1_i(i), R2_i(i), diag_z, Options_vec(5), Options_vec(6), true );
      diag_iz.row(i) = diag_z;
    }
    // Likelihood for models with discrete support 
    if(ObsModel(0)==4 | ObsModel(0)==5 | ObsModel(0)==6 | ObsModel(0)==7){
      if(ObsModel(0)==5){
        // Zero-inflated negative binomial (not numerically stable!)
        var_i(i) = R2_i(i)*(1.0+SigmaM(c_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_i(i),1);
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
      }
      if(ObsModel(0)==6){
        // Conway-Maxwell-Poisson
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_i(i)), true, Options_vec(5));
      }
      if(ObsModel(0)==7){
        // Zero-inflated Poisson
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
      }
      LogProb1_i(i) = 0;
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel(4) isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel(1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -SigmaM(c,2)*exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    // Expected value for predictive distribution in a grid cell
    D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel[1]=1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_i );
  REPORT( P2_i );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // Turn off
  if(n_f<0){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IVECTOR(ObsModel);    // Observation model
  // Slot 0: Distribution for positive catches
  // Slot 1: Link function for encounter probabilities
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IVECTOR(c_i);         // Category for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  //  -- presence/absence
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation
  //  -- positive catch rates
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz
  PARAMETER_ARRAY(logSigmaM);   // Slots: 0=mix1 CV, 1=prob-of-mix1, 2=
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_c, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  array<Type> Omegamean1_sf(n_s, FieldConfig(0));
  Omegamean1_sf.setZero();
  array<Type> Epsilonmean1_sf(n_s, FieldConfig(1));
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
      //Epsilon1_sct.col(t) += Epsilon_rho1 * Epsilon1_sct.col(t-1);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  array<Type> Omegamean2_sf(n_s, FieldConfig(2));
  Omegamean2_sf.setZero();
  array<Type> Epsilonmean2_sf(n_s, FieldConfig(3));
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
      //Epsilon2_sct.col(t) += Epsilon_rho2 * Epsilon2_sct.col(t-1);
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  // Linear predictor (pre-link) for presence/absence component
  vector<Type> P1_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi 
  vector<Type> R1_i(n_i);   
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  vector<Type> P2_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    // Linear predictors
    P1_i(i) = Omega1_sc(s_i(i),c_i(i)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_i(i));
    P2_i(i) = Omega2_sc(s_i(i),c_i(i)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_i(i));
    for( int z=0; z<t_iz.row(0).size(); z++ ){
      if( t_iz(i,z)>=0 & t_iz(i,z)<n_t ){  // isNA doesn't seem to work for IMATRIX type
        P1_i(i) += beta1_ct(c_i(i),t_iz(i,z)) + Epsilon1_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(s_i(i),c_i(i),t_iz(i,z));
        P2_i(i) += beta2_ct(c_i(i),t_iz(i,z)) + Epsilon2_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(s_i(i),c_i(i),t_iz(i,z));
      }
    }
    // Responses
    if( ObsModel(1)==0 ){
      // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
      // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
      // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
      R1_i(i) = invlogit( P1_i(i) );
      R2_i(i) = a_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==1 ){
      // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
      // P2_i: Log-average weight;  R2_i:  Positive density prediction
      R1_i(i) = Type(1.0) - exp( -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i)) );
      R2_i(i) = a_i(i)*exp(P1_i(i)) / R1_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==2 ){
      // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Expected numbers
      // P2_i: Log-average weight;  R2_i:  Expected average weight
      R1_i(i) = a_i(i) * exp( P1_i(i) );
      R2_i(i) = exp( P2_i(i) );
    }
    // Likelihood for delta-models with continuous positive support
    if(ObsModel(0)==0 | ObsModel(0)==1 | ObsModel(0)==2){
      // Presence-absence likelihood
      if( b_i(i) > 0 ){
        LogProb1_i(i) = log( R1_i(i) );
      }else{
        LogProb1_i(i) = log( 1-R1_i(i) );
      }
      // Positive density likelihood -- models with continuous positive support
      if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
        if(ObsModel(0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(c_i(i),0), true);
        if(ObsModel(0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(c_i(i),0),2)/2, SigmaM(c_i(i),0), true); // log-space
        if(ObsModel(0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(c_i(i),0),2), R2_i(i)*pow(SigmaM(c_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
      }else{
        LogProb2_i(i) = 0;
      }
    }
    // Likelihood for Tweedie model with continuous positive support
    if(ObsModel(0)==8){
      LogProb1_i(i) = 0;
      // dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
      LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(c_i(i),0), R1_i(i), R2_i(i), diag_z, Options_vec(5), Options_vec(6), true );
      diag_iz.row(i) = diag_z;
    }
    // Likelihood for models with discrete support 
    if(ObsModel(0)==4 | ObsModel(0)==5 | ObsModel(0)==6 | ObsModel(0)==7){
      if(ObsModel(0)==5){
        // Zero-inflated negative binomial (not numerically stable!)
        var_i(i) = R2_i(i)*(1.0+SigmaM(c_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_i(i),1);
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
      }
      if(ObsModel(0)==6){
        // Conway-Maxwell-Poisson
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_i(i)), true, Options_vec(5));
      }
      if(ObsModel(0)==7){
        // Zero-inflated Poisson
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
      }
      LogProb1_i(i) = 0;
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel(4) isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel(1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -SigmaM(c,2)*exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    // Expected value for predictive distribution in a grid cell
    D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel[1]=1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_i );
  REPORT( P2_i );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#include <TMB.hpp>
#include <Eigen/Eigenvalues>

// Function for detecting NAs
template<class Type>
bool isNA(Type x){
  return R_IsNA(asDouble(x));
}

// Posfun
template<class Type>
Type posfun(Type x, Type lowerlimit, Type &pen){
  pen += CppAD::CondExpLt(x,lowerlimit,Type(0.01)*pow(x-lowerlimit,2),Type(0));
  return CppAD::CondExpGe(x,lowerlimit,x,lowerlimit/(Type(2)-x/lowerlimit));
}

// Variance
template<class Type>
Type var( array<Type> vec ){
  Type vec_mod = vec - (vec.sum()/vec.size());
  Type res = pow(vec_mod, 2).sum() / vec.size();
  return res;
}

// dlnorm
template<class Type>
Type dlnorm(Type x, Type meanlog, Type sdlog, int give_log=0){
  //return 1/(sqrt(2*M_PI)*sd)*exp(-.5*pow((x-mean)/sd,2));
  Type logres = dnorm( log(x), meanlog, sdlog, true) - log(x);
  if(give_log) return logres; else return exp(logres);
}

// Generate loadings matrix
template<class Type>
matrix<Type> loadings_matrix( vector<Type> L_val, int n_rows, int n_cols ){
  matrix<Type> L_rc(n_rows, n_cols);
  int Count = 0;
  for(int r=0; r<n_rows; r++){
  for(int c=0; c<n_cols; c++){
    if(r>=c){
      L_rc(r,c) = L_val(Count);
      Count++;
    }else{
      L_rc(r,c) = 0.0;
    }
  }}
  return L_rc;
}

// IN: eta1_vf; L1_z
// OUT: jnll_comp; eta1_vc
template<class Type>
matrix<Type> overdispersion_by_category_nll( int n_f, int n_v, int n_c, matrix<Type> eta_vf, vector<Type> L_z, Type &jnll_pointer){
  using namespace density;
  matrix<Type> eta_vc(n_v, n_c);
  vector<Type> Tmp_c;
  // Turn off
  if(n_f<0){
    eta_vc.setZero();
  }
  // AR1 structure
  if( n_f==0 ){
    for(int v=0; v<n_v; v++){
      Tmp_c = eta_vc.row(v);
      jnll_pointer += SCALE( AR1(L_z(1)), exp(L_z(0)) )( Tmp_c );
    }
    eta_vc = eta_vf;
  }
  // Factor analysis structure
  if( n_f>0 ){
    // Assemble the loadings matrix
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    // Multiply out overdispersion
    eta_vc = eta_vf * L_cf.transpose();
    // Probability of overdispersion
    for(int v=0; v<n_v; v++){
    for(int f=0; f<n_f; f++){
      jnll_pointer -= dnorm( eta_vf(v,f), Type(0.0), Type(1.0), true );
    }}
  }
  return eta_vc;
}

template<class Type>                                                                                        //
matrix<Type> convert_upper_cov_to_cor( matrix<Type> cov ){
  int nrow = cov.row(0).size();
  for( int i=0; i<nrow; i++){
  for( int j=i+1; j<nrow; j++){
    cov(i,j) = cov(i,j) / pow(cov(i,i),0.5) / pow(cov(j,j),0.5);
  }}
  return cov;
}

// Input: L_omega1_z, Q1, Omegainput1_sf, n_f, n_s, n_c, FieldConfig(0)
// Output: jnll_comp(0), Omega1_sc
template<class Type>                                                                                        //
matrix<Type> gmrf_by_category_nll( int n_f, int method, int n_s, int n_c, Type logkappa, array<Type> gmrf_input_sf, array<Type> gmrf_mean_sf, vector<Type> L_z, density::GMRF_t<Type> gmrf_Q, Type &jnll_pointer){
  using namespace density;
  matrix<Type> gmrf_sc(n_s, n_c);
  Type logtau;
  // Turn off
  if(n_f<0){
    gmrf_sc.setZero();
  }
  // AR1 structure
  if(n_f==0){
    logtau = L_z(0) - logkappa;  //
    jnll_pointer += SEPARABLE( AR1(L_z(1)), gmrf_Q )(gmrf_input_sf - gmrf_mean_sf);
    gmrf_sc = gmrf_input_sf / exp(logtau);                                // Rescaling from comp_index_v1d.cpp
  }
  // Factor analysis structure
  if(n_f>0){
    if(method==0) logtau = log( 1 / (exp(logkappa) * sqrt(4*M_PI)) );
    if(method==1) logtau = log( 1 / sqrt(1-exp(logkappa*2)) );
    for( int f=0; f<n_f; f++ ) jnll_pointer += gmrf_Q(gmrf_input_sf.col(f) - gmrf_mean_sf.col(f));  // Rescaling from spatial_vam_v13.cpp
    matrix<Type> L_cf = loadings_matrix( L_z, n_c, n_f );
    gmrf_sc = (gmrf_input_sf.matrix() * L_cf.transpose()) / exp(logtau);
  }
  return gmrf_sc;
}

// CMP distribution
template<class Type>
Type dCMP(Type x, Type mu, Type nu, int give_log=0, int iter_max=30, int break_point=10){
  // Explicit
  Type ln_S_1 = nu*mu - ((nu-1)/2)*log(mu) - ((nu-1)/2)*log(2*M_PI) - 0.5*log(nu);
  // Recursive
  vector<Type> S_i(iter_max);
  S_i(0) = 1;
  for(int i=1; i<iter_max; i++) S_i(i) = S_i(i-1) * pow( mu/Type(i), nu );
  Type ln_S_2 = log( sum(S_i) );
  // Blend (breakpoint:  mu=10)
  Type prop_1 = invlogit( (mu-break_point)*5 );
  //Type S_comb = prop_1*exp(ln_S_1) + (1-prop_1)*exp(ln_S_2);
  Type log_S_comb = prop_1*ln_S_1 + (1-prop_1)*ln_S_2;
  // Likelihood
  Type loglike = nu*x*log(mu) - nu*lgamma(x+1) - log_S_comb;
  // Return
  if(give_log) return loglike; else return exp(loglike);
}

// compound Poisson-Gamma ("Tweedie") distribution
template<class Type>
Type dPoisGam( Type x, Type shape, Type scale, Type intensity, vector<Type> &diag_z, int maxsum=50, int minsum=1, int give_log=0 ){
  // Maximum integration constant to prevent numerical overflow, but capped at value for maxsum to prevent numerical underflow when subtracting by a higher limit than is seen in the sequence
  Type max_log_wJ, z1, maxJ_bounded;
  if( x==0 ){
    diag_z(0) = 1;
    max_log_wJ = 0;
    diag_z(1) = 0;
  }else{
    z1 = log(intensity) + shape*log(x/scale) - shape*log(shape) + 1;
    diag_z(0) = exp( (z1 - 1) / (1 + shape) );
    maxJ_bounded = CppAD::CondExpGe(diag_z(0), Type(maxsum), Type(maxsum), diag_z(0));
    max_log_wJ = maxJ_bounded*log(intensity) + (maxJ_bounded*shape)*log(x/scale) - lgamma(maxJ_bounded+1) - lgamma(maxJ_bounded*shape);
    diag_z(1) = diag_z(0)*log(intensity) + (diag_z(0)*shape)*log(x/scale) - lgamma(diag_z(0)+1) - lgamma(diag_z(0)*shape);
  }
  // Integration constant
  Type W = 0;
  Type log_w_j, pos_penalty;
  for( int j=minsum; j<=maxsum; j++ ){
    Type j2 = j;
    //W += pow(intensity,j) * pow(x/scale, j2*shape) / exp(lgamma(j2+1)) / exp(lgamma(j2*shape)) / exp(max_log_w_j);
    log_w_j = j2*log(intensity) + (j2*shape)*log(x/scale) - lgamma(j2+1) - lgamma(j2*shape);
    //W += exp( posfun(log_w_j, Type(-30), pos_penalty) );
    W += exp( log_w_j - max_log_wJ );
    if(j==minsum) diag_z(2) = log_w_j;
    if(j==maxsum) diag_z(3) = log_w_j;
  }
  // Loglikelihood calculation
  Type loglike = 0;
  if( x==0 ){
    loglike = -intensity;
  }else{
    loglike = -x/scale - intensity - log(x) + log(W) + max_log_wJ;
  }
  // Return
  if(give_log) return loglike; else return exp(loglike);
}


// Space time
template<class Type>
Type objective_function<Type>::operator() ()
{
  using namespace R_inla;
  using namespace Eigen;
  using namespace density;

  // Dimensions
  DATA_INTEGER(n_i);         // Number of observations (stacked across all years)
  DATA_INTEGER(n_s);         // Number of "strata" (i.e., vectices in SPDE mesh) 
  DATA_INTEGER(n_x);         // Number of real "strata" (i.e., k-means locations) 
  DATA_INTEGER(n_t);         // Number of time-indices
  DATA_INTEGER(n_c);         // Number of categories (e.g., length bins)
  DATA_INTEGER(n_j);         // Number of static covariates
  DATA_INTEGER(n_p);         // Number of dynamic covariates
  DATA_INTEGER(n_k);          // Number of catchability variables
  DATA_INTEGER(n_v);          // Number of tows/vessels (i.e., levels for the factor explaining overdispersion)
  DATA_INTEGER(n_l);         // Number of indices to post-process
  DATA_INTEGER(n_m);         // Number of range metrics to use (probably 2 for Eastings-Northings)

  // Config
  DATA_IVECTOR( Options_vec );
  // Slot 0 -- Aniso: 0=No, 1=Yes
  // Slot 1 -- DEPRECATED
  // Slot 2 -- AR1 on betas (year intercepts) to deal with missing years: 0=No, 1=Yes
  // Slot 3 -- DEPRECATED
  // Slot 4 -- DEPRECATED
  // Slot 5 -- Upper limit constant of integration calculation for infinite-series density functions (Conway-Maxwell-Poisson and Tweedie)
  // Slot 6 -- Breakpoint in CMP density function
  // Slot 7 -- Whether to use SPDE or 2D-AR1 hyper-distribution for spatial process: 0=SPDE; 1=2D-AR1
  DATA_IVECTOR(FieldConfig);  // Input settings (vector, length 4)
  DATA_IVECTOR(OverdispersionConfig);          // Input settings (vector, length 2)
  DATA_IVECTOR(ObsModel);    // Observation model
  // Slot 0: Distribution for positive catches
  // Slot 1: Link function for encounter probabilities
  DATA_IVECTOR(Options);    // Reporting options
  // Slot 0: Calculate SD for Index_xctl
  // Slot 1: Calculate SD for log(Index_xctl)
  // Slot 2: Calculate mean_Z_ctm (i.e., center-of-gravity)
  // Slot 3: DEPRECATED
  // Slot 4: Calculate mean_D_tl and effective_area_tl
  // Slot 5: Calculate standard errors for Covariance and Correlation among categories using factor-analysis parameterization
  // Slot 6: Calculate synchrony for different periods specified via yearbounds_zz
  // Slot 7: Calculate coherence and variance for Epsilon1_sct and Epsilon2_sct
  DATA_IMATRIX(yearbounds_zz);
  // Two columns, and 1+ rows, specifying first and last t for each period used in calculating synchrony

  // Data vectors
  DATA_VECTOR(b_i);       	// Response (biomass) for each observation
  DATA_VECTOR(a_i);       	// Area swept for each observation (km^2)
  DATA_IVECTOR(c_i);         // Category for each observation
  DATA_IVECTOR(s_i);          // Station for each observation
  DATA_IMATRIX(t_iz);          // Time-indices (year, season, etc.) for each observation
  DATA_IVECTOR(v_i);          // tows/vessels for each observation (level of factor representing overdispersion)
  DATA_VECTOR(PredTF_i);          // vector indicating whether an observatino is predictive (1=used for model evaluation) or fitted (0=used for parameter estimation)
  DATA_MATRIX(a_xl);		     // Area for each "real" stratum(km^2) in each stratum
  DATA_MATRIX(X_xj);		    // Covariate design matrix (strata x covariate)
  DATA_ARRAY(X_xtp);		    // Covariate design matrix (strata x covariate)
  DATA_MATRIX(Q_ik);        // Catchability matrix (observations x variable)
  DATA_IMATRIX(t_yz);        // Matrix for time-indices of calculating outputs (abundance index and "derived-quantity")
  DATA_MATRIX(Z_xm);        // Derived quantity matrix

  // SPDE objects
  DATA_STRUCT(spde,spde_t);
  
  // Aniso objects
  DATA_STRUCT(spde_aniso,spde_aniso_t);

  // Sparse matrices for precision matrix of 2D AR1 process
  // Q = M0*(1+rho^2)^2 + M1*(1+rho^2)*(-rho) + M2*rho^2
  DATA_SPARSE_MATRIX(M0);
  DATA_SPARSE_MATRIX(M1);
  DATA_SPARSE_MATRIX(M2);

  // Parameters 
  PARAMETER_VECTOR(ln_H_input); // Anisotropy parameters
  //  -- presence/absence
  PARAMETER_MATRIX(beta1_ct);       // Year effect
  PARAMETER_VECTOR(gamma1_j);        // Static covariate effect
  PARAMETER_ARRAY(gamma1_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda1_k);       // Catchability coefficients
  PARAMETER_VECTOR(L1_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega1_z);
  PARAMETER_VECTOR(L_epsilon1_z);
  PARAMETER(logkappa1);
  PARAMETER(Beta_mean1);  // mean-reversion for beta1_t
  PARAMETER(logsigmaB1);  // SD of beta1_t (default: not included in objective function)
  PARAMETER(Beta_rho1);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho1);  // AR1 for presence/absence Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio1_z);  // Ratio of variance for columns of t_iz
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta1_vf);
  PARAMETER_ARRAY(Omegainput1_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput1_sft);   // Annual variation
  //  -- positive catch rates
  PARAMETER_MATRIX(beta2_ct);  // Year effect
  PARAMETER_VECTOR(gamma2_j);        // Covariate effect
  PARAMETER_ARRAY(gamma2_ctp);       // Dynamic covariate effect
  PARAMETER_VECTOR(lambda2_k);       // Catchability coefficients
  PARAMETER_VECTOR(L2_z);          // Overdispersion parameters
  PARAMETER_VECTOR(L_omega2_z);
  PARAMETER_VECTOR(L_epsilon2_z);
  PARAMETER(logkappa2);
  PARAMETER(Beta_mean2);  // mean-reversion for beta2_t
  PARAMETER(logsigmaB2);  // SD of beta2_t (default: not included in objective function)
  PARAMETER(Beta_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER(Epsilon_rho2);  // AR1 for positive catch Epsilon component, Default=0
  PARAMETER_VECTOR(log_sigmaratio2_z);  // Ratio of variance for columns of t_iz
  PARAMETER_ARRAY(logSigmaM);   // Slots: 0=mix1 CV, 1=prob-of-mix1, 2=
  // -- Gaussian random fields
  PARAMETER_MATRIX(eta2_vf);
  PARAMETER_ARRAY(Omegainput2_sf);      // Expectation
  PARAMETER_ARRAY(Epsiloninput2_sft);   // Annual variation

  // Indices -- i=Observation; j=Covariate; v=Vessel; t=Year; s=Stratum
  int i,j,v,t,s,c,y,z;
  
  // Objective function
  vector<Type> jnll_comp(13);
  // Slot 0 -- spatial, encounter
  // Slot 1 -- spatio-temporal, encounter
  // Slot 2 -- spatial, positive catch
  // Slot 3 -- spatio-temporal, positive catch
  // Slot 4 -- tow/vessel overdispersion, encounter
  // Slot 5 -- tow/vessel overdispersion, positive catch
  // Slot 8 -- penalty on beta, encounter
  // Slot 9 -- penalty on beta, positive catch
  // Slot 10 -- likelihood of data, encounter
  // Slot 11 -- likelihood of data, positive catch
  jnll_comp.setZero();
  Type jnll = 0;                

  // Derived parameters
  Type Range_raw1, Range_raw2;
  if( Options_vec(7)==0 ){
    Range_raw1 = sqrt(8) / exp( logkappa1 );   // Range = approx. distance @ 10% correlation
    Range_raw2 = sqrt(8) / exp( logkappa2 );     // Range = approx. distance @ 10% correlation
  }
  if( Options_vec(7)==1 ){
    Range_raw1 = log(0.1) / logkappa1;   // Range = approx. distance @ 10% correlation
    Range_raw2 = log(0.1) / logkappa2;     // Range = approx. distance @ 10% correlation
  }
  array<Type> SigmaM( n_c, 3 );
  SigmaM = exp( logSigmaM );

  // Anisotropy elements
  matrix<Type> H(2,2);
  H(0,0) = exp(ln_H_input(0));
  H(1,0) = ln_H_input(1);
  H(0,1) = ln_H_input(1);
  H(1,1) = (1+ln_H_input(1)*ln_H_input(1)) / exp(ln_H_input(0));

  ////////////////////////
  // Calculate joint likelihood
  ////////////////////////

  // Random field probability
  Eigen::SparseMatrix<Type> Q1;
  Eigen::SparseMatrix<Type> Q2;
  GMRF_t<Type> gmrf_Q;
  if( Options_vec(7)==0 & Options_vec(0)==0 ){
    Q1 = Q_spde(spde, exp(logkappa1));
    Q2 = Q_spde(spde, exp(logkappa2));
  }
  if( Options_vec(7)==0 & Options_vec(0)==1 ){
    Q1 = Q_spde(spde_aniso, exp(logkappa1), H);
    Q2 = Q_spde(spde_aniso, exp(logkappa2), H);
  }
  if( Options_vec(7)==1 ){
    Q1 = M0*pow(1+exp(logkappa1*2),2) + M1*(1+exp(logkappa1*2))*(-exp(logkappa1)) + M2*exp(logkappa1*2);
    Q2 = M0*pow(1+exp(logkappa2*2),2) + M1*(1+exp(logkappa2*2))*(-exp(logkappa2)) + M2*exp(logkappa2*2);
  }
  // Probability of encounter
  gmrf_Q = GMRF(Q1);
  array<Type> Omegamean1_sf(n_s, FieldConfig(0));
  Omegamean1_sf.setZero();
  array<Type> Epsilonmean1_sf(n_s, FieldConfig(1));
  array<Type> Omega1_sc(n_s, n_c);
  Omega1_sc = gmrf_by_category_nll(FieldConfig(0), Options_vec(7), n_s, n_c, logkappa1, Omegainput1_sf, Omegamean1_sf, L_omega1_z, gmrf_Q, jnll_comp(0));
  array<Type> Epsilon1_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean1_sf.setZero();
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
    }
    if(t>=1){
      Epsilonmean1_sf = Epsilon_rho1 * Epsiloninput1_sft.col(t-1);
      Epsilon1_sct.col(t) = gmrf_by_category_nll(FieldConfig(1), Options_vec(7), n_s, n_c, logkappa1, Epsiloninput1_sft.col(t), Epsilonmean1_sf, L_epsilon1_z, gmrf_Q, jnll_comp(1));
      //Epsilon1_sct.col(t) += Epsilon_rho1 * Epsilon1_sct.col(t-1);
    }
  }
  // Positive catch rate
  gmrf_Q = GMRF(Q2);
  array<Type> Omegamean2_sf(n_s, FieldConfig(2));
  Omegamean2_sf.setZero();
  array<Type> Epsilonmean2_sf(n_s, FieldConfig(3));
  array<Type> Omega2_sc(n_s, n_c);
  Omega2_sc = gmrf_by_category_nll(FieldConfig(2), Options_vec(7), n_s, n_c, logkappa2, Omegainput2_sf, Omegamean2_sf, L_omega2_z, gmrf_Q, jnll_comp(2));
  array<Type> Epsilon2_sct(n_s, n_c, n_t);
  for(t=0; t<n_t; t++){
    if(t==0){
      Epsilonmean2_sf.setZero();
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
    }
    if(t>=1){
      Epsilonmean2_sf = Epsilon_rho2 * Epsiloninput2_sft.col(t-1);
      Epsilon2_sct.col(t) = gmrf_by_category_nll(FieldConfig(3), Options_vec(7), n_s, n_c, logkappa2, Epsiloninput2_sft.col(t), Epsilonmean2_sf, L_epsilon2_z, gmrf_Q, jnll_comp(3));
      //Epsilon2_sct.col(t) += Epsilon_rho2 * Epsilon2_sct.col(t-1);
    }
  }

  ////// Probability of correlated overdispersion among bins
  // IN: eta1_vf; n_f; L1_z
  // OUT: jnll_comp; eta1_vc
  matrix<Type> eta1_vc(n_v, n_c);
  eta1_vc = overdispersion_by_category_nll( OverdispersionConfig(0), n_v, n_c, eta1_vf, L1_z, jnll_comp(4) );
  matrix<Type> eta2_vc(n_v, n_c);
  eta2_vc = overdispersion_by_category_nll( OverdispersionConfig(1), n_v, n_c, eta2_vf, L2_z, jnll_comp(5) );

  // Possible structure on betas
  if( Options_vec(2)!=0 ){
    for(c=0; c<n_c; c++){
    for(t=1; t<n_t; t++){
      jnll_comp(8) -= dnorm( beta1_ct(c,t), Beta_rho1*beta1_ct(c,t-1) + Beta_mean1, exp(logsigmaB1), true );
      jnll_comp(9) -= dnorm( beta2_ct(c,t), Beta_rho2*beta2_ct(c,t-1) + Beta_mean2, exp(logsigmaB2), true );
    }}
  }
  
  // Covariates
  vector<Type> eta1_x = X_xj * gamma1_j.matrix();
  vector<Type> zeta1_i = Q_ik * lambda1_k.matrix();
  vector<Type> eta2_x = X_xj * gamma2_j.matrix();
  vector<Type> zeta2_i = Q_ik * lambda2_k.matrix();
  array<Type> eta1_xct(n_x, n_c, n_t);
  array<Type> eta2_xct(n_x, n_c, n_t);
  eta1_xct.setZero();
  eta2_xct.setZero();
  for(int x=0; x<n_x; x++){
  for(int c=0; c<n_c; c++){
  for(int t=0; t<n_t; t++){
  for(int p=0; p<n_p; p++){
    eta1_xct(x,c,t) += gamma1_ctp(c,t,p) * X_xtp(x,t,p);
    eta2_xct(x,c,t) += gamma2_ctp(c,t,p) * X_xtp(x,t,p);
  }}}}

  // Derived quantities
  vector<Type> var_i(n_i);
  Type var_y;
  // Linear predictor (pre-link) for presence/absence component
  vector<Type> P1_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:4 or 11:12: probability ("phi") that data is greater than zero
  // ObsModel = 5 (ZINB):  phi = 1-ZeroInflation_prob -> Pr[D=0] = NB(0|mu,var)*phi + (1-phi) -> Pr[D>0] = phi - NB(0|mu,var)*phi 
  vector<Type> R1_i(n_i);   
  vector<Type> LogProb1_i(n_i);
  // Linear predictor (pre-link) for positive component
  vector<Type> P2_i(n_i);   
  // Response predictor (post-link)
  // ObsModel = 0:3, 11:12:  expected value of data, given that data is greater than zero -> E[D] = mu*phi
  // ObsModel = 4 (ZANB):  expected value ("mu") of neg-bin PRIOR to truncating Pr[D=0] -> E[D] = mu/(1-NB(0|mu,var))*phi  ALSO  Pr[D] = NB(D|mu,var)/(1-NB(0|mu,var))*phi
  // ObsModel = 5 (ZINB):  expected value of data for non-zero-inflation component -> E[D] = mu*phi
  vector<Type> R2_i(n_i);   
  vector<Type> LogProb2_i(n_i);
  vector<Type> maxJ_i(n_i);
  vector<Type> diag_z(4);
  matrix<Type> diag_iz(n_i,4);
  diag_iz.setZero();  // Used to track diagnostics for Tweedie distribution (columns: 0=maxJ; 1=maxW; 2=lowerW; 3=upperW)

  // Likelihood contribution from observations
  for(int i=0; i<n_i; i++){
    // Linear predictors
    P1_i(i) = Omega1_sc(s_i(i),c_i(i)) + eta1_x(s_i(i)) + zeta1_i(i) + eta1_vc(v_i(i),c_i(i));
    P2_i(i) = Omega2_sc(s_i(i),c_i(i)) + eta2_x(s_i(i)) + zeta2_i(i) + eta2_vc(v_i(i),c_i(i));
    for( int z=0; z<t_iz.row(0).size(); z++ ){
      if( t_iz(i,z)>=0 & t_iz(i,z)<n_t ){  // isNA doesn't seem to work for IMATRIX type
        P1_i(i) += beta1_ct(c_i(i),t_iz(i,z)) + Epsilon1_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(s_i(i),c_i(i),t_iz(i,z));
        P2_i(i) += beta2_ct(c_i(i),t_iz(i,z)) + Epsilon2_sct(s_i(i),c_i(i),t_iz(i,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(s_i(i),c_i(i),t_iz(i,z));
      }
    }
    // Responses
    if( ObsModel(1)==0 ){
      // Log and logit-link, where area-swept only affects positive catch rate exp(P2_i(i))
      // P1_i: Logit-Probability of occurrence;  R1_i:  Probability of occurrence
      // P2_i: Log-Positive density prediction;  R2_i:  Positive density prediction
      R1_i(i) = invlogit( P1_i(i) );
      R2_i(i) = a_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==1 ){
      // Poisson-process link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Probability of occurrence
      // P2_i: Log-average weight;  R2_i:  Positive density prediction
      R1_i(i) = Type(1.0) - exp( -1*SigmaM(c_i(i),2)*a_i(i)*exp(P1_i(i)) );
      R2_i(i) = a_i(i)*exp(P1_i(i)) / R1_i(i) * exp( P2_i(i) );
    }
    if( ObsModel(1)==2 ){
      // Tweedie link, where area-swept affects numbers density exp(P1_i(i))
      // P1_i: Log-numbers density;  R1_i:  Expected numbers
      // P2_i: Log-average weight;  R2_i:  Expected average weight
      R1_i(i) = a_i(i) * exp( P1_i(i) );
      R2_i(i) = exp( P2_i(i) );
    }
    // Likelihood for delta-models with continuous positive support
    if(ObsModel(0)==0 | ObsModel(0)==1 | ObsModel(0)==2){
      // Presence-absence likelihood
      if( b_i(i) > 0 ){
        LogProb1_i(i) = log( R1_i(i) );
      }else{
        LogProb1_i(i) = log( 1-R1_i(i) );
      }
      // Positive density likelihood -- models with continuous positive support
      if( b_i(i) > 0 ){    // 1e-500 causes overflow on laptop
        if(ObsModel(0)==0) LogProb2_i(i) = dnorm(b_i(i), R2_i(i), SigmaM(c_i(i),0), true);
        if(ObsModel(0)==1) LogProb2_i(i) = dlnorm(b_i(i), log(R2_i(i))-pow(SigmaM(c_i(i),0),2)/2, SigmaM(c_i(i),0), true); // log-space
        if(ObsModel(0)==2) LogProb2_i(i) = dgamma(b_i(i), 1/pow(SigmaM(c_i(i),0),2), R2_i(i)*pow(SigmaM(c_i(i),0),2), true); // shape = 1/CV^2, scale = mean*CV^2
      }else{
        LogProb2_i(i) = 0;
      }
    }
    // Likelihood for Tweedie model with continuous positive support
    if(ObsModel(0)==8){
      LogProb1_i(i) = 0;
      // dPoisGam( Type x, Type shape, Type scale, Type intensity, Type &max_log_w_j, int maxsum=50, int minsum=1, int give_log=0 )
      LogProb2_i(i) = dPoisGam( b_i(i), SigmaM(c_i(i),0), R1_i(i), R2_i(i), diag_z, Options_vec(5), Options_vec(6), true );
      diag_iz.row(i) = diag_z;
    }
    // Likelihood for models with discrete support 
    if(ObsModel(0)==4 | ObsModel(0)==5 | ObsModel(0)==6 | ObsModel(0)==7){
      if(ObsModel(0)==5){
        // Zero-inflated negative binomial (not numerically stable!)
        var_i(i) = R2_i(i)*(1.0+SigmaM(c_i(i),0)) + pow(R2_i(i),2.0)*SigmaM(c_i(i),1);
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dnbinom2(Type(0.0), R2_i(i), var_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + NB(X=0)*phi
        }else{
          LogProb2_i(i) = dnbinom2(b_i(i), R2_i(i), var_i(i), true) + log(R1_i(i)); // Pr[X=x] = NB(X=x)*phi
        }
      }
      if(ObsModel(0)==6){
        // Conway-Maxwell-Poisson
        LogProb2_i(i) = dCMP(b_i(i), R2_i(i), exp(P1_i(i)), true, Options_vec(5));
      }
      if(ObsModel(0)==7){
        // Zero-inflated Poisson
        if( b_i(i)==0 ){
          LogProb2_i(i) = log( (1-R1_i(i)) + dpois(Type(0.0), R2_i(i), false)*R1_i(i) ); //  Pr[X=0] = 1-phi + Pois(X=0)*phi
        }else{
          LogProb2_i(i) = dpois(b_i(i), R2_i(i), true) + log(R1_i(i)); // Pr[X=x] = Pois(X=x)*phi
        }
      }
      LogProb1_i(i) = 0;
    }
  }
  REPORT( diag_iz );

  // Joint likelihood
  jnll_comp(10) = -1 * (LogProb1_i * (Type(1.0)-PredTF_i)).sum();
  jnll_comp(11) = -1 * (LogProb2_i * (Type(1.0)-PredTF_i)).sum();
  jnll = jnll_comp.sum();
  Type pred_jnll = -1 * ( LogProb1_i*PredTF_i + LogProb2_i*PredTF_i ).sum();
  REPORT( pred_jnll );

  ////////////////////////
  // Calculate outputs
  ////////////////////////

  // Number of output-years
  int n_y = t_yz.col(0).size();

  // Predictive distribution -- ObsModel(4) isn't implemented (it had a bug previously)
  Type a_average = a_i.sum()/a_i.size();
  array<Type> P1_xcy(n_x, n_c, n_y);
  array<Type> R1_xcy(n_x, n_c, n_y);
  array<Type> P2_xcy(n_x, n_c, n_y);
  array<Type> R2_xcy(n_x, n_c, n_y);
  array<Type> D_xcy(n_x, n_c, n_y);
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int x=0; x<n_x; x++){
    // Calculate linear predictors
    P1_xcy(x,c,y) = Omega1_sc(x,c) + eta1_x(x);
    P2_xcy(x,c,y) =  Omega2_sc(x,c) + eta2_x(x);
    for( int z=0; z<t_yz.row(0).size(); z++ ){
      if( t_yz(y,z)>=0 & t_yz(y,z)<n_t ){    // isNA doesn't seem to work for IMATRIX type
        P1_xcy(x,c,y) += beta1_ct(c,t_yz(y,z)) + Epsilon1_sct(x,c,t_yz(y,z))*exp(log_sigmaratio1_z(z)) + eta1_xct(x,c,t_yz(y,z));
        P2_xcy(x,c,y) += beta2_ct(c,t_yz(y,z)) + Epsilon2_sct(x,c,t_yz(y,z))*exp(log_sigmaratio2_z(z)) + eta2_xct(x,c,t_yz(y,z));
      }
    }
    // Calculate predictors in link-space
    if( ObsModel(1)==0 ){
      R1_xcy(x,c,y) = invlogit( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==1 ){
      R1_xcy(x,c,y) = Type(1.0) - exp( -SigmaM(c,2)*exp(P1_xcy(x,c,y)) );
      R2_xcy(x,c,y) = exp(P1_xcy(x,c,y)) / R1_xcy(x,c,y) * exp( P2_xcy(x,c,y) );
    }
    if( ObsModel(1)==2 ){
      R1_xcy(x,c,y) = exp( P1_xcy(x,c,y) );
      R2_xcy(x,c,y) = exp( P2_xcy(x,c,y) );
    }
    // Expected value for predictive distribution in a grid cell
    D_xcy(x,c,y) = R1_xcy(x,c,y) * R2_xcy(x,c,y);
  }}}

  // Calculate indices
  array<Type> Index_xcyl(n_x, n_c, n_y, n_l);
  array<Type> Index_cyl(n_c, n_y, n_l);
  array<Type> ln_Index_cyl(n_c, n_y, n_l);
  Index_cyl.setZero();
  for(int c=0; c<n_c; c++){
  for(int y=0; y<n_y; y++){
  for(int l=0; l<n_l; l++){
    for(int x=0; x<n_x; x++){
      Index_xcyl(x,c,y,l) = D_xcy(x,c,y) * a_xl(x,l) / 1000;  // Convert from kg to metric tonnes
      Index_cyl(c,y,l) += Index_xcyl(x,c,y,l);
    }
  }}}
  ln_Index_cyl = log( Index_cyl );

  // Calculate other derived summaries
  // Each is the weighted-average X_xl over polygons (x) with weights equal to abundance in each polygon and time (where abundance is from the first index)
  array<Type> mean_Z_cym(n_c, n_y, n_m);
  if( Options(2)==1 ){
    mean_Z_cym.setZero();
    int report_summary_TF = false;
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int m=0; m<n_m; m++){
      for(int x=0; x<n_x; x++){
        if( Z_xm(x,m)!=0 ){
          mean_Z_cym(c,y,m) += Z_xm(x,m) * Index_xcyl(x,c,y,0)/Index_cyl(c,y,0);
          report_summary_TF = true;
        }
      }
    }}}
    if( report_summary_TF==true ){
      REPORT( mean_Z_cym );
      ADREPORT( mean_Z_cym );
    }
  }

  // Calculate average density, weighted.mean( x=Abundance/Area, w=Abundance )
  // Doesn't require Z_xm, because it only depends upon Index_tl
  if( Options(4)==1 ){
    array<Type> mean_D_cyl(n_c, n_y, n_l);
    array<Type> log_mean_D_cyl(n_c, n_y, n_l);
    mean_D_cyl.setZero();
    for(int c=0; c<n_c; c++){
    for(int y=0; y<n_y; y++){
    for(int l=0; l<n_l; l++){
      for(int x=0; x<n_x; x++){
        mean_D_cyl(c,y,l) += D_xcy(x,c,y) * Index_xcyl(x,c,y,l)/Index_cyl(c,y,l);
      }
    }}}
    REPORT( mean_D_cyl );
    ADREPORT( mean_D_cyl );
    log_mean_D_cyl = log( mean_D_cyl );
    ADREPORT( log_mean_D_cyl );

    // Calculate effective area = Index / average density
    array<Type> effective_area_cyl(n_c, n_y, n_l);
    array<Type> log_effective_area_cyl(n_c, n_y, n_l);
    effective_area_cyl = Index_cyl / (mean_D_cyl/1000);  // Correct for different units of Index and density
    log_effective_area_cyl = log( effective_area_cyl );
    REPORT( effective_area_cyl );
    ADREPORT( effective_area_cyl );
    ADREPORT( log_effective_area_cyl );
  }

  // Reporting and standard-errors for covariance and correlation matrices
  if( Options(5)==1 ){
    if( FieldConfig(0)>0 ){
      matrix<Type> L1_omega_cf = loadings_matrix( L_omega1_z, n_c, FieldConfig(0) );
      matrix<Type> lowercov_uppercor_omega1 = L1_omega_cf * L1_omega_cf.transpose();
      lowercov_uppercor_omega1 = convert_upper_cov_to_cor( lowercov_uppercor_omega1 );
      REPORT( lowercov_uppercor_omega1 );
      ADREPORT( lowercov_uppercor_omega1 );
    }
    if( FieldConfig(1)>0 ){
      matrix<Type> L1_epsilon_cf = loadings_matrix( L_epsilon1_z, n_c, FieldConfig(1) );
      matrix<Type> lowercov_uppercor_epsilon1 = L1_epsilon_cf * L1_epsilon_cf.transpose();
      lowercov_uppercor_epsilon1 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon1 );
      REPORT( lowercov_uppercor_epsilon1 );
      ADREPORT( lowercov_uppercor_epsilon1 );
    }
    if( FieldConfig(2)>0 ){
      matrix<Type> L2_omega_cf = loadings_matrix( L_omega2_z, n_c, FieldConfig(2) );
      matrix<Type> lowercov_uppercor_omega2 = L2_omega_cf * L2_omega_cf.transpose();
      lowercov_uppercor_omega2 = convert_upper_cov_to_cor( lowercov_uppercor_omega2 );
      REPORT( lowercov_uppercor_omega2 );
      ADREPORT( lowercov_uppercor_omega2 );
    }
    if( FieldConfig(3)>0 ){
      matrix<Type> L2_epsilon_cf = loadings_matrix( L_epsilon2_z, n_c, FieldConfig(3) );
      matrix<Type> lowercov_uppercor_epsilon2 = L2_epsilon_cf * L2_epsilon_cf.transpose();
      lowercov_uppercor_epsilon2 = convert_upper_cov_to_cor( lowercov_uppercor_epsilon2 );
      REPORT( lowercov_uppercor_epsilon2 );
      ADREPORT( lowercov_uppercor_epsilon2 );
    }
  }

  // Synchrony
  if( Options(6)==1 ){
    int n_z = yearbounds_zz.col(0).size();
    // Density ("D") or area-expanded total biomass ("B") for each category (use B when summing across sites)
    matrix<Type> D_xy( n_x, n_y );
    matrix<Type> B_cy( n_c, n_y );
    vector<Type> B_y( n_y );
    D_xy.setZero();
    B_cy.setZero();
    B_y.setZero();
    // Sample variance in category-specific density ("D") and biomass ("B")
    array<Type> varD_xcz( n_x, n_c, n_z );
    array<Type> varD_xz( n_x, n_z );
    array<Type> varB_cz( n_c, n_z );
    vector<Type> varB_z( n_z );
    vector<Type> varB_xbar_z( n_z );
    vector<Type> varB_cbar_z( n_z );
    vector<Type> ln_varB_z( n_z );
    vector<Type> ln_varB_xbar_z( n_z );
    vector<Type> ln_varB_cbar_z( n_z );
    array<Type> maxsdD_xz( n_x, n_z );
    array<Type> maxsdB_cz( n_c, n_z );
    vector<Type> maxsdB_z( n_z );
    varD_xcz.setZero();
    varD_xz.setZero();
    varB_cz.setZero();
    varB_z.setZero();
    varB_xbar_z.setZero();
    varB_cbar_z.setZero();
    maxsdD_xz.setZero();
    maxsdB_cz.setZero();
    maxsdB_z.setZero();
    // Proportion of total biomass ("P") for each location or each category
    matrix<Type> propB_xz( n_x, n_z );
    matrix<Type> propB_cz( n_c, n_z );
    propB_xz.setZero();
    propB_cz.setZero();
    // Synchrony indices
    matrix<Type> phi_xz( n_x, n_z );
    matrix<Type> phi_cz( n_c, n_z );
    vector<Type> phi_xbar_z( n_z );
    vector<Type> phi_cbar_z( n_z );
    vector<Type> phi_z( n_z );
    phi_xbar_z.setZero();
    phi_cbar_z.setZero();
    phi_z.setZero();
    // Calculate total biomass for different categories
    for( int y=0; y<n_y; y++ ){
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          D_xy(x,y) += D_xcy(x,c,y);
          B_cy(c,y) += D_xcy(x,c,y) * a_xl(x,0);
          B_y(y) += D_xcy(x,c,y) * a_xl(x,0);
        }
      }
    }
    // Loop through periods (only using operations available in TMB, i.e., var, mean, row, col, segment)
    Type temp_mean;
    for( int z=0; z<n_z; z++ ){
      for( int x=0; x<n_x; x++ ){
        // Variance for biomass in each category, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        for( int c=0; c<n_c; c++ ){
          temp_mean = 0;
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xcy(x,c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
          for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ){
            varD_xcz(x,c,z) += pow(D_xcy(x,c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
          }
        }
        // Variance for combined biomass across categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += D_xy(x,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varD_xz(x,z) += pow(D_xy(x,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      for( int c=0; c<n_c; c++ ){
        // Variance for combined biomass across sites, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
        temp_mean = 0;
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_cy(c,y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          varB_cz(c,z) += pow(B_cy(c,y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
        }
      }
      // Variance for combined biomass across sites and categories, use sum(diff^2)/(length(diff)-1) where -1 in denominator is the sample-variance Bessel correction
      temp_mean = 0;
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) temp_mean += B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
      for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
        varB_z(z) += pow(B_y(y)-temp_mean,2) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0));
      }
      // Proportion in each site
      for( int x=0; x<n_x; x++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_xz(x,z) += a_xl(x,0) * D_xy(x,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Proportion in each category
      for( int c=0; c<n_c; c++ ){
        for( int y=yearbounds_zz(z,0); y<=yearbounds_zz(z,1); y++ ) {
          propB_cz(c,z) += B_cy(c,y) / B_y(y) / float(yearbounds_zz(z,1)-yearbounds_zz(z,0)+1);
        }
      }
      // Species-buffering index (calculate in Density so that areas with zero area are OK)
      for( int x=0; x<n_x; x++ ){
        for( int c=0; c<n_c; c++ ){
          maxsdD_xz(x,z) += pow(varD_xcz(x,c,z), 0.5);
        }
        phi_xz(x,z) = varD_xz(x,z) / pow( maxsdD_xz(x,z), 2);
        varB_xbar_z(z) += pow(a_xl(x,0),2) * varD_xz(x,z) * propB_xz(x,z);
        phi_xbar_z(z) += phi_xz(x,z) * propB_xz(x,z);
      }
      // Spatial-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_cz(c,z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
        phi_cz(c,z) = varB_cz(c,z) / pow( maxsdB_cz(c,z), 2);
        varB_cbar_z(z) += varB_cz(c,z) * propB_cz(c,z);
        phi_cbar_z(z) += phi_cz(c,z) * propB_cz(c,z);
      }
      // Spatial and species-buffering index
      for( int c=0; c<n_c; c++ ){
        for( int x=0; x<n_x; x++ ){
          maxsdB_z(z) += a_xl(x,0) * pow(varD_xcz(x,c,z), 0.5);
        }
      }
      phi_z(z) = varB_z(z) / pow( maxsdB_z(z), 2);
    }
    ln_varB_xbar_z = log( varB_xbar_z );
    ln_varB_cbar_z = log( varB_cbar_z );
    ln_varB_z = log( varB_z );
    REPORT( B_y );
    REPORT( D_xy );
    REPORT( B_cy );
    REPORT( phi_xz );
    REPORT( phi_xbar_z );
    REPORT( phi_cz );
    REPORT( phi_cbar_z );
    REPORT( phi_z );
    REPORT( propB_xz );
    REPORT( propB_cz );
    REPORT( varD_xcz );
    REPORT( varD_xz );
    REPORT( varB_cz );
    REPORT( varB_z );
    REPORT( varB_xbar_z );
    REPORT( varB_cbar_z );
    REPORT( maxsdB_z );
    REPORT( maxsdD_xz );
    REPORT( maxsdB_cz );
    ADREPORT( varB_xbar_z );
    ADREPORT( varB_cbar_z );
    ADREPORT( varB_z );
    ADREPORT( ln_varB_xbar_z );
    ADREPORT( ln_varB_cbar_z );
    ADREPORT( ln_varB_z );
    ADREPORT( phi_xbar_z );
    ADREPORT( phi_cbar_z );
    ADREPORT( phi_z );
  }

  // Calculate coherence and variance and covariance matrices
  // psi: "Coherence" = degree to which covariance is explained by one or many factors (1/n_c, 1), 1=Single factor; 1/n_c=even factors
  if( Options(7)==1 ){
    // Eigendecomposition see: https://github.com/kaskr/adcomp/issues/144#issuecomment-228426834
    using namespace Eigen;
    // Spatio-temporal covariance (summation only works when ObsModel[1]=1)
    //Matrix<Type,Dynamic,Dynamic> CovHat( n_c, n_c );
    matrix<Type> CovHat( n_c, n_c );
    CovHat.setIdentity();
    CovHat *= pow(0.0001, 2);
    if( FieldConfig(1)>0 ) CovHat += loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)) * loadings_matrix(L_epsilon1_z, n_c, FieldConfig(1)).transpose();
    if( FieldConfig(3)>0 ) CovHat += loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)) * loadings_matrix(L_epsilon2_z, n_c, FieldConfig(3)).transpose();
    // Coherence ranges from 0 (all factors are equal) to 1 (first factor explains all variance)
    SelfAdjointEigenSolver<Matrix<Type,Dynamic,Dynamic> > es(CovHat);
    vector<Type> eigenvalues_c = es.eigenvalues();       // Ranked from lowest to highest for some reason
    Type psi = 0;
    for(int c=0; c<n_c; c++) psi += eigenvalues_c(n_c-c-1) * (n_c - c);
    psi = 2 * ((psi / eigenvalues_c.sum() / n_c) - 0.5);
    // Total variance
    vector<Type> diag_CovHat( n_c );
    vector<Type> log_diag_CovHat( n_c );
    for(int c=0; c<n_c; c++) diag_CovHat(c) = CovHat(c,c);
    Type totalvar_CovHat = diag_CovHat.sum();
    Type log_totalvar_CovHat = log(totalvar_CovHat);
    log_diag_CovHat = log(diag_CovHat);
    // Reporting
    REPORT( CovHat );
    REPORT( psi );
    REPORT( eigenvalues_c );
    ADREPORT( psi );
    ADREPORT( diag_CovHat );
    ADREPORT( totalvar_CovHat );
    ADREPORT( log_totalvar_CovHat );
    ADREPORT( log_diag_CovHat );
    ADREPORT( eigenvalues_c );
  }

  // Diagnostic output
  REPORT( Q1 );
  REPORT( Q2 );
  REPORT( P1_i );
  REPORT( P2_i );
  REPORT( R1_i );
  REPORT( R2_i );
  REPORT( P1_xcy );
  REPORT( P2_xcy );
  REPORT( var_i );
  REPORT( LogProb1_i );
  REPORT( LogProb2_i );
  REPORT( a_average );
  REPORT( eta1_x );
  REPORT( eta2_x );
  REPORT( eta1_xct );
  REPORT( eta2_xct );
  REPORT( eta1_vc );
  REPORT( eta2_vc );
  REPORT( eta1_vf );
  REPORT( eta2_vf );
  REPORT( zeta1_i );
  REPORT( zeta2_i );

  REPORT( SigmaM );
  REPORT( Index_cyl );
  REPORT( D_xcy );
  REPORT( R1_xcy );
  REPORT( R2_xcy );
  REPORT( Index_xcyl );
  REPORT( Omega1_sc );
  REPORT( Omega2_sc );
  REPORT( Omegainput1_sf );
  REPORT( Omegainput2_sf );
  REPORT( Epsilon1_sct );
  REPORT( Epsilon2_sct );
  REPORT( Epsiloninput1_sft );
  REPORT( Epsiloninput2_sft );
  REPORT( H );
  REPORT( Range_raw1 );
  REPORT( Range_raw2 );
  REPORT( beta1_ct );
  REPORT( beta2_ct );
  REPORT( jnll_comp );
  REPORT( jnll );

  ADREPORT( Range_raw1 );
  ADREPORT( Range_raw2 );
  ADREPORT( Index_cyl );
  ADREPORT( ln_Index_cyl );
  ADREPORT( SigmaM );

  // Additional miscellaneous outputs
  if( Options(0)==1 ){
    ADREPORT( Index_xcyl );
  }
  if( Options(1)==1 ){
    ADREPORT( log(Index_xcyl) );
  }

  return jnll;
  
}


#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Testing parallel implementation across species
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 3.30.17
#
#Purpose: To test whether the following can be done in parallel (within a wrapper fxn)
#  1) Draw in RACE data
#  2) Create VAST input files
#  3) Compile and call VAST
#  4) Report pertinent outputs
#
#
#
#==================================================================================================
#NOTES:
# Timings:
#  100 n_X, and no bias cor
# [1] "START: Tue May 02 09:00:52 2017"
# [1] "End: Tue May 02 09:14:15 2017"
#==================================================================================================
require(snowfall)
require(parallel)
require(ggplot2)
require(TMB)
require(TMBhelper)
require(VAST)

#Source necessary files
source("R/create-VAST-input.r")
source("R/create-Data-Geostat.r")
source("R/load-RACE-data.r")
source("R/plot-VAST-output.r")
source("R/cleanup-VAST-file.r")

#Create testing directory
home.dir <- getwd()
working.dir <- paste0(home.dir,'/examples/Test_Parallel')

#Determine species list
species.list <- read.csv("data/eval_species_list.csv", stringsAsFactors=FALSE)
#Limit to those included
species.list <- species.list[species.list$include=='Y',]

#NOTE: This now represents speciesXsurvey
n.species <- nrow(species.list)
species.series <- c(1:n.species)

#=======================================================================
##### CONTROL SECTION #####
#Number of cores to use
n.cores <- detectCores()-1

#=======================================================================
##### VAST MODEL SPECIFICATIONS #####

lat_lon.def <- "start"

#SPATIAL SETTINGS
Method = c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km = 25
n_x = c(100, 250, 500, 1000, 2000)[1] # Number of stations
Kmeans_Config = list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("All_areas"))#,
                            # west_border = c(-Inf),
                            # east_border = c(Inf))


#DERIVED OBJECTS
Version <-  "VAST_v2_4_0"

bias.correct <- FALSE
###########################
# DateFile=paste0(getwd(),'/examples/VAST_output/')

#MODEL SETTINGS
FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

ObsModel = c(1, 0) #Lognormal dist for pos catch rates, logit-link for encounter probability.
# ObsModel = c(2, 0) #Gamma dist for pos catch rates, logit-link for encounter probability.
# ObsModel = c(1, 1) #Poisson-Process Link function approximating Tweedie distribution

#SPECIFY OUTPUTS
Options = c(SD_site_density = 0, SD_site_logdensity = 0,
            Calculate_Range = 1, Calculate_evenness = 0, Calculate_effective_area = 1,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)


#=======================================================================
##### WRAPPER FUNCTION FOR RUNNING IN PARALLEL #####


#Temporary wrapper function for species
# s <- 1 #S is for species number
species_wrapper_fxn <- function(s) {
# for(s in 1:n.species) {  
  #Define file for analyses
  DateFile <- paste0(working.dir,"/",species.list$survey[s],"_",species.list$name[s],"/")
  
  #Define species.codes
  species.codes <- species.list$species.code[s]
  survey <- species.list$survey[s]
  #=======================================================================
  ##### READ IN DATA AND BUILD VAST INPUT #####
  #  NOTE: this will create the DateFile
  
  VAST_input <- create_VAST_input(species.codes=species.codes, lat_lon.def=lat_lon.def, save.Record=TRUE,
                                  Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                  Kmeans_Config=Kmeans_Config,
                                  strata.limits=NULL, survey=survey,
                                  DateFile=DateFile,
                                  FieldConfig, RhoConfig, OverdispersionConfig,
                                  ObsModel, Options)
  
  
  
  
  #Unpack
  TmbData <- VAST_input$TmbData
  Data_Geostat <- VAST_input$Data_Geostat
  Spatial_List <- VAST_input$Spatial_List
  Extrapolation_List <- VAST_input$Extrapolation_List
  
  
  #=======================================================================
  ##### RUN VAST #####
  
  
  
  #Build TMB Object
  #  Compilation may take some time
  TmbList <- VAST::Build_TMB_Fn(TmbData = TmbData, RunDir = DateFile,
                                Version = "VAST_v2_4_0", RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                                Method = Method)
  Obj <- TmbList[["Obj"]]
  
  
  Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
                             upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
                             bias.correct = bias.correct)
  #Save output
  Report = Obj$report()
  Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
  # save(Save, file=paste0(DateFile,"Save.RData")) #Works, but I want to save space
  
  #========================================================================
  ##### DIAGNOSTIC AND PREDICTION PLOTS #####
  plot_VAST_output(Opt, Report, DateFile, survey, TmbData, Data_Geostat, Extrapolation_List, Spatial_List)
  
  #========================================================================
  ##### CLEANUP VAST OUTPUT #####
  cleanup_VAST_file(DateFile=DateFile, Version=Version)
  
  rm("VAST_input", "TmbData", "Data_Geosta", "Spatial_List", "Extrapolation_List",
     "TmbList", "Obj", "Report", "Save")
  #========================================================================
  #Reset working directory
  setwd(home.dir)
  ##### RETURN SECTION #####
  return(Opt$AIC)

} 

#=======================================================================
##### SNOWFALL CODE FOR PARALLEL #####
start.time <- date()

sfInit(parallel=TRUE, cpus=n.cores, type='SOCK')
sfExportAll() #Exportas all global variables to cores
sfLibrary(TMB)  #Loads a package on all nodes
sfLibrary(VAST)
output <- sfLapply(species.series, fun=species_wrapper_fxn)

# output <- sfLapply(1:3, fun=species_wrapper_fxn)
sfStop()

output.snowfall <- unlist(rbind(output))

end.time <- date()

#Print timing
print(paste('START:',start.time))
print(paste('End:',end.time))

#=======================================================================
##### POST-HOC REMOVAL OF LARGE DATA OBJECT #####
s <- 1
for(s in 1:n.species) {
  unlink(paste0(working.dir,"/",species.list$survey[s],"_",species.list$name[s],"/Save.RData"), recursive=TRUE)
}




#' Function to calculate design-based survey abundance estimate from RACE survey data.
#'
#' @param species.codes vector of species codes for which data will be returned
#' @param survey string indicating the survey for which data are being extracted: GOA, AI, EBS_SHELF, EBS_SLOPE
#' @param combineSpecies boolean indicating whether species codes should be combined into a single index (i.e. Dusky Rockfish)
#' @param reg.area string indicating Regulatory Area Name for 
#'
#' @return data frame containing annual survey biomass estimate, variance, SD, and CV.
#' @export
calc_design_based_index <- function(species.codes, combineSpecies=FALSE, survey, reg.area=NULL) {
  ### TESTING VALUES
  # species.codes <- 21720 #Pacific Cod
  # survey <- 'GOA'
  # reg.area <- 'WESTERN GOA'
  ###
  require(dplyr)
  # source("R/load-RACE-data.r")
  
  
  #Input Checking...
  # if(Region %in% c("Gulf_of_Alaska", "Eastern_Bering_Sea", "Aleutian_Islands")) {
  #   if(Region=="Gulf_of_Alaska") { area <- "GOA"; survey <- "GOA" }
  #   if(Region=="Eastern_Bering_Sea") { area <- "BS"; stop("Error: Currently not implemented for EBS, need to correct slope vs shelf survey") }
  #   if(Region=="Aleutian_Islands") { area <- "AI"; survey <- "AI" }
  # }else {
  #   stop("Region must be one of: Gulf_of_Alaska, Eastern_Bering_Sea, Aleutian_Islands")
  # }
  
  
  # if(length(species.codes)>1) { stop("Error: calc_design_based_index is currently only tested for a single species.") }
  if(survey %in% c("GOA","AI","EBS_SHELF",'EBS_SLOPE')) { 
    if(survey=="GOA") { Region <- "Gulf_of_Alaska"; area <- "GOA" }
    if(survey=="AI") { Region <- "Aleutian_Islands"; area <- "AI" }
    if(survey=="EBS_SHELF" | survey=="EBS_SLOPE") { Region <- "Eastern_Bering_Sea"; area <- "BS" }
    
  }else {
    stop(paste("survey is:",survey,", should be one of: GOA, AI, EBS_SHELF, EBS_SLOPE"))
  }
  
  
  #Load RACE survey data
  load.data <- load_RACE_data(species.codes=species.codes, combineSpecies=combineSpecies, survey=survey, writeCSV=FALSE, writeDATA=FALSE)
  
  #Calculate design-based estimator
  
  strata <- sort(unique(load.data$STRATUM))
  n.strata <- length(strata)
  
  #Calculate sum and var in cpue by year and stratum
  # cstrat<-ddply(load.data ,c("Year","STRATUM"), summarize, CPUE=sum(cpue), CPUEvar=var(cpue)) #plyr
  cstrat <- data.frame(load.data %>% group_by(Year, STRATUM) %>% summarize(CPUE=sum(cpue), CPUEvar=var(cpue))) #dplyr
  
  #Calculate number of hauls in each year and stratum
  # hstrat <- ddply(load.data, c("Year","STRATUM"), summarize, n_sta=length(unique(HAULJOIN))) #plyr
  hstrat <- data.frame(load.data %>% group_by(Year, STRATUM) %>% summarize(n_sta=length(unique(HAULJOIN)))) #dplyr
  
  #Join together
  # biomvar <- merge(cstrat, hstrat, by.x=c("Year","STRATUM"), by.y=c("Year","STRATUM"), all.x=TRUE) #all.x=TRUE, all.y=FALSE
  biomvar <- left_join(cstrat, hstrat, by=c("Year", "STRATUM")) #dplyr
  colnames(biomvar) <- c("YEAR","STRATUM","CPUE","VAR","n_stations")
  
  
  #Load Strata Data
  strata.data <- read.csv("data/race_stratum_info.csv", header=TRUE)
  #Limit to correct survey area
  strata.area <- strata.data[strata.data$Survey==survey,c(2,3,5,12)] #NEEDS TO BE UPDATED
  names(strata.area) <- c("STRATUM","AREA","INPFC_AREA","REGULATORY.AREA")
  
  # biomvar <- merge(biomvar, strata.area, by=c("STRATUM"), all.x=TRUE) #plyr
  biomvar <- left_join(biomvar, strata.area, by=c("STRATUM")) #dplyr
  
  #####
  if(!is.null(reg.area)) {
    if(survey=='GOA') {
      if(reg.area %in% c("WESTERN GOA","CENTRAL GOA","EASTERN GOA")) {
        biomvar <- biomvar[biomvar$REGULATORY.AREA==reg.area,]
      }else {
        stop(paste0("Current reg.area: ", reg.area, ", must be one of WESTERN GOA, CENTRAL GOA, EASTERN GOA"))
      }
    }else {
      stop("Regulatory Area Subsampling Currently only Implemented for Gulf of Alaska")
    }
  }
  ####
  
  biomvar$BIOMASS<-(biomvar$CPUE/biomvar$n_stations)*biomvar$AREA
  biomvar$VAR2<-biomvar$AREA^2*(biomvar$VAR/biomvar$n_stations)
  
  #Calculate complete
  biomass <- data.frame(biomvar %>% group_by(YEAR) %>%
                          summarize(Biomass=sum(BIOMASS,na.rm=TRUE)/1e3,
                                    Variance=sum(VAR2,na.rm=TRUE)/(1e3^2),
                                    SD=sqrt(sum(VAR2,na.rm=TRUE))/1e3,
                                    CV=SD/(sum(BIOMASS,na.rm=TRUE)/1e3)))
  
  return(biomass)
  
}

#' Delete extra VAST files to reduce overhead, after estimation has been completed 
#'
#' @param DateFile string with file extension for current directory
#' @param Version string identifying VAST version
#'
#' @export
cleanup_VAST_file <- function(DateFile, Version="VAST_v2_4_0") {
  #Delete VAST .o ~ 14,387 KB
  unlink(paste0(DateFile,Version,".o"))
  unlink(paste0(DateFile,Version,".cpp"))
  
  
  #Delete VAST .dll ~ 13,3559 KB
  if(.Platform$OS.type=='windows') {
    unlink(paste0(DateFile,Version,".dll"))
  }
  if(.Platform$OS.type=='unix') {
    unlink(paste0(DateFile,Version,".so"))
  }
}


#' Create the Data_Geostat dataframe for vast, based on RACE survey data
#'
#' @param species.codes vector of species codes for which data will be returned
#' @param lat_lon.def string defining how tow-specific Latitude and Longitude will be calculated
#' @param survey string indicating the survey for which data are being extracted: GOA, AI, EBS_SHELF, EBS_SLOPE
#' @param combineSpecies boolean indicating whether species codes should be combined into a single index (i.e. Dusky Rockfish)
#'
#' @return dataframe Data_Geostat with input data for VAST
#' @export
create_Data_Geostat <- function(species.codes, combineSpecies=FALSE, lat_lon.def="start", survey="GOA") {
  ###TESTING###
  # species.codes <- c(30152)#,30420)
  # lat_lon.def <- "mean"
  # survey <- 'EBS_SHELF'
  #############
  
  # source("R/load-RACE-data.r")
  
  #Check Inputs
  if(!lat_lon.def %in% c("mean", "start", "end")) { stop("lat_lon.def must be mean, start, or end") }
  if(!survey %in% c("GOA","AI","EBS_SHELF",'EBS_SLOPE')) { stop(paste("survey is:",survey,", should be one of: GOA, AI, EBS_SHELF, EBS_SLOPE"))  }
  
  #Get Data
  load.data <- load_RACE_data(species.codes=species.codes, combineSpecies=combineSpecies, survey=survey)
  
  #Create VAST input data object
  Data_Geostat <- NULL
  #List Species, if multispecies
  if(length(species.codes) > 1) {
    Data_Geostat$spp <- load.data$Common.Name
  }
  Data_Geostat$Catch_KG <- load.data$WEIGHT  
  Data_Geostat$Year <- load.data$Year 
  Data_Geostat$Vessel <- load.data$VESSEL.x
  Data_Geostat$AreaSwept_km2 <- load.data$effort
  
  #Define Lat and Lon
  if(lat_lon.def=="start") {
    Data_Geostat$Lat <- load.data$START_LATITUDE
    Data_Geostat$Lon <- load.data$START_LONGITUDE
  }
  if(lat_lon.def=="end") {
    Data_Geostat$Lat <- load.data$END_LATITUDE
    Data_Geostat$Lon <- load.data$END_LONGITUDE
  }
  if(lat_lon.def=="mean") {
    Data_Geostat$Lat <- rowMeans(cbind(load.data$START_LATITUDE, load.data$END_LATITUDE), na.rm=TRUE)
    Data_Geostat$Lon <- rowMeans(cbind(load.data$START_LONGITUDE, load.data$END_LONGITUDE), na.rm=TRUE)
  }
  
  #Convert to data frame
  Data_Geostat <- data.frame(Data_Geostat)
  return(Data_Geostat)
}


#######
#' Create input objects for VAST model - New with new VAST functions
#'   NOTE: Currently only tested for single-species application
#'
#' @param species.codes vector of species codes to be evaluated
#' @param lat_lon.def string defining how tow-specific Latitude and Longitude will be calculated
#' @param Method 
#' @param grid_size_km 
#' @param n_x 
#' @param Kmeans_Config 
#' @param strata.limits dataframe of strata limits for post-hoc apportionment
#' @param survey string indicating the survey for which data are being extracted: GOA, AI, EBS_SHELF, EBS_SLOPE
#' @param DateFile path for directory housing VAST model and output figures and objects
#' @param FieldConfig 
#' @param RhoConfig 
#' @param OverdispersionConfig 
#' @param ObsModel 
#' @param Options 
#' @param save.Record boolean indicating whether or not VAST settings record is saved
#' @param combineSpecies boolean indicating whether species codes should be combined into a single index (i.e. Dusky Rockfish)
#' @param Version version number for VAST cpp
#'
#' @return VAST_input: Containing Data_Geostat, Spatial_List, Extrapolation_List, and TmbData
#' @export
create_VAST_input_new <- function(species.codes, combineSpecies=FALSE, lat_lon.def="mean", save.Record=TRUE,
                              Method="Mesh", grid_size_km=25, n_x=250,
                              Kmeans_Config=list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 ),
                              strata.limits=NULL, survey="GOA",
                              DateFile=paste0(getwd(),'/VAST_output/'),
                              FieldConfig, RhoConfig, OverdispersionConfig,
                              ObsModel, Options, Version="VAST_v2_8_0") {
  
  # source('R/create-Data-Geostat.r')
  
  #vERSION NUMBER
  # Version  <- "VAST_v2_4_0"
  # require(SpatialDeltaGLMM)
  #DATA Set
  Data_Set <- "VAST_EVAL"
  
  #Operation if no strata.limits are defined
  if(is.null(strata.limits)) {
    strata.limits <- data.frame(STRATA = c("All_areas"))#,
    # west_border = c(-Inf),
    # east_border = c(Inf))
  }
  
  #Determine Correct area to allign with RACE survey
  if(survey %in% c("GOA","AI","EBS_SHELF",'EBS_SLOPE')) { 
    if(survey=="GOA") { Region <- "Gulf_of_Alaska"; area <- "GOA" }
    if(survey=="AI") { Region <- "Aleutian_Islands"; area <- "AI" }
    if(survey=="EBS_SHELF" | survey=="EBS_SLOPE") { Region <- "Eastern_Bering_Sea"; area <- "BS" }
    
  }else {
    stop(paste("survey is:",survey,", should be one of: GOA, AI, EBS_SHELF, EBS_SLOPE"))
  }
  
  # Region <- "Other"
  
  #Retreive Data
  Data_Geostat <- create_Data_Geostat(species.codes=species.codes, combineSpecies=combineSpecies, 
                                      lat_lon.def=lat_lon.def, survey=survey) 
  
  #Build Extrapolition Grid
  start.time <- date()
  if(Region=="Other") {
    Extrapolation_List  <- FishStatsUtils::make_extrapolation_info(Region=Region, strata.limits=strata.limits,
                                                                           observations_LL=Data_Geostat[,c("Lat","Lon")],
                                                                           maximum_distance_from_sample=15)
  }else {
    Extrapolation_List  <- FishStatsUtils::make_extrapolation_info(Region=Region, strata.limits=strata.limits)
  }
  end.time <- date()
  #Create Location for Saving Files
  dir.create(DateFile, recursive=TRUE) #Recursive may need to be false if other elements exist
  
  #Save Settings for Later reference
  if(save.Record==TRUE) {
    warning("Currently save.Record=FALSE only accepted, easy-read records will not be saved.")
    # Record = ThorsonUtilities::bundlelist(c("Data_Set"=Data_Set,
    #                                         "Version"=Version, "Method"=Method, "grid_size_km"=grid_size_km,
    #                                         "n_x"=n_X, "FieldConfig"=FieldConfig,
    #                                         "RhoConfig"=RhoConfig, "OverdispersionConfig"=OverdispersionConfig,
    #                                         "ObsModel"=ObsModel,
    #                                         "Kmeans_Config"=Kmeans_Config))
    # # save(Record, file = file.path(DateFile, "Record.RData"))
    # capture.output(Record, file = paste0(DateFile, "Record.txt"))
    
  }
  #Generate Information used for spatio-temporal estimation
  Spatial_List <- FishStatsUtils::make_spatial_info(grid_size_km = grid_size_km,
                                                           n_x = n_x, Method = Method, Lon = Data_Geostat[,"Lon"], Lat = Data_Geostat[, "Lat"],
                                                           Extrapolation_List = Extrapolation_List,
                                                           randomseed = Kmeans_Config[["randomseed"]],
                                                           nstart = Kmeans_Config[["nstart"]],
                                                           iter.max = Kmeans_Config[["iter.max"]], DirPath = DateFile,
                                                           Save_Results = TRUE)
  #Update Data_Geostat
  Data_Geostat <- cbind(Data_Geostat, knot_i = Spatial_List$knot_i)
  
  #Build VAST model data input
  
  if(length(species.codes) > 1 & combineSpecies==FALSE) {
    #MULTISPECIES
    TmbData <- VAST::make_data(Version=Version, FieldConfig=FieldConfig, 
                             OverdispersionConfig=OverdispersionConfig,
                             RhoConfig=RhoConfig, ObsModel=ObsModel, c_i=as.numeric(Data_Geostat[,'spp'])-1,
                             b_i=Data_Geostat[,'Catch_KG'], a_i=Data_Geostat[,'AreaSwept_km2'],
                             v_i=as.numeric(Data_Geostat[,'Vessel'])-1, s_i=Data_Geostat[,'knot_i']-1,
                             t_i=Data_Geostat[,'Year'], a_xl=Spatial_List$a_xl, MeshList=Spatial_List$MeshList,
                             GridList=Spatial_List$GridList, Method=Spatial_List$Method, Options=Options,
                             spatial_list=Spatial_List) #Added this line at recommendation
  }else {
    #SINGLE SPECIES
    TmbData <- VAST::make_data(Version = Version, FieldConfig = FieldConfig,
                             OverdispersionConfig = OverdispersionConfig, RhoConfig = RhoConfig,
                             ObsModel = ObsModel, c_i = rep(0, nrow(Data_Geostat)),
                             b_i = Data_Geostat[, "Catch_KG"], a_i = Data_Geostat[,"AreaSwept_km2"],
                             v_i = as.numeric(Data_Geostat[,"Vessel"]) - 1,
                             s_i = Data_Geostat[, "knot_i"] - 1, t_i = Data_Geostat[, "Year"],
                             a_xl = Spatial_List$a_xl,
                             MeshList = Spatial_List$MeshList, GridList = Spatial_List$GridList,
                             Method = Spatial_List$Method, Options = Options,
                             spatial_list=Spatial_List) #Added this line at recommendation
  }
  
  # Make Settings ==============================
  settings <- FishStatsUtils::make_settings(n_x=n_x, Region=Region, purpose="index",
                                            fine_scale=FALSE, strata.limits=strata.limits,
                                            FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                            OverdispersionConfig=OverdispersionConfig,
                                            ObsModel=ObsModel, bias.correct=bias.correct,
                                            Options=Options, use_anisotropy=TRUE,
                                            vars_to_correct = "Index_cyl", 
                                            Version=Version, treat_nonencounter_as_zero=FALSE)
  
  # Make Mapping Data =======================
  MapDetails_List <- FishStatsUtils::make_map_info(Region=Region, Extrapolation_List=Extrapolation_List, 
                                  spatial_list = Spatial_List,
                                  NN_Extrap = Spatial_List$PolygonList$NN_Extrap,
                                  fine_scale = Spatial_List$fine_scale,
                                  Include = (Extrapolation_List[["Area_km2_x"]] > 0 &
                                               Extrapolation_List[["a_el"]][, 1] > 0))
  
  #Return Section
  VAST_input <- NULL
  VAST_input$Data_Geostat <- Data_Geostat
  VAST_input$Spatial_List <- Spatial_List
  VAST_input$TmbData <- TmbData
  VAST_input$Extrapolation_List <- Extrapolation_List
  VAST_input$settings <- settings
  VAST_input$MapDetails_List <- MapDetails_List
  
  return(VAST_input)
}


##### TESTING #####





#######
#' Create input objects for VAST model
#'   NOTE: Currently only tested for single-species application
#'
#' @param species.codes vector of species codes to be evaluated
#' @param lat_lon.def string defining how tow-specific Latitude and Longitude will be calculated
#' @param Method 
#' @param grid_size_km 
#' @param n_x 
#' @param Kmeans_Config 
#' @param strata.limits dataframe of strata limits for post-hoc apportionment
#' @param survey string indicating the survey for which data are being extracted: GOA, AI, EBS_SHELF, EBS_SLOPE
#' @param DateFile path for directory housing VAST model and output figures and objects
#' @param FieldConfig 
#' @param RhoConfig 
#' @param OverdispersionConfig 
#' @param ObsModel 
#' @param Options 
#' @param save.Record boolean indicating whether or not VAST settings record is saved
#' @param combineSpecies boolean indicating whether species codes should be combined into a single index (i.e. Dusky Rockfish)
#' @param Version version number for VAST cpp
#'
#' @return VAST_input: Containing Data_Geostat, Spatial_List, Extrapolation_List, and TmbData
#' @export
create_VAST_input <- function(species.codes, combineSpecies=FALSE, lat_lon.def="mean", save.Record=TRUE,
                                Method="Mesh", grid_size_km=25, n_x=250,
                                Kmeans_Config=list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 ),
                                strata.limits=NULL, survey="GOA",
                                DateFile=paste0(getwd(),'/VAST_output/'),
                                FieldConfig, RhoConfig, OverdispersionConfig,
                                ObsModel, Options, Version="VAST_v2_8_0") {
  
  # source('R/create-Data-Geostat.r')
  
  #vERSION NUMBER
  # Version  <- "VAST_v2_4_0"
  # require(SpatialDeltaGLMM)
  #DATA Set
  Data_Set <- "VAST_EVAL"
  
  #Operation if no strata.limits are defined
  if(is.null(strata.limits)) {
    strata.limits <- data.frame(STRATA = c("All_areas"))#,
                                # west_border = c(-Inf),
                                # east_border = c(Inf))
  }
  
  #Determine Correct area to allign with RACE survey
  if(survey %in% c("GOA","AI","EBS_SHELF",'EBS_SLOPE')) { 
    if(survey=="GOA") { Region <- "Gulf_of_Alaska"; area <- "GOA" }
    if(survey=="AI") { Region <- "Aleutian_Islands"; area <- "AI" }
    if(survey=="EBS_SHELF" | survey=="EBS_SLOPE") { Region <- "Eastern_Bering_Sea"; area <- "BS" }
    
  }else {
    stop(paste("survey is:",survey,", should be one of: GOA, AI, EBS_SHELF, EBS_SLOPE"))
  }
  
  # Region <- "Other"
  
  #Retreive Data
  Data_Geostat <- create_Data_Geostat(species.codes=species.codes, combineSpecies=combineSpecies, 
                                        lat_lon.def=lat_lon.def, survey=survey) 
  
  #Build Extrapolition Grid
  start.time <- date()
  if(Region=="Other") {
    Extrapolation_List  <- SpatialDeltaGLMM::Prepare_Extrapolation_Data_Fn(Region=Region, strata.limits=strata.limits,
                                                                             observations_LL=Data_Geostat[,c("Lat","Lon")],
                                                                             maximum_distance_from_sample=15)
  }else {
    Extrapolation_List  <- SpatialDeltaGLMM::Prepare_Extrapolation_Data_Fn(Region=Region, strata.limits=strata.limits)
  }
  end.time <- date()
  #Create Location for Saving Files
  dir.create(DateFile, recursive=TRUE) #Recursive may need to be false if other elements exist
  
  #Save Settings for Later reference
  if(save.Record==TRUE) {
    warning("Currently save.Record=FALSE only accepted, easy-read records will not be saved.")
    # Record = ThorsonUtilities::bundlelist(c("Data_Set"=Data_Set,
    #                                         "Version"=Version, "Method"=Method, "grid_size_km"=grid_size_km,
    #                                         "n_x"=n_X, "FieldConfig"=FieldConfig,
    #                                         "RhoConfig"=RhoConfig, "OverdispersionConfig"=OverdispersionConfig,
    #                                         "ObsModel"=ObsModel,
    #                                         "Kmeans_Config"=Kmeans_Config))
    # # save(Record, file = file.path(DateFile, "Record.RData"))
    # capture.output(Record, file = paste0(DateFile, "Record.txt"))
  
  }
  #Generate Information used for spatio-temporal estimation
  Spatial_List <- SpatialDeltaGLMM::Spatial_Information_Fn(grid_size_km = grid_size_km,
                                         n_x = n_x, Method = Method, Lon = Data_Geostat[,"Lon"], Lat = Data_Geostat[, "Lat"],
                                         Extrapolation_List = Extrapolation_List,
                                         randomseed = Kmeans_Config[["randomseed"]],
                                         nstart = Kmeans_Config[["nstart"]],
                                         iter.max = Kmeans_Config[["iter.max"]], DirPath = DateFile,
                                         Save_Results = FALSE)
  #Update Data_Geostat
  Data_Geostat <- cbind(Data_Geostat, knot_i = Spatial_List$knot_i)
  
  #Build VAST model data input
  
  if(length(species.codes) > 1 & combineSpecies==FALSE) {
    #MULTISPECIES
    TmbData <- VAST::Data_Fn(Version=Version, FieldConfig=FieldConfig, 
                      OverdispersionConfig=OverdispersionConfig,
                      RhoConfig=RhoConfig, ObsModel=ObsModel, c_i=as.numeric(Data_Geostat[,'spp'])-1,
                      b_i=Data_Geostat[,'Catch_KG'], a_i=Data_Geostat[,'AreaSwept_km2'],
                      v_i=as.numeric(Data_Geostat[,'Vessel'])-1, s_i=Data_Geostat[,'knot_i']-1,
                      t_i=Data_Geostat[,'Year'], a_xl=Spatial_List$a_xl, MeshList=Spatial_List$MeshList,
                      GridList=Spatial_List$GridList, Method=Spatial_List$Method, Options=Options )
  }else {
    #SINGLE SPECIES
    TmbData <- VAST::Data_Fn(Version = Version, FieldConfig = FieldConfig,
                      OverdispersionConfig = OverdispersionConfig, RhoConfig = RhoConfig,
                      ObsModel = ObsModel, c_i = rep(0, nrow(Data_Geostat)),
                      b_i = Data_Geostat[, "Catch_KG"], a_i = Data_Geostat[,"AreaSwept_km2"],
                      v_i = as.numeric(Data_Geostat[,"Vessel"]) - 1,
                      s_i = Data_Geostat[, "knot_i"] - 1, t_i = Data_Geostat[, "Year"],
                      a_xl = Spatial_List$a_xl,
                      MeshList = Spatial_List$MeshList, GridList = Spatial_List$GridList,
                      Method = Spatial_List$Method, Options = Options)
  }

  
  
  #Return Section
  VAST_input <- NULL
  VAST_input$Data_Geostat <- Data_Geostat
  VAST_input$Spatial_List <- Spatial_List
  VAST_input$TmbData <- TmbData
  VAST_input$Extrapolation_List <- Extrapolation_List
  
  return(VAST_input)
}


##### TESTING #####





#' @title
#' Get VAST model-based survey index and uncertainty
#' 
#' @description 
#' \code{get_VAST_index} returns table of index values and errors.
#'
#' @param TmbData Input data object for VAST model
#' @param Sdreport Output object with parameter and uncertainty (delta-method) estimates from vAST
#' @param bias.correct Boolean for whether bias corrected results should be returned
#' @param Data_Geostat 
#'
#' @return A table of index values, by year, with associated standard deviation

#' @export
#'
get_VAST_index <- function(TmbData, Sdreport=Opt[["SD"]], bias.correct, Data_Geostat=NULL) {
  require(VAST)
  require(TMB)
  require(TMBhelper)
  ###Testing
  # Sdreport=Opt[["SD"]]
  # bias.correct <- FALSE
  
  #Initial Checkup
  # Which parameters
  if( "ln_Index_tl" %in% rownames(TMB::summary.sdreport(Sdreport)) ){
    # SpatialDeltaGLMM
    ParName = "Index_tl"
    TmbData[['n_c']] = 1
  }
  if( "ln_Index_ctl" %in% rownames(TMB::summary.sdreport(Sdreport)) ){
    # VAST Version < 2.0.0
    ParName = "Index_ctl"
  }
  if( "ln_Index_cyl" %in% rownames(TMB::summary.sdreport(Sdreport)) ){
    # VAST Version >= 2.0.0
    ParName = "Index_cyl"
    TmbData[["n_t"]] = nrow(TmbData[["t_yz"]])
  }
  if( "Index_tp" %in% rownames(TMB::summary.sdreport(Sdreport)) ){
    # SpatialVAM
    ParName = "Index_tp"
    TmbData[["n_l"]] = 1
    TmbData[["n_c"]] = TmbData[["n_p"]]
  }
  
  #Retreive Years
  if(is.null(Data_Geostat)) {
    Year_Set = 1:TmbData$n_t
    Years2Include = 1:TmbData$n_t
  }else {
    Year_Set <- seq(min(Data_Geostat[,'Year']),max(Data_Geostat[,'Year']))
    Years2Include = which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))
  }
  strata_names = 1:TmbData$n_l
  category_names = 1:TmbData$n_c
  
  # Extract index
  if( ParName %in% c("Index_tl","Index_ctl","Index_cyl")){
    if( bias.correct==TRUE && "unbiased"%in%names(Sdreport) ){
      log_Index_ctl = array( c(Sdreport$unbiased$value[which(names(Sdreport$value)==paste0("ln_",ParName))],TMB::summary.sdreport(Sdreport)[which(rownames(TMB::summary.sdreport(Sdreport))==paste0("ln_",ParName)),'Std. Error']), dim=c(unlist(TmbData[c('n_c','n_t','n_l')]),2), dimnames=list(NULL,NULL,NULL,c('Estimate','Std. Error')) )
      Index_ctl = array( c(Sdreport$unbiased$value[which(names(Sdreport$value)==ParName)],TMB::summary.sdreport(Sdreport)[which(rownames(TMB::summary.sdreport(Sdreport))==ParName),'Std. Error']), dim=c(unlist(TmbData[c('n_c','n_t','n_l')]),2), dimnames=list(NULL,NULL,NULL,c('Estimate','Std. Error')) )
    }else{
      log_Index_ctl = array( TMB::summary.sdreport(Sdreport)[which(rownames(TMB::summary.sdreport(Sdreport))==paste0("ln_",ParName)),], dim=c(unlist(TmbData[c('n_c','n_t','n_l')]),2), dimnames=list(NULL,NULL,NULL,c('Estimate','Std. Error')) )
      Index_ctl = array( TMB::summary.sdreport(Sdreport)[which(rownames(TMB::summary.sdreport(Sdreport))==ParName),], dim=c(unlist(TmbData[c('n_c','n_t','n_l')]),2), dimnames=list(NULL,NULL,NULL,c('Estimate','Std. Error')) )
    }
  }
  if( ParName %in% c("Index_tp")){
    if( bias.correct==TRUE && "unbiased"%in%names(Sdreport) ){
      Index_ctl = aperm( array( c(Sdreport$unbiased$value[which(names(Sdreport$value)==ParName)],TMB::summary.sdreport(Sdreport)[which(rownames(TMB::summary.sdreport(Sdreport))==ParName),'Std. Error']), dim=c(unlist(TmbData[c('n_t','n_c','n_l')]),2), dimnames=list(NULL,NULL,NULL,c('Estimate','Std. Error')) ), perm=c(2,1,3))
      if( "ln_Index_tp" %in% rownames(TMB::summary.sdreport(Sdreport))){
        log_Index_ctl = aperm( array( c(Sdreport$unbiased$value[which(names(Sdreport$value)==paste0("ln_",ParName))],TMB::summary.sdreport(Sdreport)[which(rownames(TMB::summary.sdreport(Sdreport))==paste0("ln_",ParName)),'Std. Error']), dim=c(unlist(TmbData[c('n_t','n_c','n_l')]),2), dimnames=list(NULL,NULL,NULL,c('Estimate','Std. Error')) ), perm=c(2,1,3))
      }else{
        log_Index_ctl = log( Index_ctl )
        log_Index_ctl[,,,'Std. Error'] = log_Index_ctl[,,,'Std. Error'] / log_Index_ctl[,,,'Estimate']
        warning( "Using kludge for log-standard errors of index, to be replaced in later versions of 'SpatialVAM'" )
      }
    }else{
      Index_ctl = aperm( array( TMB::summary.sdreport(Sdreport)[which(rownames(TMB::summary.sdreport(Sdreport))==ParName),], dim=c(unlist(TmbData[c('n_t','n_c','n_l')]),2), dimnames=list(NULL,NULL,NULL,c('Estimate','Std. Error')) ), perm=c(2,1,3,4))
      if( "ln_Index_tp" %in% rownames(TMB::summary.sdreport(Sdreport))){
        log_Index_ctl = aperm( array( TMB::summary.sdreport(Sdreport)[which(rownames(TMB::summary.sdreport(Sdreport))==paste0("ln_",ParName)),], dim=c(unlist(TmbData[c('n_t','n_c','n_l')]),2), dimnames=list(NULL,NULL,NULL,c('Estimate','Std. Error')) ), perm=c(2,1,3,4))
      }else{
        log_Index_ctl = log( Index_ctl )
        log_Index_ctl[,,,'Std. Error'] = log_Index_ctl[,,,'Std. Error'] / log_Index_ctl[,,,'Estimate']
        warning( "Using kludge for log-standard errors of index, to be replaced in later versions of 'SpatialVAM'" )
      }
    }
  }
  
  Table = NULL
  for( cI in 1:TmbData$n_c ){
    Tmp = data.frame( "Year"=Year_Set, "Unit"=1, "Fleet"=rep(strata_names,each=TmbData$n_t), "Estimate_metric_tons"=as.vector(Index_ctl[cI,,,'Estimate']), "SD_log"=as.vector(log_Index_ctl[cI,,,'Std. Error']), "SD_mt"=as.vector(Index_ctl[cI,,,'Std. Error']) )
    if( TmbData$n_c>1 ) Tmp = cbind( "Category"=category_names[cI], Tmp)
    Table = rbind( Table, Tmp )
  }
  
  
  return(Table)
  
  
}

#' Function to load RACE bottom trawl data. 
#' Catch and haul data are merged with species names, and zeros are added for missing no-catch observations.
#'
#' @param species.codes vector of species codes for which data will be returned
#' @param survey string indicating the survey for which data are being extracted: GOA, AI, EBS_SHELF, EBS_SLOPE
#' @param writeCSV boolean indicating whether "output/RACE_data_output.csv" is created
#' @param writeDATA boolean indicating whether "output/RACE_data_output.RData" is created
#' @param combineSpecies boolean indicating whether species codes should be combined into a single index (i.e. Dusky Rockfish)
#'
#' @return A data frame of RACE bottom trawl data, with rows equal to species-by-haul observations
#' @export
load_AKFIN_data <- function(species.codes=c(30150,30152), combineSpecies=FALSE, survey="GOA", writeCSV=FALSE, writeDATA=FALSE) {
  require(FishData)
  require(dplyr)
  require(readr)
  ##TESTING###
  species.codes <- 30420
  combineSpecies <- FALSE
  survey <- 'GOA'
  writeCSV <- FALSE
  writeDATA <- FALSE
  #############
  
  if(survey %in% c("GOA","AI","EBS_SHELF",'EBS_SLOPE')) { 
    if(survey=="GOA") { Region <- "Gulf_of_Alaska"; area <- "GOA" }
    if(survey=="AI") { Region <- "Aleutian_Islands"; area <- "AI" }
    if(survey=="EBS_SHELF" | survey=="EBS_SLOPE") { Region <- "Eastern_Bering_Sea"; area <- "BS" }
    
  }else {
    stop(paste("survey is:",survey,", should be one of: GOA, AI, EBS_SHELF, EBS_SLOPE"))
  }
  #FOR ORIGINAL .CSV INPUT
  akfin.haul <- readr::read_csv(file.path("data","AKFIN Fall 2019","Haul Descriptions.csv"))
  head(akfin.haul)
  akfin.cpue <- readr::read_csv(file.path("data","AKFIN Fall 2019","race_cpue_by_haul.csv"), skip=5)
  head(akfin.cpue)
  
  # Save as .rds for quicker read-in
  # saveRDS(akfin.haul, )
  
  # akfin.catch <- readr::read_csv(file.path("data","AKFIN Fall 2019","race_catch_by_haul.csv"), skip=5)
  # head(akfin.catch)
  
  
    #FOR NEW .RData INPUT
    #Opening section to determine suitability
    # if(file.exists("data/race_base_haul.RData")==FALSE) { stop("data/race_base_haul.RData NOT FOUND") }
    # if(file.exists("data/race_base_catch.RData")==FALSE) { stop("data/race_base_catch.RData NOT FOUND") }
    if(file.exists("data/race_base_haul.rds")==FALSE) { stop("data/race_base_haul.rds NOT FOUND") }
    if(file.exists("data/race_base_catch.rds")==FALSE) { stop("data/race_base_catch.rds NOT FOUND") }
    if(file.exists("data/race_species_codes.csv")==FALSE) { stop("data/race_species_codes.csv NOT FOUND") }
    if(file.exists("data/race_cruise_info.csv")==FALSE) { stop("data/race_cruise_info.csv NOT FOUND") }
    
    #Load catch and haul data
    # load("data/race_base_catch.RData")
    # load("data/race_base_haul.RData")
    catch <- readRDS("data/race_base_catch.rds")
    haul <- readRDS("data/race_base_haul.rds")
    #Limit to only certified abundance hauls
    haul <- haul[haul$ABUNDANCE_HAUL=='Y',]
    
    
    #Merge data
    # catchhaul <- merge(x=catch, y=haul, all.y=TRUE, all.x=FALSE, by.x="HAULJOIN", by.y="HAULJOIN")
    #dplyr it for greater speed
    catchhaul <- right_join(x=catch, y=haul, by=c("HAULJOIN"))
    
    #Add in zero observations for catch weight, for no catches.
    #  Drawing on Jim Thorson's code from FishData
    #NOTE: species.codes are now treated as a factor.
    catchhaul.2 <- FishData::add_missing_zeros(data_frame=catchhaul, unique_sample_ID_colname="HAULJOIN",
                                               sample_colname="WEIGHT", species_colname="SPECIES_CODE",
                                               species_subset=species.codes,
                                               if_multiple_records="First",
                                               Method="Fast")
    
    
    
    
    #Bring in cruise info
    #Add year of survey and name
    cruise.info <- read.csv("data/race_cruise_info.csv", header=TRUE, stringsAsFactors=FALSE)
    # catchhaul.3 <- left_join(x=catchhaul.2, y=cruise.info[,c("Cruise.Join.ID","Year","Survey")],
    #                            by=c("CRUISEJOIN.x"="Cruise.Join.ID"))
    
    #User inner_join because it removes hauls WITHOUT identified Year and Survey
    catchhaul.3 <- inner_join(x=catchhaul.2, y=cruise.info[,c("Cruise.Join.ID","Year","Survey")],
                              by=c("CRUISEJOIN.x"="Cruise.Join.ID"))
    
    
    # #Find difference
    # loc <- which(catchhaul.3$CRUISEJOIN.x %in% catchhaul.3.in$CRUISEJOIN.x)
    # catchhaul.3[-loc,]
  }else {
    
    
    # Using AKFIN DATA ===========================================
    akfin.haul <- readr::read_csv(file.path("data","AKFIN Fall 2019","Haul Descriptions.csv"))
    head(akfin.haul)
    akfin.cpue <- readr::read_csv(file.path("data","AKFIN Fall 2019","race_cpue_by_haul.csv"), skip=5)
    head(akfin.cpue)
    # akfin.catch <- readr::read_csv(file.path("data","AKFIN Fall 2019","race_catch_by_haul.csv"), skip=5)
    # head(akfin.catch)
    
    akfin.haul <- akfin.haul
    
    #=============================================================
    
  }
  #Limit to specific survey
  catchhaul.3 <- catchhaul.3[catchhaul.3$Survey==survey,]
  # catchhaul.3 <- catchhaul.3[catchhaul.3$Survey==survey & catchhaul.3$REGION.x==area,]
  
  #=================================================
  #AGGREGATE CATCH BIOMASS ACROSS SPECIES CODES IN THE CASE OF A COMBINED INDEX
  #  Dusky Rockfish Example 2 species codes to single index
  #   Variables to combine: WEIGHT
  if(combineSpecies==TRUE) {
    catchhaul.4 <- data.frame(catchhaul.3 %>% group_by(HAULJOIN) %>% 
                                mutate('WEIGHT'=sum(WEIGHT, na.rm=TRUE)))
    #Since we have aggregated, only retain rows for 1st listed species code
    catchhaul.5 <- catchhaul.4[catchhaul.4$SPECIES_CODE==species.codes[1],]
  }else {
    catchhaul.5 <- catchhaul.3
  }
  #=================================================
  
  
  #Calculate and add Effort and CPUE
  catchhaul.5$effort <- catchhaul.5$NET_WIDTH*catchhaul.5$DISTANCE_FISHED/1000
  catchhaul.5$cpue <- catchhaul.5$WEIGHT/catchhaul.5$effort
  
  #Add species name
  species.code.data <- read.csv("data/race_species_codes.csv", header=TRUE, stringsAsFactors=FALSE)
  
  #Convert codes to a factor
  # species.code.data$Species.Code <- as.factor(species.code.data$Species.Code)
  # 
  # output <- left_join(x=catchhaul.3, y=species.code.data[,c("Species.Code","Common.Name")],
  #                      by=c("SPECIES_CODE"="Species.Code"))
  output <- merge(x=catchhaul.5, y=species.code.data[,c("Species.Code","Common.Name")], 
                  by.x="SPECIES_CODE", by.y="Species.Code")
  
  #Return Section
  if(writeCSV==TRUE) { write.csv(output, file="output/RACE_data_output.csv") }
  if(writeDATA==TRUE) { save(output, file="output/RACE_data_output.RData") }
  
  return(output)
}


##### TESTING FUNCTION #####
# #Northern Rockfish
# temp <- load_RACE_data(species.codes=c(30420), combineSpecies=FALSE, survey='GOA', writeCSV=FALSE, writeDATA=FALSE)
# dim(temp)
# 
# #Dusky Rockfish partial
# temp.2 <- load_RACE_data(species.codes=c(30150), combineSpecies=FALSE, survey='GOA', writeCSV=FALSE, writeDATA=FALSE) 
# dim(temp.2)
# 
# 
# #Dusky Rockfish Complete/combined
# temp.3 <- load_RACE_data(species.codes=c(30150,30152), combineSpecies=TRUE, survey='GOA', writeCSV=FALSE, writeDATA=FALSE)
# dim(temp.3)
# 
# #Checkup
# unique(sort(unique(temp.3$HAULJOIN))==sort(unique(temp$HAULJOIN)))
# unique(sort(unique(temp.2$HAULJOIN))==sort(unique(temp$HAULJOIN)))
# 
# 
# #Checkup figure
# require(ggplot2)
# require(ggthemes)
# 
# temp.4 <- load_RACE_data(species.codes=c(30150,30152), combineSpecies=FALSE, survey='GOA', writeCSV=FALSE, writeDATA=FALSE)
# dim(temp.4)
# 
# 
# 
# g <- ggplot(temp.3, aes(x=Year, y=WEIGHT)) +
#        stat_summary(fun.y=sum, geom='area', fill='blue') +
#        stat_summary(fun.y=sum, geom='point', color='red')
# 
# g
# 
# g2 <- ggplot(temp.4, aes(x=Year, y=WEIGHT, fill=SPECIES_CODE)) +
#         stat_summary(fun.y=sum, geom='area') +
#         stat_summary(fun.y=sum, geom='point', color='black') +
#         facet_wrap(~SPECIES_CODE, ncol=1)
# 
# g2
# 
# g3 <- ggplot(temp.4, aes(x=Year, y=WEIGHT, fill=SPECIES_CODE)) +
#         stat_summary(fun.y=sum, geom='area', aes(group=SPECIES_CODE), alpha=0.5) +
#         stat_summary(fun.y=sum, geom='point', color='black') +
#         scale_fill_colorblind()
# 
# g3






#' Function to load RACE bottom trawl data. 
#' Catch and haul data are merged with species names, and zeros are added for missing no-catch observations.
#'
#' @param species.codes vector of species codes for which data will be returned
#' @param survey string indicating the survey for which data are being extracted: GOA, AI, EBS_SHELF, EBS_SLOPE
#' @param writeCSV boolean indicating whether "output/RACE_data_output.csv" is created
#' @param writeDATA boolean indicating whether "output/RACE_data_output.RData" is created
#' @param combineSpecies boolean indicating whether species codes should be combined into a single index (i.e. Dusky Rockfish)
#'
#' @return A data frame of RACE bottom trawl data, with rows equal to species-by-haul observations
#' @export
load_RACE_data <- function(species.codes=c(30150,30152), combineSpecies=FALSE, survey="GOA", writeCSV=FALSE, writeDATA=FALSE) {
  require(FishData)
  require(dplyr)
  ###TESTING###
  # species.codes <- c(30150,30152) #Pacific Cod 30420#
  # combineSpecies <- TRUE
  # survey <- 'GOA'
  # writeCSV <- FALSE
  # writeDATA <- FALSE
  #############
  
  if(survey %in% c("GOA","AI","EBS_SHELF",'EBS_SLOPE')) { 
    if(survey=="GOA") { Region <- "Gulf_of_Alaska"; area <- "GOA" }
    if(survey=="AI") { Region <- "Aleutian_Islands"; area <- "AI" }
    if(survey=="EBS_SHELF" | survey=="EBS_SLOPE") { Region <- "Eastern_Bering_Sea"; area <- "BS" }
    
  }else {
    stop(paste("survey is:",survey,", should be one of: GOA, AI, EBS_SHELF, EBS_SLOPE"))
  }
  #FOR ORIGINAL .CSV INPUT
  # #Opening section to determine suitability
  # if(file.exists("data/race_base_haul.csv")==FALSE) { stop("data/race_base_haul.csv NOT FOUND") }
  # if(file.exists("data/race_base_catch.csv")==FALSE) { stop("data/race_base_catch.csv NOT FOUND") }
  # if(file.exists("data/race_species_codes.csv")==FALSE) { stop("data/race_species_codes.csv NOT FOUND") }
  # 
  # #Load catch and haul data - FRIST TIME THEN SAVE AS .rds
  # haul <- read.csv("data/race_base_haul.csv", header=TRUE, stringsAsFactors=FALSE)
  # catch <- read.csv("data/race_base_catch.csv", header=TRUE, stringsAsFactors=FALSE)[,-1]
  # saveRDS(haul, file=file.path("data","race_base_haul.rds"))
  # saveRDS(catch, file=file.path("data","race_base_catch.rds"))
  
  #FOR NEW .RData INPUT
  #Opening section to determine suitability
  # if(file.exists("data/race_base_haul.RData")==FALSE) { stop("data/race_base_haul.RData NOT FOUND") }
  # if(file.exists("data/race_base_catch.RData")==FALSE) { stop("data/race_base_catch.RData NOT FOUND") }
  if(file.exists("data/race_base_haul.rds")==FALSE) { stop("data/race_base_haul.rds NOT FOUND") }
  if(file.exists("data/race_base_catch.rds")==FALSE) { stop("data/race_base_catch.rds NOT FOUND") }
  if(file.exists("data/race_species_codes.csv")==FALSE) { stop("data/race_species_codes.csv NOT FOUND") }
  if(file.exists("data/race_cruise_info.csv")==FALSE) { stop("data/race_cruise_info.csv NOT FOUND") }
  
  #Load catch and haul data
  # load("data/race_base_catch.RData")
  # load("data/race_base_haul.RData")
  catch <- readRDS("data/race_base_catch.rds")
  haul <- readRDS("data/race_base_haul.rds")
  #Limit to only certified abundance hauls
  haul <- haul[haul$ABUNDANCE_HAUL=='Y',]
  
  # Experimental Extraction of New Data from Pete Fall 2019 ==========================
  
  # Print Examples
  # write.csv(head(catch), file=file.path("data","RACE Fall 2019","Examples of Prior Files","race_base_catch_EXAMPLE.csv"))
  # write.csv(head(haul), file=file.path("data","RACE Fall 2019","Examples of Prior Files","race_base_haul_EXAMPLE.csv"))
  # write.csv(head(cruise.info), file=file.path("data","RACE Fall 2019","Examples of Prior Files","race_cruise_info_EXAMPLE.csv"))
  
  
  # test.haul <- read.csv(file.path("data","RACE Fall 2019","race_base_haul.csv"))
  # head(test.haul)
  # head(haul)
  # test.cruise
  # ==================================================================================
  
  #Merge data
  # catchhaul <- merge(x=catch, y=haul, all.y=TRUE, all.x=FALSE, by.x="HAULJOIN", by.y="HAULJOIN")
  #dplyr it for greater speed
  catchhaul <- right_join(x=catch, y=haul, by=c("HAULJOIN"))
  
  #Add in zero observations for catch weight, for no catches.
  #  Drawing on Jim Thorson's code from FishData
  #NOTE: species.codes are now treated as a factor.
  catchhaul.2 <- FishData::add_missing_zeros(data_frame=catchhaul, unique_sample_ID_colname="HAULJOIN",
                                               sample_colname="WEIGHT", species_colname="SPECIES_CODE",
                                               species_subset=species.codes,
                                               if_multiple_records="First",
                                                Method="Fast")
  
  #Bring in cruise info
  #Add year of survey and name
  cruise.info <- read.csv("data/race_cruise_info.csv", header=TRUE, stringsAsFactors=FALSE)
  # catchhaul.3 <- left_join(x=catchhaul.2, y=cruise.info[,c("Cruise.Join.ID","Year","Survey")],
  #                            by=c("CRUISEJOIN.x"="Cruise.Join.ID"))
  
  #User inner_join because it removes hauls WITHOUT identified Year and Survey
  catchhaul.3 <- inner_join(x=catchhaul.2, y=cruise.info[,c("Cruise.Join.ID","Year","Survey")],
                           by=c("CRUISEJOIN.x"="Cruise.Join.ID"))

  # #Find difference
  # loc <- which(catchhaul.3$CRUISEJOIN.x %in% catchhaul.3.in$CRUISEJOIN.x)
  # catchhaul.3[-loc,]

  #Limit to specific survey
  catchhaul.3 <- catchhaul.3[catchhaul.3$Survey==survey,]
  # catchhaul.3 <- catchhaul.3[catchhaul.3$Survey==survey & catchhaul.3$REGION.x==area,]
  
  #=================================================
  #AGGREGATE CATCH BIOMASS ACROSS SPECIES CODES IN THE CASE OF A COMBINED INDEX
  #  Dusky Rockfish Example 2 species codes to single index
  #   Variables to combine: WEIGHT
  if(combineSpecies==TRUE) {
    catchhaul.4 <- data.frame(catchhaul.3 %>% group_by(HAULJOIN) %>% 
                                mutate('WEIGHT'=sum(WEIGHT, na.rm=TRUE)))
    #Since we have aggregated, only retain rows for 1st listed species code
    catchhaul.5 <- catchhaul.4[catchhaul.4$SPECIES_CODE==species.codes[1],]
  }else {
    catchhaul.5 <- catchhaul.3
  }
  #=================================================
  
  
  #Calculate and add Effort and CPUE
  catchhaul.5$effort <- catchhaul.5$NET_WIDTH*catchhaul.5$DISTANCE_FISHED/1000
  catchhaul.5$cpue <- catchhaul.5$WEIGHT/catchhaul.5$effort
  
  #Add species name
  species.code.data <- read.csv("data/race_species_codes.csv", header=TRUE, stringsAsFactors=FALSE)
  
  #Convert codes to a factor
  # species.code.data$Species.Code <- as.factor(species.code.data$Species.Code)
  # 
  # output <- left_join(x=catchhaul.3, y=species.code.data[,c("Species.Code","Common.Name")],
  #                      by=c("SPECIES_CODE"="Species.Code"))
  output <- merge(x=catchhaul.5, y=species.code.data[,c("Species.Code","Common.Name")], 
                       by.x="SPECIES_CODE", by.y="Species.Code")
  
  #Return Section
  if(writeCSV==TRUE) { write.csv(output, file="output/RACE_data_output.csv") }
  if(writeDATA==TRUE) { save(output, file="output/RACE_data_output.RData") }
  
  return(output)
}


##### TESTING FUNCTION #####
# #Northern Rockfish
# temp <- load_RACE_data(species.codes=c(30420), combineSpecies=FALSE, survey='GOA', writeCSV=FALSE, writeDATA=FALSE)
# dim(temp)
# 
# #Dusky Rockfish partial
# temp.2 <- load_RACE_data(species.codes=c(30150), combineSpecies=FALSE, survey='GOA', writeCSV=FALSE, writeDATA=FALSE) 
# dim(temp.2)
# 
# 
# #Dusky Rockfish Complete/combined
# temp.3 <- load_RACE_data(species.codes=c(30150,30152), combineSpecies=TRUE, survey='GOA', writeCSV=FALSE, writeDATA=FALSE)
# dim(temp.3)
# 
# #Checkup
# unique(sort(unique(temp.3$HAULJOIN))==sort(unique(temp$HAULJOIN)))
# unique(sort(unique(temp.2$HAULJOIN))==sort(unique(temp$HAULJOIN)))
# 
# 
# #Checkup figure
# require(ggplot2)
# require(ggthemes)
# 
# temp.4 <- load_RACE_data(species.codes=c(30150,30152), combineSpecies=FALSE, survey='GOA', writeCSV=FALSE, writeDATA=FALSE)
# dim(temp.4)
# 
# 
# 
# g <- ggplot(temp.3, aes(x=Year, y=WEIGHT)) +
#        stat_summary(fun.y=sum, geom='area', fill='blue') +
#        stat_summary(fun.y=sum, geom='point', color='red')
# 
# g
# 
# g2 <- ggplot(temp.4, aes(x=Year, y=WEIGHT, fill=SPECIES_CODE)) +
#         stat_summary(fun.y=sum, geom='area') +
#         stat_summary(fun.y=sum, geom='point', color='black') +
#         facet_wrap(~SPECIES_CODE, ncol=1)
# 
# g2
# 
# g3 <- ggplot(temp.4, aes(x=Year, y=WEIGHT, fill=SPECIES_CODE)) +
#         stat_summary(fun.y=sum, geom='area', aes(group=SPECIES_CODE), alpha=0.5) +
#         stat_summary(fun.y=sum, geom='point', color='black') +
#         scale_fill_colorblind()
# 
# g3






#' Wrapper function generate VAST diagnostic and prediction plots
#'
#' @param Opt list output from Template Model Builder (TMB) fitting of VAST model
#' @param Report list of TMB specifications for VAST model
#' @param DateFile path for directory housing VAST model and output figures and objects
#' @param survey string indicating the survey for which data are being extracted: GOA, AI, EBS_SHELF, EBS_SLOPE
#' @param TmbData list of TMB inputs for VAST model
#' @param Data_Geostat dataframe containing RACE bottom trawl survey data
#' @param Extrapolation_List list object containing definition of for which density extrapolation is conducted when calculating indices
#' @param Spatial_List tagged list with spatial information needed for Data_Fn
#'
#' @return A series of output figures exploring model diagnostics, density predictions, and indices.
#' @export
plot_VAST_output <- function(Opt, Report, DateFile, survey, TmbData, Data_Geostat, Extrapolation_List, Spatial_List) {
  
  #Determine Correct area to allign with RACE survey
  if(survey %in% c("GOA","AI","EBS_SHELF",'EBS_SLOPE')) { 
    if(survey=="GOA") { Region <- "Gulf_of_Alaska"; area <- "GOA" }
    if(survey=="AI") { Region <- "Aleutian_Islands"; area <- "AI" }
    if(survey=="EBS_SHELF" | survey=="EBS_SLOPE") { Region <- "Eastern_Bering_Sea"; area <- "BS" }
    
  }else {
    stop(paste("survey is:",survey,", should be one of: GOA, AI, EBS_SHELF, EBS_SLOPE"))
  }
  
  #========================================================================
  ##### DIAGNOSTIC PLOTS #####
  
  #Plot spatial distribution of data
  SpatialDeltaGLMM::Plot_data_and_knots(Extrapolation_List = Extrapolation_List,
                                        Spatial_List = Spatial_List, Data_Geostat = Data_Geostat,
                                        PlotDir = DateFile)
  
  #Diagnostics for Encounter Probability
  #  "Diag--Encounter_prob"
  Enc_prob = SpatialDeltaGLMM::Check_encounter_prob(Report = Report,
                                                    Data_Geostat = Data_Geostat,
                                                    DirName = DateFile)
  
  #Diagnostics for positive-catch-rate component
  Q = SpatialDeltaGLMM::QQ_Fn(TmbData = TmbData, Report = Report,
                              FileName_PP = paste0(DateFile, "Posterior_Predictive.jpg"),
                              FileName_Phist = paste0(DateFile, "Posterior_Predictive-Histogram.jpg"),
                              FileName_QQ = paste0(DateFile, "Q-Q_plot.jpg"),
                              FileName_Qhist = paste0(DateFile, "Q-Q_hist.jpg"))
  
  
  #Diagnostics for plotting residuals on a map
  
  
  MapDetails_List = SpatialDeltaGLMM::MapDetails_Fn( "Region"=Region,
                                                     "NN_Extrap"=Spatial_List$PolygonList$NN_Extrap,
                                                     "Extrapolation_List"=Extrapolation_List )
  
  #Which Years to Include
  Year_Set = seq(min(Data_Geostat[,'Year']),max(Data_Geostat[,'Year']))
  Years2Include = which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))
  
  #Or just include years with observations
  # Year_Set = y
  # Years2Include = which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))
  
  #Plot Pearson Residuals
  #  Look for spatial patterns-- indication of "overshrinking"
  #  Creates "maps--" files
  SpatialDeltaGLMM::plot_residuals(Lat_i = Data_Geostat[,"Lat"], Lon_i = Data_Geostat[, "Lon"], TmbData = TmbData,
                                    Report = Report, Q = Q, savedir = DateFile, MappingDetails = MapDetails_List[["MappingDetails"]],
                                    PlotDF = MapDetails_List[["PlotDF"]], MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
                                    Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
                                    FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
                                    Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
                                    Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
                                    mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8)
  
  
  
  
  #========================================================================
  ##### MODEL OUTPUT PLOTS #####
  
  #Direction of "geometric anisotropy"
  SpatialDeltaGLMM::PlotAniso_Fn(FileName = paste0(DateFile,"Aniso.png"),
                                 Report = Report, TmbData = TmbData)
  
  #Density Surface for Each Year -- "Dens"
  SpatialDeltaGLMM::PlotResultsOnMap_Fn(plot_set = c(3),
                                        MappingDetails = MapDetails_List[["MappingDetails"]],
                                        Report = Report, Sdreport = Opt$SD, PlotDF = MapDetails_List[["PlotDF"]],
                                        MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
                                        Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
                                        FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
                                        Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
                                        Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
                                        mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8,
                                        plot_legend_fig = TRUE)
  
  
  
  #Generate Index of Abundance
  
  Index = SpatialDeltaGLMM::PlotIndex_Fn(DirName = DateFile,
                                         TmbData = TmbData, Sdreport = Opt[["SD"]],
                                         Year_Set = Year_Set,
                                         Years2Include = Years2Include, 
                                         use_biascorr = TRUE)
  
  # idx <- Index$Table
  # 
  # 
  # #Plotting 
  # x.lim <- c(min(yrs.surv), max(yrs.surv))
  # up.sd <- idx$Estimate_metric_tons + idx$SD_mt
  # low.sd <- idx$Estimate_metric_tons - idx$SD_mt
  # y.lim <- c(min(low.sd), max(up.sd))
  # 
  # loc.yrs <- which(idx$Year %in% yrs.surv)
  # 
  # 
  # plot(x=NULL, y=NULL, xlim=x.lim, ylim=y.lim, ylab='Survey Estimate (metric Tons)', xlab='Year',
  #      main='Gulf of Alaska\nNorthern Rockfish Survey Index')
  # 
  # polygon(x=c(yrs.surv, rev(yrs.surv)), y=c(low.sd[loc.yrs],rev(up.sd[loc.yrs])), col='lightgray', border=FALSE)
  # lines(x=yrs.surv, y=idx$Estimate_metric_tons[loc.yrs], col='red')
  # points(x=yrs.surv, y=idx$Estimate_metric_tons[loc.yrs], pch=21, bg='red')
  # grid(col='black')
  
  
  #Center of gravity and range expansion/contraction
  #  For some reason I can't actually change the years to plot 
  SpatialDeltaGLMM::Plot_range_shifts(Report = Report,
                                      TmbData = TmbData, Sdreport = Opt[["SD"]], Znames = colnames(TmbData$Z_xm),
                                      PlotDir = DateFile, Year_Set = Year_Set)
  
  
}


run_RE_model <- function(input.yrs, input.idx, input.cv, DateFile, home.dir, n_PE=1, PE_vec=c(1,1,1)) {
  require(R2admb)
  require(PBSmodelling)
  ### TESTING ###
  # n_PE <- 3#1
  # PE_vec <- 1:3#c(1,1,1)
  ###############
  
  setwd(DateFile)
  
  ### CREATE ADMB INPUT FILE ###
  styr <- min(input.yrs)  #Start year
  endyr <- max(input.yrs)  #End year
  num_indx <- ncol(input.idx) #Number of survey indices
  # n_PE <- 1 #3  #Number of process error parameters
  nobs <- length(input.yrs)  #Number of surveys
  yrs_srv <- input.yrs  #Survey years
  srv_est <- input.idx  #Survey Biomass
  srv_cv <- input.cv  #Survey Biomass CV

  write_dat(paste0(DateFile,'/re'), L=list(styr, endyr, num_indx, n_PE, PE_vec,
                               nobs, yrs_srv,
                               srv_est, srv_cv))
  
  ### COPY IN .tpl ###
  file.copy(from=paste0(home.dir,"/admb/re.tpl"), to=paste0(DateFile,"re.tpl"), overwrite=TRUE)
  
  ### COMPILE ADMB-RE MODEL ###
  compile_admb(fn="re", re=TRUE, verbose=TRUE)
  
  ### ADMB RANDOM EFFECTS MODEL ###
  run_admb(fn="re", verbose=FALSE)
  
  ### READ MODEL OUTPUTS ###
  biomA <- readList(paste0(DateFile,"/biomA.out"))$biomA
  
  return(biomA)
}

## Compute GOA survey biomass estimates from scratch

##### Generalize
## Compute GOA survey biomass estimates from scratch
#### Prep the data ##############
HAUL<-read.csv("HAUL.csv",header=T)
CATCH<-read.csv("CATCH.csv",header=T)
STRATUM<-read.csv("STRATUM2.csv",header=T)
BS<-read.csv("BIENNIAL_SURVEYS.csv",header=T)
BSGOA<-BS[BS$SURVEY_NAME=="GOA BIENNIAL SURVEY"|BS$SURVEY_NAME=="GOA TRIENNIAL SURVEY"|BS$SURVEY_NAME=="2009 Gulf of Alaska Bottom Trawl Survey"|BS$SURVEY_NAME=="2013 Gulf of Alaska Bottom Trawl Survey",]
CPUE<-read.csv("CPUE.csv",header=T)
catch<-CATCH[CATCH$CRUISEJOIN%in%BSGOA$CRUISEJOIN,]
haul<-HAUL[HAUL$CRUISEJOIN%in%BSGOA$CRUISEJOIN,]
cpue<-CPUE[CPUE$HAULJOIN%in%haul$HAULJOIN,]
catch$YEAR<-floor(catch$CRUISE/100)
haul$YEAR<-floor(haul$CRUISE/100)
dim(CATCH)
dim(catch)
dim(CPUE)
dim(cpue)
dim(HAUL)
dim(haul)
haul<-haul[!is.na(haul$STRATUM),]
haul<-haul[haul$PERFORMANCE>-0.01,]
haul<-haul[haul$HAUL_TYPE==3,]
haul<-haul[!is.na(haul$NET_WIDTH),]
# get rid of some more stuff, groundfish only
catch<-catch[catch$SPECIES_CODE>20000,]
catch<-catch[catch$SPECIES_CODE<40000,]
# calculate CPUE per catch records
goastrat<-STRATUM
#year<-c(2013) # Choose number of survey years to estimate from
year<-as.numeric(names(table(haul$YEAR)))
species<-c(30052,30051) #choose species group to choose from
species<-c(30060)
# Orock:
#species<-c(30100,30120,30170,30190,30200,30220,30240,30260,30270,30320,30340,30350,30370,30380,30400,30410,30420,30430,30470,30475,30490,30535,30550,30560,30600)
#species<-c(30576) #shortraker is 30576
### Get haul data and catch data in one object
catch2<-merge(catch,haul,by=c("CRUISEJOIN","HAULJOIN"),all.x=TRUE,all.y=TRUE) # outer join
### Get rid of hauls with no stratum association
cpue<-catch2[!is.na(catch2$STRATUM),]
#### Get rid of "unstatisfactory hauls"
## some changes for orocks
#cpue$ew<-(cpue$STRATUM+100)/100-floor((cpue$STRATUM+100)/100)
#cpue<-cpue[-which(cpue$SPECIES_CODE==30420&cpue$ew<0.4,),]
#dsr<-c(30120,30270,30320,30340,30370,30380,30410,30470)
#cpue<-cpue[-which(cpue$SPECIES_CODE%in%dsr&cpue$ew>0.39),]
############
######
cpue<-cpue[cpue$PERFORMANCE>-0.01,]
#cpue<-cpue[cpue$GEAR!=174,] weird gear type in 1999
### Extract years
temp<-cpue[cpue$YEAR.y%in%year,]
### Get an object with all hauljoins, but no duplicates
temp2<-temp[!duplicated(temp$HAULJOIN),]
### Calculated combined catch for multiple species
cpue2<-cpue[!cpue$SPECIES_CODE%in%species,]
cpue<-cpue[cpue$SPECIES_CODE%in%species,]
cpue2$WEIGHT<-0
cpue2$SPECIES_CODE<-99999
cpue2<-cpue2[!duplicated(cpue2$HAULJOIN),]
cpue3<-rbind(cpue,cpue2)
cpue3$SPECIES_CODE<-"POP"

h2<-haul[!duplicated(haul$HAULJOIN),]

### sum up catch across species for each haul
sumcatch<-as.matrix(tapply(cpue$WEIGHT,cpue$HAULJOIN,sum)) 
### sloppy matrix work
x<-as.numeric(rownames(sumcatch))
sumcatch<-cbind(x,as.numeric(sumcatch[,1]))
colnames(sumcatch)<-c("HAULJOIN","sumcatch")
### name all the species codes to one...
cpue$SPECIES_CODE<-species[1]
#### add new column onto table with combined catch
cpue<-merge(cpue,sumcatch,by="HAULJOIN")
### calculate CPUE (kg/km^2)
cpue$cpue<-cpue$sumcatch/(cpue$DISTANCE_FISHED*cpue$NET_WIDTH/1000)
#### again getting rid of extra records
cpue<-cpue[!duplicated(cpue$HAULJOIN),]
cpue<-cpue[cpue$SPECIES_CODE%in%species[1],]
cpue<-cpue[!is.na(cpue$SPECIES_CODE),]
#pc2013<-pc[pc$YEAR.y%in%year,]
cpue<-cpue[cpue$YEAR.y%in%year,]
temp2$cpue<-NA
temp2[match(cpue$HAULJOIN,temp2$HAULJOIN),]$cpue<-cpue$cpue
temp2[is.na(temp2$cpue),]$cpue<-0
temp2[is.infinite(temp2$cpue),]$cpue<-0
####


cstrat<-tapply(temp2$cpue,temp2$STRATUM,sum,na.rm=T)
vstrat<-tapply(temp2$cpue,temp2$STRATUM,var)
h<-haul[haul$YEAR%in%year,]
hstrat<-tapply(h$HAUL,h$STRATUM,length)
### build stratified esitmates table

xx<-data.frame(cbind(rownames(cstrat),as.numeric(cstrat),as.numeric(vstrat)))
yy<-data.frame(cbind(rownames(hstrat),as.numeric(hstrat)))
names(xx)<-c("STRATUM","SUMC","var")
names(yy)<-c("STRATUM","n")
zz<-merge(yy,xx,all.x=TRUE,all.y=TRUE)
zzz<-merge(zz,goastrat,all.x=TRUE)
zzz$bio<-as.numeric(as.character(zzz[,3]))/as.numeric(as.character(zzz[,2]))*as.numeric(as.character(zzz$AREA))
zzz$var2<-as.numeric(as.character(zzz$AREA))^2*(as.numeric(as.character(zzz[,4]))/as.numeric(as.character(zzz[,2])))

### Output results
print("Biomass")
sum(zzz$bio,na.rm=T)/1000
print("Variance")
sum(zzz$var2,na.rm=T)/1000000
print("SD")
sqrt(sum(zzz$var2,na.rm=T))/1000
print("CV")
#cv
sqrt(sum(zzz$var2,na.rm=T))/1000/(sum(zzz$bio,na.rm=T)/1000)

#write.csv(zzz,"c://sa//orox_strata.csv")

####Cindy's addition
library(plyr)
#for whole GOA
cstrat<-ddply(temp2,c("YEAR.x","STRATUM"),summarize,CPUE=sum(cpue),CPUEvar=var(cpue))
hstrat<-ddply(haul,c("YEAR","STRATUM"),summarize,n_sta=length(unique(HAULJOIN)))
biomvar<-merge(cstrat,hstrat,by.x=c("YEAR.x","STRATUM"),by.y=c("YEAR","STRATUM"),all.x=T)
colnames(biomvar)<-c("YEAR","STRATUM","CPUE","VAR","n_stations")
stratarea<-STRATUM[,c("STRATUM","AREA","INPFC_AREA")]
biomvar<-merge(biomvar,stratarea,by=c("STRATUM"),all.x=T)
biomvar$BIOMASS<-(biomvar$CPUE/biomvar$n_stations)*biomvar$AREA
biomvar$VAR2<-biomvar$AREA^2*(biomvar$VAR/biomvar$n_stations)
BiomassGOA<-ddply(biomvar,c("YEAR"),summarize,Biomass=sum(BIOMASS,na.rm=T)/1000,Variance=sum(VAR2,na.rm=T)/1000000,
                  SD=sqrt(sum(VAR2,na.rm=T))/1000,CV=SD/(sum(BIOMASS,na.rm=T)/1000))

cstrat<-ddply(temp4,c("YEAR.x","STRATUM"),summarize,CPUE=sum(cpue),CPUEvar=var(cpue))
hstrat<-ddply(haul,c("YEAR","STRATUM"),summarize,n_sta=length(unique(HAULJOIN)))
biomvar<-merge(cstrat,hstrat,by.x=c("YEAR.x","STRATUM"),by.y=c("YEAR","STRATUM"),all.x=T)
colnames(biomvar)<-c("YEAR","STRATUM","CPUE","VAR","n_stations")
stratarea<-STRATUM[,c("STRATUM","AREA","INPFC_AREA")]
biomvar<-merge(biomvar,stratarea,by=c("STRATUM"),all.x=T)
biomvar$BIOMASS<-(biomvar$CPUE/biomvar$n_stations)*biomvar$AREA
biomvar$VAR2<-biomvar$AREA^2*(biomvar$VAR/biomvar$n_stations)
BiomassGOA<-ddply(biomvar,c("YEAR"),summarize,Biomass=sum(BIOMASS,na.rm=T)/1000,Variance=sum(VAR2,na.rm=T)/1000000,
                  SD=sqrt(sum(VAR2,na.rm=T))/1000,CV=SD/(sum(BIOMASS,na.rm=T)/1000))



library(ggmap)
library(plyr)
library(ggplot2)
library(reshape2)
library(sp)
library(grid)
#library(geoshape)
testmap <- get_googlemap(c(lon=-155,lat=58) ,maptype='satellite',zoom=4, xlim=c(-135,-170), ylim=c(51,63)) # set up mapframe

species<-30060
species_name<-"POP"
c2<-catch2
y2<-(unique(c2$YEAR.x))
y2<-sort(y2[!is.na(y2)])
c2<-c2[c2$SPECIES_CODE==species,]
for(i in 1:length(y2)){
  c3<-c2[c2$YEAR.x==y2[i],]
  c3<-c3[!is.na(c3$YEAR.x),]
  c3<-c3[!is.na(c3$START_LONGITUDE),]
  
  ggmap(testmap,legend="topleft") + geom_point(data=c3,aes(x= START_LONGITUDE, y=START_LATITUDE,size=log(WEIGHT)),alpha=0.62,colour="red")+
    xlab("Longitude") +ylab("Latitude") +
    ggtitle(paste("Gulf of Alaska Survey Biomass of",species_name))+
    geom_text(mapping=aes(label = c(y2[i]), x = -140, y = 51.5),colour="white",size=10) +
    theme_grey(base_size=20) 
   ggsave(paste("GOABiomass_",y2[i],".png",sep=""), dpi=150, width=10, height=12)
}



#==================================================================================================
#Project Name: VAST spatial delta-GLMM (Thorson) Evaluation: Testing of Alternatives for Reducing RAM Load for Estimation with Epsilon-estimator for Bias Correction and Large (>300) Knots
#Creator: Curry James Cunningham, NOAA/NMFS, ABL
#Date: 3.1.2018
#
#Purpose: Example implementation of VAST model for GOA Pollock
#  bias.corr.option - controls which method is used.
#     Alternatives:  1 = Orignal implementation (error @ 300kt + )
#                    2 = Use nsplit to break up epsilon-estimator bias correction processes - slow
#                    3 = Manually estimte without bias correction then re-estimate the bias corrected index - faster
#                    4 = Use NEW option in TMBhelper::Optimize() to specify which variables are bias corrected.
#
#==================================================================================================
#NOTES:
#Memory ProfilingJust


# OPTION #2: Using nsplit=200
# [1] "bias.correct: TRUE"
# [1] "n_x: 1000"
# [1] "START: Mon Mar 05 10:18:23 2018"
# [1] "END: Mon Mar 05 17:28:07 2018"

# OPTION #3: Using Jim's new method - Restimate of index with bias corr. 
# [1] "bias.correct: TRUE"
# [1] "n_x: 1000"
# [1] "START: Tue Mar 06 10:02:39 2018"
# [1] "END: Tue Mar 06 11:24:57 2018"

# OPTION #4: New TMBHelper implementation ~1.5 hours = not converged, good gradient
# [1] "bias.correct: TRUE"
# [1] "n_x: 1000"
# [1] "nsplit: 100"
# [1] "START: Thu May 03 10:22:45 2018"
# [1] "END: Thu May 03 11:44:15 2018"
# 

#BETTER - NO ERRORS 1.5 HRS
# [1] "bias.correct: TRUE"
# [1] "n_x: 1000"
# [1] "nsplit: 200"
# [1] "START: Fri May 04 08:30:25 2018"
# [1] "END: Fri May 04 09:53:00 2018"




# Opt = TMBhelper::Optimize( obj=Obj, lower=TmbList[["Lower"]], upper=TmbList[["Upper"]], getsd=TRUE, 
#                            savedir=DateFile, bias.correct=bias.correct, newtonsteps=1,
#                            bias.correct.control=list(sd=FALSE, nsplit=nsplit, split="Index_cyl") )

#   Requires development version of TMB: devtools::install_github("kaskr/adcomp/TMB")
#     Checkup

# [1] "bias.correct: TRUE"
# [1] "n_x: 1000"
# [1] "nsplit: 100"
# [1] "START: Wed May 02 14:20:00 2018" ~ 15
# [1] "END: Thu May 03 05:19:04 2018"

# [1] "bias.correct: TRUE"
# [1] "n_x: 1000"
# [1] "nsplit: 200"
# [1] "START: Mon Apr 30 09:04:17 2018" ~ 9 hours
# [1] "END: Mon Apr 30 16:16:39 2018"

#ATTEMPT WITH nsplit=10,50,75
# Error in sparseHessianFun(env, skipFixedEffects = skipFixedEffects) : 
#   Memory allocation fail in function 'MakeADHessObject2' 

#==================================================================================================
 source("R/create-VAST-input.r")
 source("R/create-Data-Geostat.r")
 source("R/load-RACE-data.r")
 source("R/plot-VAST-output.r")
 source("R/cleanup-VAST-file.r")
 source("R/run-RE-model.r") 

require(VAST)
require(TMB)
require(tidyverse)

#=======================================================================
##### SETUP INPUT DATA #####

#Generate a dataset
species.codes <- 21740 #30420#
lat_lon.def <- "start"

survey <- "GOA"

bias.correct <- TRUE

#SPATIAL SETTINGS
Method <- c("Grid", "Mesh", "Spherical_mesh")[2]
grid_size_km <- 25
n_x <- c(100, 250, 500, 1000, 2000)[4] # Number of stations
Kmeans_Config <- list( "randomseed"=1, "nstart"=100, "iter.max"=1e3 )


#SET SRATIFICATOIN
#Basic - Single Area
strata.limits <- data.frame(STRATA = c("All_areas"))


#DERIVED OBJECTS
Version <-  "VAST_v4_0_0"

#SPECIFY METHOD FOR DEALING WITH BIAS.CORRECTION
bias.corr.option <- 4

###########################
trial.file <- paste0(getwd(),"/tests/Test_bias.correct_Efficency")

#MODEL SETTINGS
FieldConfig = c(Omega1 = 1, Epsilon1 = 1, Omega2 = 1, Epsilon2 = 1)
RhoConfig = c(Beta1 = 0, Beta2 = 0, Epsilon1 = 0, Epsilon2 = 0)
OverdispersionConfig = c(Delta1 = 0, Delta2 = 0)

ObsModel = c(1, 0) #Lognormal
# ObsModel = c(2, 0) #Gamma
# ObsModel = c(1, 1) #Poisson-Process Link function approximating Tweedie distribution

#SPECIFY OUTPUTS
Options = c(SD_site_density = 0, SD_site_logdensity = 0,
            Calculate_Range = 1, Calculate_evenness = 0, Calculate_effective_area = 1,
            Calculate_Cov_SE = 0, Calculate_Synchrony = 0,
            Calculate_Coherence = 0)


DateFile <- paste0(trial.file,"/Option_",bias.corr.option,"/GOA Pollock knots_",n_x," bias.correct_", bias.correct, " Rho_",RhoConfig[1],RhoConfig[2],RhoConfig[3],RhoConfig[4],"/")


#=======================================================================
##### READ IN DATA AND BUILD vAST INPUT #####
VAST_input <- create_VAST_input(species.codes=species.codes, combineSpecies=FALSE,
                                lat_lon.def=lat_lon.def, save.Record=FALSE,
                                Method=Method, grid_size_km=grid_size_km, n_x=n_x,
                                Kmeans_Config=Kmeans_Config,
                                strata.limits=strata.limits, survey=survey,
                                DateFile=DateFile,
                                FieldConfig=FieldConfig, RhoConfig=RhoConfig,
                                OverdispersionConfig=OverdispersionConfig,
                                ObsModel=ObsModel, Options=Options, Version=Version)
#Unpack
TmbData <- VAST_input$TmbData
Data_Geostat <- VAST_input$Data_Geostat
Spatial_List <- VAST_input$Spatial_List
Extrapolation_List <- VAST_input$Extrapolation_List #Becomes zeros for non-GOA
# head(Extrapolation_List$a_el)
# head(Extrapolation_List$Area_km2_x)
# head(Extrapolation_List$Data_Extrap)

#=======================================================================
##### RUN VAST #####



#Build TMB Object
#  Compilation may take some time
TmbList <- VAST::Build_TMB_Fn(TmbData = TmbData, RunDir = DateFile,
                                Version = Version, RhoConfig = RhoConfig, loc_x = Spatial_List$loc_x,
                                Method = Method)
Obj <- TmbList[["Obj"]]

start.time <- date()

#================================================
#TESTING OPTIMIZATION 1: Original Call
if(bias.corr.option==1 | bias.correct==FALSE) {
  Opt <- NULL
  Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
                          upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
                          bias.correct = bias.correct)
}

#================================================
#TESTING OPTIMIZATION 2: Updated call with nsplit to reduce memory load and 
#                        allow running bias.cor with kt > ~300
if(bias.corr.option==2) {
  nsplit <- 200
  Opt <- NULL
  Opt <- TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
                             upper = TmbList[["Upper"]], getsd = TRUE, savedir = DateFile,
                             bias.correct = bias.correct,
                             bias.correct.control=list(nsplit=nsplit, split=NULL, sd=FALSE))
}
#================================================
#TESTING OPTIMIZATION 3: New Alternative Following Jim's Suggestion
#  Should limit bias correction to single vector of interst: index
#  Conducts estimation without bias correction then re-estimates bias corrected index parameter.
if(bias.corr.option==3) {
  nsplit <- 200
  Opt <- NULL
  
  Opt = TMBhelper::Optimize(obj = Obj, lower = TmbList[["Lower"]],
                            upper = TmbList[["Upper"]], getsd = TRUE,
                            savedir = DateFile, bias.correct=FALSE )

  # First SD run
  h <- optimHess(Opt$par, Obj$fn, Obj$gr)
  SD = sdreport( obj=Obj, par.fixed=Opt$par, hessian.fixed=h )

  # Determine indices
  BiasCorrNames = c("Index_cyl")
  Which = which( rownames(summary(SD,"report")) %in% BiasCorrNames )
  Which = split( Which, cut(seq_along(Which), nsplit) )
  Which = Which[sapply(Which,FUN=length)>0]


  # Repeat SD with indexing
  SD = sdreport( obj=Obj, par.fixed=Opt$par, hessian.fixed=h, bias.correct=TRUE, 
                 bias.correct.control=list(sd=FALSE, split=Which, nsplit=NULL) )
  
}
#================================================
#TESTING OPTIMIZATION 4: Updated TMB Implementation Where Transformed Variables Are Specified
if(bias.corr.option==4) {
  nsplit <- 200
  Opt <- NULL
  
  Opt = TMBhelper::Optimize( obj=Obj, lower=TmbList[["Lower"]], upper=TmbList[["Upper"]], getsd=TRUE, 
                             savedir=DateFile, bias.correct=bias.correct, newtonsteps=1,
                             bias.correct.control=list(sd=FALSE, nsplit=nsplit, split=NULL, vars_to_correct="Index_cyl") )
  

  # Opt = TMBhelper::Optimize( obj=Obj, lower=TmbList[["Lower"]], upper=TmbList[["Upper"]], getsd=TRUE, 
  #                            savedir=DateFile, bias.correct=bias.correct, newtonsteps=1,
  #                            bias.correct.control=list(sd=FALSE, nsplit=nsplit, split="Index_cyl") )
                             # bias.correct.control=list(vars_to_correct="Index_cyl") )
                             # bias.correct.control=list(nsplit=nsplit, vars_to_correct="Index_cyl") )  FAIL:  Error in seq.int(rx[1L], rx[2L], length.out = nb) : 'from' must be finite
  # bias.correct.control=list(split=NULL, sd=FALSE, nsplit=nsplit, vars_to_correct="Index_cyl") ) #FAIL even with low knot number
  
  # bias.correct.control=list(sd=FALSE, vars_to_correct="Index_cyl") )
  
   
}
#================================================

head(summary(Opt$SD))
unique(summary(Opt$SD)[,'Est. (bias.correct)'])


end.time <- date()
#Save output
Report = Obj$report()
Save = list("Opt"=Opt, "Report"=Report, "ParHat"=Obj$env$parList(Opt$par), "TmbData"=TmbData)
save(Save, file=paste0(DateFile,"Save.RData"))

#========================================================================
##### DIAGNOSTIC AND PREDICTION PLOTS #####
# plot_VAST_output(Opt, Report, DateFile, survey, TmbData, Data_Geostat, Extrapolation_List, Spatial_List)

#========================================================================
##### CLEAN UP MODEL FILES #####
# cleanup_VAST_file(DateFile, Version=Version)

print(paste('bias.correct:',bias.correct))
print(paste('n_x:',n_x))
print(paste('nsplit:',nsplit))
print(paste('START:',start.time))
print(paste('END:',end.time))


#========================================================================
##### APPORTIONMENT #####














# 
# 
# #========================================================================
# ##### DIAGNOSTIC PLOTS #####
# 
# #Plot spatial distribution of data
# SpatialDeltaGLMM::Plot_data_and_knots(Extrapolation_List = Extrapolation_List,
#                                       Spatial_List = Spatial_List, Data_Geostat = Data_Geostat,
#                                       PlotDir = DateFile)
# 
# #Diagnostics for Encounter Probability
# #  "Diag--Encounter_prob"
# Enc_prob = SpatialDeltaGLMM::Check_encounter_prob(Report = Report,
#                                                   Data_Geostat = Data_Geostat,
#                                                   DirName = DateFile)
# 
# #Diagnostics for positive-catch-rate component
# Q = SpatialDeltaGLMM::QQ_Fn(TmbData = TmbData, Report = Report,
#                             FileName_PP = paste0(DateFile, "Posterior_Predictive.jpg"),
#                             FileName_Phist = paste0(DateFile, "Posterior_Predictive-Histogram.jpg"),
#                             FileName_QQ = paste0(DateFile, "Q-Q_plot.jpg"),
#                             FileName_Qhist = paste0(DateFile, "Q-Q_hist.jpg"))
# 
# 
# #Diagnostics for plotting residuals on a map
# 
# 
# MapDetails_List = SpatialDeltaGLMM::MapDetails_Fn( "Region"=Region,
#                                                    "NN_Extrap"=Spatial_List$PolygonList$NN_Extrap,
#                                                    "Extrapolation_List"=Extrapolation_List )
# 
# #Which Years to Include
# Year_Set = seq(min(Data_Geostat[,'Year']),max(Data_Geostat[,'Year']))
# Years2Include = which( Year_Set %in% sort(unique(Data_Geostat[,'Year'])))
# 
# #Or just include years with observations
# 
# #Plot Pearson Residuals
# #  Look for spatial patterns-- indication of "overshrinking"
# #  Creates "maps--" files
# SpatialDeltaGLMM:::plot_residuals(Lat_i = Data_Geostat[,"Lat"], Lon_i = Data_Geostat[, "Lon"], TmbData = TmbData,
#                                   Report = Report, Q = Q, savedir = DateFile, MappingDetails = MapDetails_List[["MappingDetails"]],
#                                   PlotDF = MapDetails_List[["PlotDF"]], MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
#                                   Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
#                                   FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
#                                   Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
#                                   Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
#                                   mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8)
# 
# 
# 
# 
# #========================================================================
# ##### MODEL OUTPUT PLOTS #####
# 
# #Direction of "geometric anisotropy"
# SpatialDeltaGLMM::PlotAniso_Fn(FileName = paste0(DateFile,"Aniso.png"),
#                                Report = Report, TmbData = TmbData)
# 
# #Density Surface for Each Year -- "Dens"
# SpatialDeltaGLMM::PlotResultsOnMap_Fn(plot_set = c(3),
#                                       MappingDetails = MapDetails_List[["MappingDetails"]],
#                                       Report = Report, Sdreport = Opt$SD, PlotDF = MapDetails_List[["PlotDF"]],
#                                       MapSizeRatio = MapDetails_List[["MapSizeRatio"]],
#                                       Xlim = MapDetails_List[["Xlim"]], Ylim = MapDetails_List[["Ylim"]],
#                                       FileName = DateFile, Year_Set = Year_Set, Years2Include = Years2Include,
#                                       Rotate = MapDetails_List[["Rotate"]], Cex = MapDetails_List[["Cex"]],
#                                       Legend = MapDetails_List[["Legend"]], zone = MapDetails_List[["Zone"]],
#                                       mar = c(0, 0, 2, 0), oma = c(3.5, 3.5, 0, 0), cex = 1.8,
#                                       plot_legend_fig = TRUE)
# 
# 
# 
# #Generate Index of Abundance
# 
# Index = SpatialDeltaGLMM::PlotIndex_Fn(DirName = DateFile,
#                                        TmbData = TmbData, Sdreport = Opt[["SD"]],
#                                        Year_Set = Year_Set,
#                                        Years2Include = Years2Include, 
#                                        use_biascorr = TRUE)
# 
# idx <- Index$Table
# 
# 
# dev.off()
# #Plotting 
# 
# yrs.surv <- Year_Set[Years2Include]
# x.lim <- c(min(yrs.surv), max(yrs.surv))
# up.sd <- idx$Estimate_metric_tons + idx$SD_mt
# low.sd <- idx$Estimate_metric_tons - idx$SD_mt
# y.lim <- c(min(low.sd), max(up.sd))
# 
# loc.yrs <- which(idx$Year %in% yrs.surv)
# 
# 
# plot(x=NULL, y=NULL, xlim=x.lim, ylim=y.lim, ylab='Survey Estimate (metric Tons)', xlab='Year',
#      main='Gulf of Alaska\nNorthern Rockfish Survey Index')
# 
# polygon(x=c(yrs.surv, rev(yrs.surv)), y=c(low.sd[loc.yrs],rev(up.sd[loc.yrs])), col='lightblue', border=FALSE)
# lines(x=yrs.surv, y=idx$Estimate_metric_tons[loc.yrs], col='red')
# points(x=yrs.surv, y=idx$Estimate_metric_tons[loc.yrs], pch=21, bg='red')
# grid(col='black')
# 
# 
# #Center of gravity and range expansion/contraction
# #  For some reason I can't actually change the years to plot 
# SpatialDeltaGLMM::Plot_range_shifts(Report = Report,
#                                     TmbData = TmbData, Sdreport = Opt[["SD"]], Znames = colnames(TmbData$Z_xm),
#                                     PlotDir = DateFile, Year_Set = Year_Set)