R/DBSRA.R

#' Depletion Based Stock Reduction Analysis
#'
#' @param CatchDat
#' @param DCAC.start.yr
#' @param DCAC.end.yr
#' @param delta.yr
#' @param DBSRA.OFL.yr
#' @param FMSYtoMRatio
#' @param SD.FMSYtoMratio
#' @param Delta
#' @param SD.Delta
#' @param DeltaLowerBound
#' @param DeltaUpperBound
#' @param BMSYtoB0ratio
#' @param SD.BMSYtoB0ratio
#' @param BMSYtoB0LowerBound
#' @param BMSYtoB0UpperBound
#' @param InterpCatch
#' @param Niter
#'
#' @return DBSRA outputs
#' @export
DBSRA<- function(CatchDat,DCAC.start.yr,DCAC.end.yr,delta.yr,DBSRA.OFL.yr,FMSYtoMRatio,SD.FMSYtoMratio,Delta,SD.Delta, DeltaLowerBound,DeltaUpperBound,BMSYtoB0ratio, SD.BMSYtoB0ratio,BMSYtoB0LowerBound, BMSYtoB0UpperBound ,InterpCatch, Niter)
{

  # DCAC and DB-SRA
  # CatchDat<- CatchData
  # DCAC.start.yr <- 1963 #start of the catch period
  # DCAC.end.yr<- 2011 #end of the catch period
  # delta.yr<- 2011 #Year that current depletion is fit to
  # DBSRA.OFL.yr<- 2011 #Year to calculate DBSRA OFL outputs
  # M.est<- .32 #Natural morality estiamte
  # SD.lnM<- .5 #natural mortality error estimate
  # FMSYtoMratio <- 0.8 #ratio of Fmsy to M
  # SD.FMSYtoMratio<- .05
  # Delta<- 0.6
  # SD.Delta<- 0.1
  # DeltaLowerBound<- 0.5
  # DeltaUpperBound<- 0.9
  # BMSYtoB0ratio<- 0.4
  # SD.BMSYtoB0ratio<- 0.1
  # BMSYtoB0LowerBound<- 0.2
  # BMSYtoB0UpperBound<- 0.5
  # InterpCatch<-1

  start.enchilada <- Sys.time()

  yr<- unique(CatchDat$Year)

  Output<- as.data.frame(matrix(NA,nrow=length(yr),ncol=9))

  colnames(Output)<- c('Year','Method','SampleSize','Value','LowerCI','UpperCI','SD','Metric','Flag')

  # MCOutput<- as.data.frame(matrix(NA,nrow=length(yr),ncol=7))

  # colnames(MCOutput)<- c('Iteration','Year','Method','SampleSize','Value','Metric','Flag')



  # number of CPUs available for optional parallel processing
  NumCPUs <- 1

  # turn on parallel processing if #CPUs > 1
  do.parallel <- ifelse(NumCPUs > 1, TRUE, FALSE)

  # toggle M correction term in production model
  M.correction <- TRUE

  # specify number of random draws for DCAC and delay-difference model
  # Niter <- 500

  ############################
  # LOAD DATA AND R PACKAGES #
  ############################

  # load snowfall package (also requires snow)
  require(snowfall)

  # get life history data

  parms.df<- as.data.frame(matrix(NA,nrow=1,ncol=24))
  colnames(parms.df)<- c('run','group','sci.name','common.name','species.code','exclude','age.mat','DCAC.start.yr','DCAC.end.yr','delta.yr','DBSRA.OFL.yr','M.est','SD.lnM','FMSYtoMratio','SD.FMSYtoMratio','Delta','SD.Delta','DeltaLowerBound','DeltaUpperBound','BMSYtoB0ratio','SD.BMSYtoB0ratio','BMSYtoB0LowerBound','BMSYtoB0UpperBound','random.seed')

  parms.df$run<- 1
  parms.df$group<- Fish$Group
  parms.df$sci.name<- Fish$SciName
  parms.df$common.name<- Fish$CommName
  parms.df$common.name<- Fish$CommName
  parms.df$species.code<- 'SpnyLob'
  parms.df$exclude<- 0
  parms.df$age.mat<- round(AgeAtLength(Fish$Mat50,Fish, Fish$AgeSD))
  parms.df$DCAC.start.yr<- DCAC.start.yr
  parms.df$DCAC.end.yr<- DCAC.end.yr
  parms.df$delta.yr<- delta.yr
  parms.df$DBSRA.OFL.yr <- DBSRA.OFL.yr
  parms.df$M.est <- Fish$M
  parms.df$SD.lnM <- Fish$MortalityError
  parms.df$FMSYtoMratio <- FMSYtoMRatio
  parms.df$SD.FMSYtoMratio <- SD.FMSYtoMratio
  parms.df$Delta <- Delta
  parms.df$SD.Delta <- SD.Delta
  parms.df$DeltaLowerBound <- DeltaLowerBound
  parms.df$DeltaUpperBound <- DeltaUpperBound
  parms.df$BMSYtoB0ratio <- BMSYtoB0ratio
  parms.df$SD.BMSYtoB0ratio <- SD.BMSYtoB0ratio
  parms.df$BMSYtoB0LowerBound <- BMSYtoB0LowerBound
  parms.df$BMSYtoB0UpperBound <- BMSYtoB0UpperBound
  parms.df$random.seed <- 4000


  # vector of species codes
  sp.vec <- as.character(parms.df$species.code)
  N.spp  <- length(sp.vec)

  # import landings data

  TempCatch<- NULL
  for (y in 1:length(yr))
  {
    TempCatch[y]<- sum(CatchDat$Catch[CatchDat$Year==yr[y]])
  }


  lands.df<- data.frame(CatchDat$Year, sp.vec[1],TempCatch)
  colnames(lands.df)<- c('year','sp.code','catch.mt')

  if (InterpCatch==1)
  {

    InterpCatch<- na.approx(lands.df$catch.mt)

    lands.df$catch.mt<- InterpCatch

  }

  # get first and last year of landings for each species and append to parms.df
  first.yr <- numeric(N.spp)
  last.yr <- numeric(N.spp)
  for (i in 1:N.spp)
  {
    first.yr[i] <- min(lands.df[lands.df$sp.code==sp.vec[i], "year"])
    last.yr[i]  <- max(lands.df[lands.df$sp.code==sp.vec[i], "year"])
  }

  parms.df <- cbind.data.frame(parms.df, first.yr, last.yr)
  dimnames(parms.df)[[1]] <- sp.vec
  rm(i, first.yr, last.yr)

  # print warning if DCAC start year or end year is beyond landings
  if (any(parms.df$DCAC.start.yr < parms.df$first.yr)) print("DCAC start year is earlier than landings")
  if (any(parms.df$DCAC.end.yr > parms.df$last.yr)) print("DCAC end year is later than landings")

  # print warning if any species has fewer catch records than years in time series
  for (i in sp.vec)
  {
    if ( nrow(lands.df[lands.df$sp.code==i,]) != length(parms.df[i,"first.yr"]:parms.df[i,"last.yr"]) )
    {
      print(paste("Species", i, "has missing catch records."))
    }
  }

  # sort annual landings data frame by species, year
  lands.df <- lands.df[with(lands.df, order(sp.code, year)), ]

  # convert time series of catch to list object (convenient format for DB-SRA)
  Catch.list <- as.list(1:N.spp)
  names(Catch.list) <- sp.vec
  for (i in 1:N.spp)
  {
    Catch.list[[i]] <- lands.df[lands.df$sp.code==sp.vec[i], "catch.mt"]
    names(Catch.list[[i]]) <- lands.df[lands.df$sp.code==sp.vec[i], "year"]
  }

  # sum catch for species i between start and target years for DCAC
  DCAC.SumC <- numeric(N.spp)
  names(DCAC.SumC) <- sp.vec
  for (i in 1:N.spp)
  {
    catch.i <- lands.df[lands.df$sp.code==sp.vec[i] &
                          lands.df$year>=parms.df[sp.vec[i],"DCAC.start.yr"] &
                          lands.df$year<=parms.df[sp.vec[i],"DCAC.end.yr"], "catch.mt"]
    DCAC.SumC[i] <- sum(catch.i)
  }

  parms.df <- cbind.data.frame(parms.df, DCAC.SumC)
  DCAC.Nyrs <- parms.df$DCAC.end.yr - parms.df$DCAC.start.yr + 1
  parms.df <- cbind.data.frame(parms.df, DCAC.Nyrs, AvgCatch=(DCAC.SumC/DCAC.Nyrs))
  rm(DCAC.SumC, DCAC.Nyrs, i, catch.i)

  ###############################################################################
  # function to simulate from bounded beta distribution with
  # m=mean and s=standard deviation;
  # NOTE: the mean is the mean of the truncated dist, but the SD is from a standard (0,1) beta
  # the user specifies the upper and lower values [a,b]

  rbeta.ab <- function(n, m, s, a, b)
  {
    # calculate mean of corresponding standard beta dist
    mu.std <- (m-a)/(b-a)

    # calculate parameters of std. beta with mean=mu.std and sd=s
    alpha <- (mu.std^2 - mu.std^3 - mu.std*s^2) / s^2
    beta  <- (mu.std - 2*mu.std^2 + mu.std^3 - s^2 + mu.std*s^2) / s^2

    # generate n draws from standard beta
    b.std <- rbeta(n, alpha, beta)

    # linear transformation from beta(0,1) to beta(a,b)
    b.out <- (b-a)*b.std + a

    return(b.out)
  }
  ###############################################################################

  # initialize list object for random draws
  rand.list <- as.list(1:N.spp)
  names(rand.list) <- sp.vec

  # generate random draws from input distributions
  for (i in 1:N.spp)
  {
    M <- parms.df[sp.vec[i], "M.est"]
    SD.lnM <- parms.df[sp.vec[i], "SD.lnM"]
    FMSYtoMratio <- parms.df[sp.vec[i], "FMSYtoMratio"]
    SD.FMSYtoMratio <- parms.df[sp.vec[i], "SD.FMSYtoMratio"]
    Delta <- parms.df[sp.vec[i], "Delta"]
    SD.Delta <- parms.df[sp.vec[i], "SD.Delta"]
    DeltaLowerBound <- parms.df[sp.vec[i], "DeltaLowerBound"]
    DeltaUpperBound <- parms.df[sp.vec[i], "DeltaUpperBound"]
    BMSYtoB0ratio <- parms.df[sp.vec[i], "BMSYtoB0ratio"]
    SD.BMSYtoB0ratio <- parms.df[sp.vec[i], "SD.BMSYtoB0ratio"]
    BMSYtoB0LowerBound <- parms.df[sp.vec[i], "BMSYtoB0LowerBound"]
    BMSYtoB0UpperBound <- parms.df[sp.vec[i], "BMSYtoB0UpperBound"]
    myseed <- parms.df[sp.vec[i], "random.seed"] + 9167*(0:3)

    # lognormal distribution for natural mortality (M)
    set.seed(myseed[1])
    M.vec     <- rlnorm(Niter, meanlog=(log(M)-0.5*SD.lnM^2), sdlog=SD.lnM)

    # lognormal distribution for ratio of ratio FMSY/M
    set.seed(myseed[2])
    FMSYtoM.vec  <- rlnorm(Niter, meanlog=(log(FMSYtoMratio)-0.5*SD.FMSYtoMratio^2), sdlog=SD.FMSYtoMratio)

    # simulate bounded beta draws for delta
    set.seed(myseed[3])
    Delta.vec <- rbeta.ab(Niter, Delta, SD.Delta,
                          DeltaLowerBound, DeltaUpperBound)

    # simulate draws for ratio of BMSY/B0
    set.seed(myseed[4])
    BMSYtoB0.vec  <- rbeta.ab(Niter, BMSYtoB0ratio, SD.BMSYtoB0ratio,
                              BMSYtoB0LowerBound, BMSYtoB0UpperBound)

    rand.list[[i]] <- cbind(M.vec, FMSYtoM.vec, Delta.vec, BMSYtoB0.vec)

  }

  # stop if RNGs generarate NAs (happens when delta SD is large relative to mean)
  if(any(unlist(lapply(rand.list, FUN=function(x) any(is.na(x))))))
  {
    stop("NAs in random draws -- check distribution parameters")
  }

  ###############################################################################
  # DCAC function that accepts vector of parameters from input distributions
  # parm.vec is a vector of length 4 with values for M, Fmsy/M, Depletion Delta, and Bmsy/B0
  # call this function from apply(); faster than using for loop
  DCAC.fun <- function(parm.vec, SumOfCatch, NumberOfYears)
  {

    # assign names to elements of parameter vector (parm.vec) to clarify code
    M        <- parm.vec[1]
    FMSYtoM  <- parm.vec[2]
    Delta    <- parm.vec[3] # proportion that biomass is reduced relative to biomass in start year (NOT "depletion")
    BMSYtoB0 <- parm.vec[4]

    # calculate DCAC
    DCAC <- SumOfCatch / (NumberOfYears + (Delta/(BMSYtoB0 * FMSYtoM * M)))

    return(DCAC)
  }
  ###############################################################################

  # PARALLEL EXECUTION OF DCAC

  # initialize "cluster" (use of multiple cores)
  sfInit(parallel=do.parallel, cpus=NumCPUs)

  # export variables & functions needed by slaves for parallel operation
  sfExport(list=c("DCAC.fun","rand.list","parms.df","sp.vec"))

  DCAC.start.time <- Sys.time() # record time to see how fast this works

  # apply function DCAC.fun to elements of rand.list, parms.df$DCAC.SumC, and parms.df$DCAC.Nyrs
  DCAC.list <- sfLapply(1:N.spp, function(i) apply(rand.list[[i]], MARGIN=1, FUN=DCAC.fun,
                                                   SumOfCatch=parms.df[sp.vec[i], "DCAC.SumC"],
                                                   NumberOfYears=parms.df[sp.vec[i], "DCAC.Nyrs"])
  )

  # how long did DCAC calculation take?
  # print(Sys.time()-DCAC.start.time)

  names(DCAC.list) <- sp.vec
  DCAC.df <- as.data.frame(DCAC.list)
  rm(DCAC.list)

  # print summary (mean and quantiles) of DCAC distributions
  DCAC.summary <- t(apply(DCAC.df, MARGIN=2, FUN=quantile, probs=c(0.025, 0.25, 0.5, 0.75, 0.975)))
  DCAC.summary <- cbind.data.frame(species=parms.df[, "common.name"],
                                   mean.DCAC=apply(DCAC.df, 2, mean),
                                   DCAC.summary)

  # stop parallel cluster
  sfStop()

  # write DCAC output
  # write.csv(DCAC.df, "DCAC_results.csv", row.names=FALSE)
  # write.csv(DCAC.summary, "DCAC_summary.csv", row.names=TRUE)

  ####################################################################
  # functions to get exponent in Fletcher parameterization of P-T model
  # based on input value of BMSY/B0
  get.n <- function(BMSYtoB0ratio)
  {
    # define half-width of region around 1/e (Fox model) to randomly
    # assign a value that won't blow up but is reasonable approximation
    eps <- 1e-3

    # search for n when BMSY/B0 < B0/e
    if(BMSYtoB0ratio < (1/exp(1) - eps))
    {
      # lower bound of 0.05 for n sets minimum BMSY/B0 just below 5% (about 4.3%); plenty low
      n.out <- optimize(phi.fun, interval=c(0.05, 1-eps^2), phi.true=BMSYtoB0ratio)[[1]]
    }

    # approximate Fox model for values of BMSY/B0 near B0/e
    if( all( BMSYtoB0ratio >= (1/exp(1) - eps),
             BMSYtoB0ratio < (1/exp(1) + eps)))
    {
      n.out <- sample(c(1-eps, 1+eps), size=1)
    }

    # search for n when BMSY/B0 > B0/e
    if(BMSYtoB0ratio >= (1/exp(1) + eps))
    {
      # upper bound of 90 for n allows BMSY to occur at up to approx. 95% of B0
      n.out <- optimize(phi.fun, interval=c(1+eps^2, 90), phi.true=BMSYtoB0ratio)[[1]]
    }

    return(n.out)
  }

  # objective function to minimize error between input BMSY/B0 (phi.true) and estimate based on current n value
  phi.fun <- function(n, phi.true)
  {
    phi.out <- (1/n)^(1/(n-1))
    out <- (phi.true - phi.out)^2
    return(out)
  }

  # estimate lower and upper bounds for B0 for each species
  # lower bound = average catch used in DCAC
  # upper bound = 110% of sumCatch/min(Delta) (or twice the hake B0, whichever is smaller)
  B0.low.vec <- numeric(N.spp)
  B0.high.vec <- numeric(N.spp)
  for (i in 1:N.spp)
  {
    B0.low.vec[i] <- parms.df[sp.vec[i],"AvgCatch"]
    B0.high.vec[i] <- min(3000000, 1.1 * sum(Catch.list[[i]])/min(rand.list[[i]][,"Delta.vec"]))
  }

  # append B0 bounds to parms.df, so you can check if the optimization step hit the boundaries
  parms.df <- cbind.data.frame(parms.df, B0.low=B0.low.vec, B0.high=B0.high.vec)
  rm(B0.low.vec, B0.high.vec)

  ###############################################################################
  # Pella-Tomlinson-Fletcher-Schaefer-MacCall model

  Delay.Diff.fun <- function(i)
  {
    i=1

    first.yr  <- parms.df[sp.vec[i],"first.yr"]
    last.yr   <- parms.df[sp.vec[i],"last.yr"]

    # "delta.yr" IS THE YEAR THAT WILL BE FIT TO THE CURRENT DEPLETION VALUE ###
    delta.yr <- parms.df[sp.vec[i],"delta.yr"]

    catch.vec <- Catch.list[[i]]
    Amat <- parms.df[sp.vec[i], "age.mat"]
    N.yrs <- last.yr - first.yr + 1

    # assume B0 is greater than average catch
    B0.low  <- parms.df[sp.vec[i],"B0.low"]

    # assume B0 is less than it would be if surplus production were zero,
    # and depletion equaled (B0 - sumCatch)/B0; i.e. some catch is from production
    B0.high <- parms.df[sp.vec[i],"B0.high"]

    # create dataframe to hold results for this species:
    # 4 input distributions, FMSY, EMSY, BMSY, MSY,
    # OFL vector, biomass (B) vector, production (P) vector,
    # Bjoin, Flag.NegBiomass, Flag.MissTarget, Flag.OptimWarn, Flag.NAs
    temp.mat <- matrix(NA, ncol = (N.yrs+1)*3 + 8, nrow=Niter) # project time series one year beyond last landing
    results.df <- cbind.data.frame(rand.list[[i]],temp.mat)
    rm(temp.mat)

    # give names to columns in the results data frame
    names(results.df) <- c(names(results.df)[1:4],
                           c("FMSY", "EMSY", "BMSY", "MSY"),
                           paste("OFL",first.yr:(last.yr+1),sep=""),		# note extra year (forecast 1 yr past landings)
                           paste("B",first.yr:(last.yr+1),sep=""),		# note extra year (forecast 1 yr past landings)
                           paste("P",first.yr:(last.yr+1),sep=""),		# note extra year (forecast 1 yr past landings)
                           "Bjoin", "Flag.NegBiomass", "Flag.NAs","Flag.OptimWarn")

    results.df[,"Flag.OptimWarn"] <- 0
    results.df[,"Flag.NAs"] <- 0

    # calculate distribution of FMSY (instantaneous rate) from input values
    results.df[,"FMSY"] <- results.df[,"M.vec"] * results.df[,"FMSYtoM.vec"]

    # calculate distribution of EMSY (exploitation rate) from input values
    results.df[,"EMSY"] <- (1 - exp(-(results.df[,"M.vec"] + results.df[,"FMSY"]))) *
      ( results.df[,"FMSY"] / (results.df[,"M.vec"] + results.df[,"FMSY"]))

    for (j in 1:Niter)
    {
      # print(j)
      M        <- results.df[j, "M.vec"]
      FMSYtoM  <- results.df[j, "FMSYtoM.vec"]
      Delta    <- results.df[j, "Delta.vec"] # fraction biomass is reduced relative to biomass in start year (Delta is NOT "depletion")
      BMSYtoB0 <- results.df[j, "BMSYtoB0.vec"]
      FMSY     <- results.df[j, "FMSY"]
      EMSY     <- results.df[j, "EMSY"]

      if (any(Delta>=1, Delta<=0))
      {
        stop("Delta must be between 0 and 1 given assumptions of current production model")
      }
      Depletion <- 1-Delta

      n <- get.n(BMSYtoB0)
      g <- (n^(n/(n-1)))/(n-1)

      # create vector to hold biomass time series for this set of parameters
      B.vec <- numeric(N.yrs + 1)

      # create a vector to hold production time series for this set of parameters
      P.vec <- numeric(N.yrs + 1)

      # production functions that minimize difference between obs/pred depletion in target year
      # over a range of B0 values; parm.vec is current draw of c(M, FMSYtoM, Delta, BMSYtoB0, plus FMSY, EMSY)
      prod.fun <- function(B0, parm.vec, C.vec, Amat, detailed.output = TRUE)
      {
        M        <- parm.vec[1]
        FMSYtoM  <- parm.vec[2]
        Delta    <- parm.vec[3]
        BMSYtoB0 <- parm.vec[4]
        FMSY     <- parm.vec[5]
        EMSY     <- parm.vec[6]

        # calculate BMSY for current guess at B0 and current value of BMSYtoB0
        BMSY <- B0 * BMSYtoB0
        MSY <- BMSY * EMSY

        # calculate Bjoin and "s" (slope of production/biomass ratio) if BMSYtoB0 <= 0.5
        if (BMSYtoB0 < 0.3)
        {
          Bjoin <- B0 * (0.5*BMSYtoB0)
          s <- (1-n) * g * MSY * (Bjoin^(n-2)) * B0^(-n)
        }
        if (all(BMSYtoB0 >= 0.3, BMSYtoB0 <= 0.5))
        {
          Bjoin <- B0 * (0.75*BMSYtoB0 - 0.075)
          s <- (1-n) * g * MSY * (Bjoin^(n-2)) * B0^(-n)
        }

        if (BMSYtoB0 > 0.5)
        {
          Bjoin <- NA
        }

        # production function for Pella-Tomlinson-Fletcher model
        PTF.fun <- function(n, g, MSY, Blag, B0)
        {
          if (Blag > 0)
          {
            P <- g * MSY * (Blag/B0) - g * MSY * (Blag/B0)^n
          } else {
            P <- 0
          }
          return(P)
        }

        # production function for hybrid PTF-Schaefer model
        # that approximates BHSRR productivity when BMSY/B0 < 0.5
        hybrid.fun <- function(n, g, MSY, Blag, B0, Bjoin, s)
        {
          if (Blag > 0)
          {
            if (Blag >= Bjoin)
            {
              P <- g * MSY * (Blag/B0) - g * MSY * (Blag/B0)^n
            } else {
              P <-  Blag * ( ((g*MSY*(Bjoin/B0)-g*MSY*(Bjoin/B0)^n) / Bjoin) + s*(Blag - Bjoin) )
            }
          } else {
            P <- 0
          }
          return(P)
        }

        # assume B0 = B(t=1)
        B.vec[1] <- B0

        # production in first year is always zero (starting from B0)
        P.vec[1] <- 0

        # PROJECT FORWARD FROM CURRENT B0 USING PRODUCTION MODEL
        # two options: with and without M correction
        if (M.correction)
        {
          for (k in 2:(N.yrs+1))
          {
            # production is zero (at B0) until source of spawning output has been harvested
            if (k <= Amat)
            {
              # remove production term (P) because production is based on unfished biomass until k>Amat,
              # but include M correction and replace B.vec[k-Amat] with B0
              B.vec[k] <- B.vec[k-1] + (1-exp(-M))*(B0-B.vec[k-1]) - C.vec[k-1]
            } else {


              # if BMSY/B0 > 0.5, then use P-T-F model
              if (BMSYtoB0>0.5)
              {
                P.vec[k] <- PTF.fun(n, g, MSY, B.vec[k-Amat], B0)
              } else {

                # if BMSY/B0 <= 0.5, use hybrid model
                P.vec[k] <- hybrid.fun(n, g, MSY, B.vec[k-Amat], B0, Bjoin, s)
              }
              # production model with M correction term
              B.vec[k] <- B.vec[k-1] + P.vec[k] + (1-exp(-M))*(B.vec[k-Amat]-B.vec[k-1]) - C.vec[k-1]
            }
          }
        } else { # loop without M correction term
          for (k in 2:(N.yrs+1))
          {
            # production is zero (at B0) until source of spawning output has been harvested
            if (k <= Amat)
            {
              # remove production term (P) because production is based on unfished biomass until k>Amat
              B.vec[k] <- B.vec[k-1] - C.vec[k-1]
            } else {

              # if BMSY/B0 > 0.5, then use P-T-F model
              if (BMSYtoB0>0.5)
              {
                P.vec[k] <- PTF.fun(n, g, MSY, B.vec[k-Amat], B0)
              } else {

                # if BMSY/B0 <= 0.5, use hybrid model
                P.vec[k] <- hybrid.fun(n, g, MSY, B.vec[k-Amat], B0, Bjoin, s)
              }
              # production model without M correction term
              B.vec[k] <- B.vec[k-1] + P.vec[k] - C.vec[k-1]
            }
          }
        }

        # add year labels to the time series vectors
        names(B.vec) <- names(P.vec) <- first.yr:(last.yr+1)
        names(C.vec) <- first.yr:last.yr
        Btgt.to.B0.ratio <- as.numeric(B.vec[as.character(delta.yr)]/B0)
        obj.fun <- as.numeric(abs(B.vec[as.character(delta.yr)] - Depletion*B0))

        if (detailed.output == FALSE)
        {
          return(as.numeric(obj.fun))
        }
        if (detailed.output == TRUE)
        {
          return(list(Species = sp.vec[i],
                      ObjectiveFunction = as.numeric(obj.fun),
                      B0.Bounds = c(B0.low, B0.high),
                      # set catch in last.yr+1 to zero (no effect)
                      TimeSeries = cbind.data.frame(B.vec, P.vec, C.vec=c(C.vec,0)),
                      BMSY = BMSY,
                      MSY = MSY,
                      Bjoin = Bjoin))
        }

      } # end of prod.fun

      # global assignment (used in tryCatch)
      tag <<- 0

      tryCatch(B0.opt <- optimize(prod.fun,
                                  interval = c(B0.low, B0.high),
                                  ### the following are arguments passed to prod.fun()
                                  parm.vec = as.numeric(results.df[j,1:6]),
                                  C.vec = catch.vec,
                                  Amat = Amat,
                                  detailed.output = FALSE,
                                  tol = 0.0001),
               # if optimize() generates warning,
               # then tag this iteration and store result in "Flag.OptimWarn" column
               warning = function(warn) { tag <<- 1 },
               finally = B0.opt <- optimize(prod.fun,
                                            interval = c(B0.low, B0.high),
                                            ### the following are arguments passed to prod.fun()
                                            parm.vec = as.numeric(results.df[j,1:6]),
                                            C.vec = catch.vec,
                                            Amat = Amat,
                                            detailed.output = FALSE,
                                            tol = 0.0001)
      )

      # generate model results as a list
      prod.out <- prod.fun(B0.opt[[1]],
                           parm.vec=as.numeric(results.df[j,1:6]),
                           catch.vec,
                           Amat,
                           detailed.output = TRUE)

      # store model results for this iteration in this species
      Flag.NegBiomass <- any(prod.out[["TimeSeries"]]$B.vec <= 0)
      Flag.NAs <- any(is.na(prod.out[["TimeSeries"]]))
      result.vec <- as.numeric( c(prod.out[["BMSY"]],
                                  prod.out[["MSY"]],
                                  results.df[j,"EMSY"] * prod.out[["TimeSeries"]][,"B.vec"], # time series of OFL (EMSY*B(t))
                                  prod.out[["TimeSeries"]]$B.vec, # time series of biomass
                                  prod.out[["TimeSeries"]]$P.vec, # time series of production
                                  prod.out[["Bjoin"]],
                                  Flag.NegBiomass,
                                  Flag.NAs,
                                  tag)
      )
      results.df[j,7:ncol(results.df)] <- result.vec

    } # end of iteration loop

    # add results.df to list containing results for all species
    return(results.df)

  } # end of species loop (function)

  ### PARALLEL EXECUTION OF DB-SRA

  # initialize "cluster" (use of multiple cores)
  sfInit(parallel=do.parallel, cpus=NumCPUs)

  # export variables & functions needed by slaves for parallel operation
  sfExport(list=c("Catch.list",
                  "M.correction",
                  "rand.list",
                  "parms.df",
                  "sp.vec",
                  "Niter",
                  "get.n",
                  "phi.fun")
  )

  options(warn=1) # print any warnings as they occur -- only works in sequential mode (1 cpu)

  DelayDiff.start.time <- Sys.time()

  # Load-balanced parallel computation of all species (requires snowfall package)
  results.list <- sfClusterApplyLB(1:N.spp, Delay.Diff.fun)

  # how long did it take?
  # print(Sys.time()-DelayDiff.start.time)

  # stop parallel cluster
  sfStop()

  names(results.list) <- sp.vec

  N.yrs.vec <- parms.df$last.yr - parms.df$first.yr + 1		# need to redefine outside of delay diff fxn
  names(N.yrs.vec) <- sp.vec

  #### FLAG RUNS WITH OBVIOUS ERRORS ######

  # create flag for runs in which final depletion doesn't match current draw
  for (i in 1:N.spp)
  {
    B.target <- results.list[[i]][ ,paste("B",parms.df[i,"delta.yr"],sep="")]
    B0 <- results.list[[i]][ ,paste("B",parms.df[i,"first.yr"],sep="")]
    Depletion <- 1 - results.list[[i]][ ,"Delta.vec"]
    Flag.Miss <- as.numeric( abs( B.target/B0 - Depletion ) > 0.005) # depletion must be within 0.5% of target
    # if biomass in target year is NA, call it a miss (usually due to oscillations)
    Flag.Miss[is.na(Flag.Miss)] <- 1
    results.list[[i]] <- cbind(results.list[[i]], Flag.MissTarget=Flag.Miss)
    rm(B.target, B0, Depletion, Flag.Miss)
  }

  # create flag that identifies any runs that hit the bounds for B0
  # DEFINITION: "hitting" the bound means coming within 1 mt of upper OR 1% of lower bound
  for (i in 1:N.spp)
  {
    B0.low.tol <- 0.01*parms.df[i,"B0.low"] # B0 "hits" lower bound if it is within 1% of avg. catch
    B0.low.logical  <- abs(parms.df[i,"B0.low"]-results.list[[i]][,N.yrs.vec[i]+10]) < B0.low.tol
    # upper bound is "hit" if B0 is within 1 ton
    B0.high.logical <- abs(parms.df[i,"B0.high"]-results.list[[i]][,N.yrs.vec[i]+10])<1
    test.mat <- cbind(B0.low.logical, B0.high.logical)
    results.list[[i]] <- cbind(results.list[[i]], Flag.HitBounds=as.numeric(apply(test.mat, 1, any)))
  }
  rm(B0.low.logical, B0.high.logical, test.mat)

  # create flag that identifies any runs with an error from optimize(), but no other errors,
  # and also flag "good runs" for easy extraction into "results.good"
  results.good <- as.list(1:N.spp)
  for (i in 1:N.spp)
  {
    # identify flagged runs from optimize()
    Optim.flagged  <- results.list[[i]][,"Flag.OptimWarn"]
    # identify runs with no other errors
    No.other.flags <- apply(results.list[[i]][,c("Flag.NegBiomass","Flag.MissTarget","Flag.NAs","Flag.HitBounds")],
                            MARGIN=1, sum)<1
    Good.run.vec <- as.numeric(No.other.flags)
    ## replace NAs with 1
    Good.run.vec[is.na(Good.run.vec)] <- 1
    results.list[[i]] <- cbind(results.list[[i]],
                               Good.Run=Good.run.vec,
                               Flag.OptimWarnOnly=as.numeric(Optim.flagged>0 & No.other.flags>0))
    results.good[[i]] <- results.list[[i]][Good.run.vec>0,]
  }
  names(results.good) <- sp.vec
  rm(Optim.flagged, No.other.flags, Good.run.vec)

  # RECORD RESULTS OF ALL ITERATIONS FROM PTFS MODEL AS .CSV FILES
  # dir.create(paste(getwd(),"/model_output",sep=""))
  # write separate .csv files for each species' PTFS results
  # for (i in 1:N.spp)
  # {
  # write.table(results.list[[i]], paste(getwd(),"/model_output/", sp.vec[i], "_all_results.csv", sep=""),
  # sep=",", row.names=FALSE)
  # }

  ### save all trajectories that don't go negative, hit bounds, miss the target depletion level, or have NAs in timeseries
  # dir.create(paste(getwd(),"/retained_model_output",sep=""))
  # for (i in 1:N.spp)
  # {
  # write.table(results.good[[i]], paste(getwd(),"/retained_model_output/", sp.vec[i], "_good_results.csv", sep=""),
  # sep=",", row.names=FALSE)
  # }

  # summarize error messages (negative biomass, hitting bounds of B0, missing target, etc.)
  errors.list <- list()
  errors.df   <- data.frame(t(rep(NA, 9)))
  names(errors.df) <- c("Species","Niter","GoodRuns","Flag.NegBiomass","Flag.MissTarget",
                        "Flag.HitBounds","Flag.NAs","Flag.OptimWarn","Flag.OptimWarnOnly")
  for(i in 1:N.spp)
  {
    errors.list[[i]] <- results.list[[i]][,c("Good.Run","Flag.NegBiomass","Flag.MissTarget","Flag.HitBounds",
                                             "Flag.NAs","Flag.OptimWarn","Flag.OptimWarnOnly")]
    errors.df[i,1]   <- sp.vec[i]
    errors.df[i,2]   <- Niter
    errors.df[i,3:9] <- apply(errors.list[[i]], MARGIN=2, sum)
  }
  rm(errors.list)
  # errors.df
  # write.table(errors.df, paste(getwd(),"/error_summary.csv", sep=""), sep=",", row.names=F, quote=F)

  # save parms.df as a comma-delimited text file
  # write.table(parms.df, paste(getwd(),"/parms.csv", sep=""), sep=",", row.names=F, quote=F)



  # CALC SUMMARY STATS FOR UNFISHED BIOMASS
  # create list with B0 draws from each species
  B0.yrs <- paste("B",parms.df[,"first.yr"],sep="")
  B0.list <- as.list(1:N.spp)
  for (i in 1:N.spp)
  {
    B0.list[[i]] <- results.good[[sp.vec[i]]][,B0.yrs[i]]
  }
  names(B0.list) <- sp.vec

  x.tmp <- matrix(NA, nrow=N.spp, ncol=6)
  for (i in 1:N.spp)
  {
    x.tmp[i,1] <- mean(B0.list[[i]])
    x.tmp[i,2:6] <- quantile(B0.list[[i]], probs=c(0.025, 0.25, 0.5, 0.75, 0.975))
  }

  B0.stats <- cbind.data.frame(Common.Name = parms.df[,"common.name"], x.tmp)
  names(B0.stats ) <- c("Common.Name", "B0.Mean", "2.5%", "25%", "50%", "75%", "97.5%")
  # print(B0.stats)
  # write.table(B0.stats, file=paste(getwd(),"/DB-SRA_B0_summary_stats.csv", sep=""), sep=",", row.names=F, quote=F)
  rm(x.tmp)



  # CALC SUMMARY STATS FOR OFL IN "DBSRA.OFL.yr" (doesn't have to be the same as "delta.yr")
  # create list with OFL timeseries
  OFL.list <- as.list(1:N.spp)
  for (i in 1:N.spp)
  {
    # use "grep" to identify column names that begin (\\b) with "OFL" (period after OFL is wildcard)
    OFL.list[[i]] <- results.good[[sp.vec[i]]][, grep("\\bOFL.", names(results.good[[sp.vec[i]]]))]
  }
  names(OFL.list) <- sp.vec



  OFLStats<- as.data.frame(matrix(NA,nrow=length(yr),ncol=5))

  BioStats<- as.data.frame(matrix(NA,nrow=length(yr),ncol=4))

  bStats<- as.data.frame(matrix(NA,nrow=length(yr),ncol=4))

  FlatResults<- as.data.frame(matrix(NA,nrow=0,ncol=14))

  #Go through and squeeze results into usable format

  Results<- results.good[[sp.vec]]

  for (y in 1:length(yr))
  {

    WhereYear<- grepl(yr[y],colnames(Results))


    YearData<- Results[,WhereYear]

    b<- YearData[,grepl('B',colnames(YearData))]/Results$BMSY

    YearData<- cbind(YearData,b)

    Quants<- as.data.frame(apply(YearData,2,quantile,probs=c(0.025, 0.25, 0.5, 0.75, 0.975)))

    Means<- as.data.frame(apply(YearData,2,mean))

    SD<- as.data.frame(apply(YearData,2,sd))


    TempResults<- data.frame(yr[y],Results[,1:8],YearData,lands.df$catch.mt[y])

    colnames(TempResults)<- NA

    FlatResults<- rbind(FlatResults,TempResults)

    OFLMean<- grepl('OFL',rownames(Means))
    OFLStats[y,]<- data.frame(yr[y],Means[OFLMean,1],Quants[1,OFLMean],Quants[5,OFLMean],SD[OFLMean,1],check.names=F)

    BioMean<- grepl('B',rownames(Means))
    BioStats[y,]<- data.frame(yr[y],Means[BioMean,1],Quants[1, BioMean],Quants[5, BioMean],check.names=F)

    bMean<- grepl('b',rownames(Means))
    bStats[y,]<- data.frame(yr[y],Means[bMean,1],Quants[1, bMean],Quants[5, bMean],check.names=F)



  }



  colnames(FlatResults)<- c( 'Year',colnames(Results)[1:8],'OFL','B','P','BvBmsy','Catch')
  colnames(OFLStats)<- c( 'Year','OFL','LowerQuantile','UpperQuantile','SD')
  colnames(BioStats)<- c( 'Year','Biomass','LowerQuantile','UpperQuantile')
  colnames(bStats)<- c( 'Year','BvBmsy','LowerQuantile','UpperQuantile')

  pdf(file=paste(FigureFolder,'DBSRA Biomass Boxplots.pdf',sep=''))
  boxplot(B~Year,data= FlatResults,outline=F)
  title(xlab='Year',ylab='Biomass (mt)')
  dev.off()

  pdf(file=paste(FigureFolder,'DBSRA BvBmsy Boxplots.pdf',sep = ''))
  boxplot(BvBmsy~Year,data= FlatResults, outline=F)
  title(xlab='Year',ylab='B/Bmsy')
  dev.off()

  pdf(file=paste(FigureFolder,'DBSRA OFL Boxplots.pdf', sep = ''))
  boxplot(OFL~Year,data= FlatResults, outline=F)
  title(xlab='Year',ylab='OFL (mt)')
  dev.off()

  pdf(file=paste(FigureFolder,'DBSRA CatchvOFL Boxplots.pdf', sep = ''))
  boxplot((Catch/OFL)~Year,data= FlatResults, outline=F)
  title(xlab='Year',ylab='Catch/OFL')
  dev.off()

  pdf(file=paste(FigureFolder,'DBSRA Parameter Posteriors.pdf', sep = ''))
  par(mfrow=c(4,2))
  hist(FlatResults$M.vec,xlab='M',main=NULL)
  hist(FlatResults$FMSYtoM.vec,xlab='FMSYtoM',main=NULL)
  hist(FlatResults$BMSYtoB0.vec,xlab='BMSYtoB0',main=NULL)
  hist(FlatResults$Delta.vec,xlab='Depletion',main=NULL)
  hist(FlatResults$FMSY,xlab='Fmsy',main=NULL)
  hist(FlatResults$EMSY,xlab='Emsy',main=NULL)
  hist(FlatResults$BMSY,xlab='Bmsy',main=NULL)
  hist(FlatResults$MSY,xlab='MSY',main=NULL)
  dev.off()

  PlotColors<- heat.colors(4,alpha=0.6)


  pdf(file=paste(FigureFolder,'DBSRA Line Plots.pdf', sep = ''))
  par(mfrow=c(3,1))
  plot(OFLStats$OFL~OFLStats$Year,type='l',bty='n',xlab='Year',ylab='OFL (mt)')
  polygon(x=c(OFLStats$Year,rev(OFLStats$Year)),y=c(OFLStats$UpperQuantile,rev(OFLStats$LowerQuantile)),col= PlotColors[1],border=F)

  plot(BioStats$Biomass ~ BioStats $Year,type='l',bty='n',xlab='Year',ylab='Biomass (mt)')
  polygon(x=c(BioStats $Year,rev(BioStats $Year)),y=c(BioStats $UpperQuantile,rev(BioStats $LowerQuantile)),col= PlotColors[2],border=F)

  plot(bStats$BvBmsy~ bStats$Year,type='l',bty='n',xlab='Year',ylab='B/Bmsy',ylim=c(0,2.5))
  polygon(x=c(bStats$Year,rev(bStats$Year)),y=c(bStats$UpperQuantile,rev(bStats$LowerQuantile)),col= PlotColors[3],border=F)
  abline(h=1,lty=2)
  dev.off()

  OFL.yrs <- paste("OFL",parms.df[,"DBSRA.OFL.yr"],sep="")
  x.tmp <- matrix(NA, nrow=N.spp, ncol=6)
  for (i in 1:N.spp)
  {
    x.tmp[i,1] <- mean(OFL.list[[i]][,OFL.yrs[i]])
    x.tmp[i,2:6] <- quantile(OFL.list[[i]][,OFL.yrs[i]], probs=c(0.025, 0.25, 0.5, 0.75, 0.975))
  }



  OFL.stats <- cbind.data.frame(Common.Name = parms.df[,"common.name"],
                                Delta.Year = parms.df[,"delta.yr"],
                                OFL.Year = parms.df[,"DBSRA.OFL.yr"],
                                x.tmp)
  names(OFL.stats) <- c("Common.Name", "Delta.Year", "OFL.Year", "OFL.Mean", "2.5%", "25%", "50%", "75%", "97.5%")
  # print(OFL.stats)
  # write.table(OFL.stats, file=paste(getwd(),"/DB-SRA_OFL_summary_stats.csv", sep=""), sep=",", row.names=F, quote=F)
  rm(x.tmp)


  Flag<- NA

  Output<- data.frame(yr,'DBSRA', lands.df$catch.mt,OFLStats$OFL, OFLStats$LowerQuantile, OFLStats$UpperQuantile, OFLStats$SD,'OFL',Flag)

  colnames(Output)<- c('Year','Method','SampleSize','Value','LowerCI','UpperCI','SD','Metric','Flag')

  Output$Method<- 'DBSRA'

  Output$Metric<- 'OFL'

  return(list(Output=Output,Details=list(AllResults=FlatResults,BioSeries=BioStats,BvBmsySeries=bStats,DCAC= DCAC.summary)))
}
DanOvando/DLSA documentation built on May 6, 2019, 1:21 p.m.