wzxhzdk:0

Model overview

A metapopulation is a collection of spatially separated populations of a given species.

In the simplified source-sink metapopulation model (Pulliam 1988), the two populations differ in their fecundity due to different habitat quality. The source population has a high-quality habitat, high fecundity, and overall positive population growth rate until it reaches a carrying capacity. The sink population has a low-quality habitat in which the population cannot sustain itself.

The two populations are linked via emmigration/immigration, and their population growths are described in the next section.

Annual cycle & population growth

The population size at the beginning of a year is denoted $n$.
During the summer breeding season, each adult produces $\beta$ offspring.
Therefore, the end-of-summer population size is given by: \begin{equation} \tag{1} n_{\mathrm{end~of~summer}} = \mathrm{adults} + \mathrm{newborn~juveniles} = n + \beta n \end{equation}

The annual survival rate of adults is denoted $P_{A}$, and that of juveniles is denoted $P_{J}$.

Therefore, the end-of-year population size is: \begin{equation} \tag{2} n_{\mathrm{end~of~year}} = \mathrm{survived~adults} + \mathrm{survived~juveniles} = P_{A}n + P_{J} \beta n \end{equation}

Thus, we can respectively write the end-of-year size of the source population and sink population as: \begin{equation} \tag{3} n_{Source,\mathrm{~end~of~year}} = P_{A}n_{Source} + P_{J} \beta_{Source} n_{Source} = \lambda_{Source} n_{Source}\ n_{Sink,\mathrm{~end~of~year}} = P_{A}n_{Sink} + P_{J} \beta_{Sink} n_{Sink} = \lambda_{Sink} n_{Sink} \end{equation} where the $\lambda_i = (P_A + P_J\beta_i)$ represents the net annual growth rates of the respective population. Populations can grow provided that $\lambda > 1$.

The Pulliam model assumes the source and sink sites differ only in their effect on the fecundity ($\beta$). By definition, the source population has a positive population growth rate ($\lambda_{Source}$ > 1), and the sink population a negative population growth rate ($\lambda_{Sink}$ < 1).

If the source population exceeds the site carrying capacity ($N^{}$), any excess individuals will disperse to the sink site, where there is no upper limit on population size (i.e. no carrying capacity). \begin{equation} \tag{4} E_{\rm{from~source}} = I_{\mathrm{to~sink}} = n_{Source,\mathrm{~end~of~year}} - N^{} \end{equation} At the beginning of the next year, the source and sink population will become:

From there, the annual cycle begins again.

Illustration of the annual cycle, taken from Pulliam 1988 Fig.1

Table of model parameters

library(dplyr)
library(knitr)
library(kableExtra)

paramTable <- data.frame(
  Parameter = c("$P_{a}$", "$P_{j}$", "$\\beta_{Source}$", "$\\beta_{Sink}$", "$\\lambda_{Source}$", "$\\lambda_{Sink}$", "$N^{*}$"),

  Description = c("Probability of adults surviving winter", "Probability of juveniles surviving winter", "Fecundity at the source site", "Fecundity at the sink site", "Source population growth rate, given by $P_{a}$ + $\\beta_{Source}$ $P_{j}$", "Sink population growth rate, given by $P_{a}$ + $\\beta_{Sink}$ $P_{j}$", "Carrying capacity/equilibirum of source population"),

  Value.Range = c("0-1, constrained by $\\lambda$s", "0-1, constrained by $\\lambda$s", "> 0,  constrained by $\\lambda$s", "> 0, constrained by $\\lambda$s", "> 1", "< 1", "positive integers"))

kable(paramTable, col.names = c("Paramter", "Description", "Acceptable range of values")) %>%
  kable_styling(bootstrap_options = c("striped"))

Simulation of the populations

Interactive simulation plot

# Pulliam source-sink metapopulation model
library(tidyverse)
library(shiny) # need the (newest as of July 2020) version 1.5.0
# use UI modeules for separate input for desktop vs mobile devices
# later each one will be under their own NameSpace
ssinputUI <- function(id) {
    tagList(
        ### ask for params (model parameters)
        ### params <- c(pa = .7, pj = .2, betaSource = 3, betaSink = 1, NSource = 300) 
        sliderInput(NS(id, "pa"), label = "Probability of adults surviving winter (Pa)", min = 0, max = 1, value = .7),
        sliderInput(NS(id, "pj"), label = "Probability of juveniles surviving winter (Pj)", min = 0, max = 1, value = .2),
        sliderInput(NS(id, "betaSource"), label = "Fecundity at the source site (betaSource)", min = 0, max = 100, value = 3),
        textOutput(NS(id, "suggest_betaSource")), # suggested betaSource range given pa and pj
        sliderInput(NS(id, "betaSink"), label = "Fecundity at the sink site (betaSink)", min = 0, max = 50, value = 1),
        textOutput(NS(id, "suggest_betaSink")), # suggested betaSink range given pa and pj
        numericInput(NS(id, "NSource"), label = "maximum breeding sites at source", min = 1, value = 300),

        ### ask for init (inital conditions)
        ### init <- c(N = 20, P = 2)
        numericInput(NS(id, "n0Source"), label = "initial population at source (n0Source)", min = 0, value = 110),
        numericInput(NS(id, "n0Sink"), label = "initial population at sink (n0Sink)", min = 0, value = 100),

        ### ask for time (time to simulate)
        numericInput(NS(id, "endtime"), label = "years to simulate", min = 2, value = 50)
    ) # end of taglist (input)
}

# use UI modeules for separate simulation plots
sssimuUI <- function(id){
    tagList(
        plotOutput(NS(id, "simulation"))
    )
}

# use UI modeules for separate equilibirum output
ssequiUI <- function(id){
    tagList(
        actionButton(NS(id, "showAnswer"), "Show/update sink equilibirum", style = "color: rgb(201, 18, 18);"),
        textOutput(NS(id, "equilibrium"))
    ) # end of taglist
}
# server modules for separate output based on respective inputs (desktop vs mobile tabs)
ssServer <- function(id) {
    moduleServer(id, function(input, output, session) {

        ### first check if betaSource is valid for source population
        observeEvent(input$betaSource, # check this input spot
          shinyFeedback::feedbackWarning( # gives warning if:
            "betaSource", # this variable (betaSource)
            input$pa + input$pj * input$betaSource <= 1, # if satisefy this condition
            "pa + pj * betaSource should >1 for source polulation, view suggestion.  
            Note that the violation does not stop the simulation. It just no longer satisfies the definition of a Pulliam source population" 
            # this warning message will show up
            # but simlulation will continue even with warning
            # if want simulation ONLY with valid input, use REQUIRE statement
          )  
        )
        ### suggested betaSource value based on pa and pj
        output$suggest_betaSource <- renderText(paste("Suggested input: betaSource >", round((1-input$pa) / input$pj, digits = 1)) )

        ### then check if betaSink is valid for sink population
        observeEvent(input$betaSink,
          shinyFeedback::feedbackWarning(
            "betaSink", 
            input$pa + input$pj * input$betaSink >= 1,
            "pa + pj * betaSink should <1 for sink polulation, view suggestion.  
            Note that the violation does not stop the simulation. It just no longer satisefies the definition of a Pulliam sink population"
          )  
        )
        ### suggested betaSource value based on pa and pj
        output$suggest_betaSink <- renderText(paste("Suggested input: betaSink <", round((1-input$pa) / input$pj, digits = 1)) )


        ### Set the initial population sizes
        #init <- c(n0Source = 110, n0Sink = 100)
        init <- reactive({ c(n0Source = input$n0Source, n0Sink = input$n0Sink)}) 

        ### Set the parameter values
        # params <- c(pa = .7, pj = .2, betaSource = 3, betaSink = 1, NSource = 300) 
        params <- reactive({ c(pa = input$pa, pj = input$pj, 
                               betaSource = input$betaSource, 
                               betaSink = input$betaSink, NSource = input$NSource) })
        ### Time over which to simulate model dynamics
        time <- reactive({ endtime = input$endtime })

        # run_source_sink function to get simulated population sizes at each time
        ss_list <- reactive({run_source_sink(endtime = time(), init = init(), params = params()) })

        ### check assumption again, if violated, project on the plot
        ss_assumption <- reactive({assumption_check(params = params()) })
        ### Plot
        ss_plot <- reactive({plot_source_sink(ss_list(), ss_assumption()) })    

        output$simulation <- renderPlot(ss_plot(), height = 300)

        ### calculate equilibrium for sink population, display on click
        equi <- eventReactive(input$showAnswer, {
          round(-(input$pa + input$pj*input$betaSource - 1)*
                  input$NSource/(input$pa + input$pj*input$betaSink - 1), digits = 0)
          })
        output$equilibrium <- renderText({ paste(equi(), ". (Read section 4 for details.)") })
  })
}
shinyApp( #to embed shiny app into the html document

    ### user interface part, ask for parameters and present the final outputs(suggested input, graph)
  ui = fluidPage(
    #withMathJax(), # not working so far -- for displaying special characters
    shinyFeedback::useShinyFeedback(), # feedback to ui

    # have two tabs for desktop vs mobile users, with different plot display format (fixed vs fluid)
    tabsetPanel(
      tabPanel("desktop full screen",
        fluidRow(
            column(4,
                wellPanel(width = "100%",
                ### ask for params (model parameters)
                # use UI module under NS "desktop"
                ssinputUI("desktop")
                )
            ), # end of left/input column

            column(8, 
                fixedPanel(right = 5, top = 30, width = "65%", sssimuUI("desktop") ) 
                # use UI module under NS "desktop" to give the plot
            ) # end of right/plot column
        ), # end of the first fluid row

        fixedRow(
          column(12, 
                 ### show calculated sink population equilibrium
                fixedPanel(right = 5, bottom = 15, width = "60%", height = 40,  
                  ssequiUI("desktop")
                ) # end of fixed panel
          ) # end of this column
        ) # end of the second fluid row
    ), # end of tab "desktop" 

    tabPanel("mobile devices",
        fluidRow(
          column(4,
              wellPanel(width = "100%",
              ### ask for params (model parameters)
              # use UI module under NS "mobile"
                ssinputUI("mobile")
              )
          ), # end of left/input column

          column(8, 
                 fluidRow(sssimuUI("mobile")),  # gives the plot
                 fluidRow(
                   wellPanel( 
                    ssequiUI("mobile")
                   ) # end of wellpanel
                 ) #end of equilibirium row
          ) # end of right/plot column
        )
      ) # end of tab "mobile"
    ) #end of tabset
  ), # end of ui

  ### server part, check validity of the input, generate output (graph) from the input
  server =  function(input, output, session) {
    # create modularized(separate) output from respective input for desktop vs mobile
    ssServer("desktop")
    ssServer("mobile")
  } # end of server

) # end of shiny app

Population equilibirium

Source population

Clearly, the equilibrium is the carrying capacity of the source site. \begin{equation} \tag{6} n_{Source}^{} = N^{} \end{equation}

Sink population

Recall that when the source population reaches equilibrium, the sink population annual increase can be written as: \begin{equation} \tag{7} n_{Sink,~next~year~initial} = (P_{A} + P_{J} \beta_{2}) n_{Sink} + (\lambda_{Source} n_{Source} - N^{}) = \lambda_{Sink} n_{Sink} + (\lambda_{Source} - 1)N^{} \end{equation} The above is a discrete time model, but if we consider a really long time, it becomes continuous. We represent the rate of change of sink population as the time deriative: \begin{equation} \tag{8} n_{Sink}^{'} = (P_{A} + P_{J} \beta_{Sink} - 1) n_{Sink} + (\lambda_{Source} n_{Source} - N^{}) = (\lambda_{Sink} - 1) n_{Sink} + (\lambda_{Source} - 1)N^{} \end{equation} At equilibrium, the rate of change is 0 and we can solve for $n_{Sink}^{}$: \begin{equation} \tag{9} 0 = n_{Sink}^{'} = (\lambda_{Sink}-1) n_{Sink}^{} + (\lambda_{SOurce} - 1)N^{} = (P_{A} + P_{J} \beta_{Sink} - 1) n_{Sink}^{} + (P_{A} + P_{J} \beta_{Source} - 1)N^{} \end{equation} \begin{equation} \tag{10} n_{Sink}^{} = -\frac{(P_{A} + P_{J} \beta_{Source} - 1)N^{*}}{(P_{A} + P_{J} \beta_{Sink} - 1)} \end{equation} Calculate the equilibrium of your sink population based on your input and verify your answer using the simulation plot.

Reference & helpful resources:

  1. Original Paper (Pulliam 1988)
  2. Colorado State University Lecture Notes
  3. UCLA Lecture Notes

ecoevoapps::print_app_footer()


gauravsk/ecoevoapps documentation built on July 9, 2024, 9:37 p.m.