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.
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:
if source population has not reached carrying capacity, the populations sizes are given by equation (3).
or, if the source population size exceeds the carrying capacity ($n_{Source}$ = $N^$): \begin{equation} \tag{5} n_{Source,\mathrm{~next~year~initial}} = N^{}\ n_{Sink,\mathrm{~next~year~initial}} = n_{Sink,\mathrm{~end~of~year}} + I_{\mathrm{to~sink}}\= \lambda_{Sink} n_{Sink} + (\lambda_{Source} n_{Source} - N^{}) = \lambda_{Sink} n_{Sink} + (\lambda_{Source} - 1)N^{} \end{equation}
From there, the annual cycle begins again.
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"))
# 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
Clearly, the equilibrium is the carrying capacity of the source site. \begin{equation} \tag{6} n_{Source}^{} = N^{} \end{equation}
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.
ecoevoapps::print_app_footer()
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.