Previously, the salinity model was defined as: \[\frac{dC}{dt} = - a Q_H (C - C_U) + b e^{-dQ_H}(C_D - C)\,.\] This model contains appropriate parameters \(a\), \(b\), and \(d\) for calibration. However, the model was originally derived via control volume as: \[\frac{dC}{dt} = - \frac{Q_C}{V} (C - C_U) + \frac{Q_E}{V} (C_D - C)\,,\] where \(Q_C\) is channel flow, \(V\) is the volume of water in the control volume, \(C_U\) is upstream salinity concentration, \(Q_E\) is the exchange flow with downstream, and \(C_D\) is downstream salinity. We can re-calibrate the model with sea level rise by making appropriate adjustments to \(a\) and \(b\) and calibrating only on the parameter \(d\). Note that this requires estimates of salinity under sea level rise, but that the calibration is improved because only \(d\) must be calibration. The sea-level-rise adjustments assume that sea-level rise will affect both the mean volume of water stored in the control volume and that the exchange flow is proportional to the variable storage in the control volume due to tidal oscillations (\(Q_E \propto \Delta V\)). We assume the original model was calibrated under current conditions represented by \(V = V_1\) and \(\Delta V = \Delta V_1\) (and captured by coefficients \(a_1\), \(b_1\), and \(d_1\)) and that sea level rise leads to new conditions \(V = V_2\) and \(\Delta V = \Delta V_2\) (captured by \(a_2\), \(b_2\), and \(d_2\)). Because we define \(a_1 = Q_C/ (V_1 Q_H)\) and \(a_2 = Q_C/ (V_2 Q_H)\), we can say that \[a_2 = a_1 V_1 / V_2\,.\] Similarly, \(b_1 = Q_{E1}/ V_1\) and \(b_2 = Q_{E2}/ V_2\) so that \(b_2 = b_1 Q_{E2}/Q_{E1} \times V_1/V_2\) or \[b_2 = b_1 \frac{\Delta V_2}{\Delta V_1}\frac{V_1}{V_2}\,.\]

In other words, the key to adjusting \(a\) and \(b\) for sea level rise is determining the ratio of exchange volumes and ratio of volumes. Then \(d\) can be re-calibrated directly to climate projections if such data are available. For clarity, we rewrite these ratios as \(R_E = \Delta V_2/\Delta V_1\) and \(R_V = V_2/V_1\) and therefore \(a_2 = a_1 / R_V\) and \(b_2 = b_1 R_E / R_V\). We therefore need appropriate functions for \(R_E\) and \(R_V\) with sea level rise as the independent variable. There are two ways in which this could be done, representing two extremes:

  1. The control volume in which salinity mixes is confined to water in the channel, and
  2. The control volume comprises the channel and groundwater which are perfectly connected and mixed.

Both approaches are described in the following section.

Adjusting parameters \(a\) and \(b\)

Control volume of isolated channel

If the channel is completely isolated from groundwater, then calculating \(R_E\) and \(R_V\) requires calculating \(V\) and \(\Delta V\) in the channel with and without sea level rise. We have done this by interpolating channel cross-sectional area at 16 locations spanning the Gorai to the Rupsa river. Channel area was fit as a power law of river stage at each of the 16 locations. The mean stage at each cross section was determined for present conditions via a linear interpolation from SW99 (the start of the Gorai river) to SW244 (Mongla, the most downstream location). We assumed that sea level rise would increase mean stage equally through the delta, so that the channel area under sea level rise could be calculated at each cross section using the original stage + sea level rise. We assumed that tidal oscillations dissipating as an exponential function moving further upstream. We therefore interpolated an exponential function to pass through the observed tidal oscillations at SW99 and SW244, and assumed that the magnitude of tidal oscillations (high tide - low tide) would remain constant with sea level rise. The change in channel area would therefore simply be recalculated as channel area with SLR at high tide minus channel area with SLR at low tide. The net result of this analysis produced curves for \(R_E\) and \(R_V\) that were nearly linear (R\(^2\) > 0.99) for sea level rise in the range \([0,1]\) m.

channel_ratios <- get_ganges_SLR_ratios(SLR_m = seq(0,1,by = 0.05))
ggplot(channel_ratios) + 
  geom_line(aes(SLR_m, R_E, color = "R_E")) + 
  geom_line(aes(SLR_m, R_V, color = "R_V")) + ylab("Ratio")

Control volume of fully mixed channel and groundwater

In the fully mixed version, we assume that the total volume is simply proportional to the depth of the water in the channel – in other words, the elevation of the water surface minus the elevation of the river bed. We take the average across all cross sections in the Gorai - Naboganga - Rupsa river, with and without sea level rise, to determine \(R_V\). Because the magnitude of tidal oscillations does not depend on sea level rise, \(R_E = 1\).

gw_ratios <- get_ganges_SLR_ratios(SLR_m = seq(0,1,by = 0.05), "gw")
ggplot(gw_ratios) + 
  geom_line(aes(SLR_m, R_E, color = "R_E")) + 
  geom_line(aes(SLR_m, R_V, color = "R_V")) + ylab("Ratio")

Calibrate to Dasgupta et al., 2015

S. Dasgupta, F.A. Kamal, Z.H. Khan, S. Choudhury, A. Nishat, River salinity and climate change: Evidence from coastal Bangladesh, World Sci. Ref. Asia World Econ. (2015) 205–242. doi:10.1142/9789814578622_0031.

Here the authors develop a mode to simulate salinity in the GBM delta by combining a localized rainfall-runoff model, a hydrodynamic (water flow and water level) module (MIKE 11), a Bay of Bengal salinity model based on MIKE 21FM, and a salinity model by combining advection-dispersion from MIKE 11 with the hydrodynamic model. The simulations included average inflor from India calculated as the average since the treaty was signed (1998-2013).

They simulate multiple scenarios in 2050, and plot the results for three specific scenarios:

  • Baseline (2012) – March average salinity is 5 ppt
  • Best future, 35 cm RMSLR (8 cm subsidence + 27 cm SLR) – March salinity is 8.046875 ppt, or an increase of 60.9375%.
  • Best future, 67 cm RMSLR (34 cm subsidence + 33 cm SLR) – March salinity is 9.15 ppt, or an increase of 83%.

Subsidence throughout the entire delta would cause the hydrodynamics within the delta to behave similarly to sea level rise. We therefore calibrate our model using the RMSLR scenarios directly as SLR.

Prepare calibration data

treaty_streamflow <- ganges_streamflow[ganges_streamflow$group == "Treaty avg",]
treat_salinity_35cm <- treaty_streamflow %>%
  bind_cols(S_ppm_model_baseline = sim_salin_annual(treaty_streamflow, v = ganges_params$param)) %>%
  mutate(S_ppm_SLR_calibrate = if_else(date >= "1998-03-01" & date <= "1998-03-31",
                                       S_ppm_model_baseline * (dasgupta_slr35cm_pct_increase+1), as.numeric(NA)))
treat_salinity_67cm <- treaty_streamflow %>%
  bind_cols(S_ppm_model_baseline = sim_salin_annual(treaty_streamflow, v = ganges_params$param)) %>%
  mutate(S_ppm_SLR_calibrate = if_else(date >= "1998-03-01" & date <= "1998-03-31",
                                       S_ppm_model_baseline * (dasgupta_slr67cm_pct_increase+1), as.numeric(NA)))

Update calibration parameters a and b

# get new a and b calibration parameters
v_ab_channel_slr35 <- update_ab_SLR(ganges_params$param, SLR_m = 0.35, "channel")
v_ab_channel_slr67 <- update_ab_SLR(ganges_params$param, SLR_m = 0.67, "channel")
v_ab_gw_slr35 <- update_ab_SLR(ganges_params$param, SLR_m = 0.35, "gw")
v_ab_gw_slr67 <- update_ab_SLR(ganges_params$param, SLR_m = 0.67, "gw")

Calibrate parameter d

# sea level rise of 35 cm
v_slr35_channel <- treat_salinity_35cm %>% rename(S_ppm = S_ppm_SLR_calibrate) %>% 
  calibrate_salinity_model(v_ab_channel_slr35, control = list(trace = FALSE))
#> Calibrating using Nelder-Mead method...
#> Warning in stats::optim(par = v_init[is.na(v)], fn = sse_wrapper, hydro_data = hydro_data, : one-dimensional optimization by Nelder-Mead is unreliable:
#> use "Brent" or optimize() directly
v_slr35_gw <- treat_salinity_35cm %>% rename(S_ppm = S_ppm_SLR_calibrate) %>% 
  calibrate_salinity_model(v_ab_gw_slr35, control = list(trace = FALSE))
#> Calibrating using Nelder-Mead method...
#> Warning in stats::optim(par = v_init[is.na(v)], fn = sse_wrapper, hydro_data = hydro_data, : one-dimensional optimization by Nelder-Mead is unreliable:
#> use "Brent" or optimize() directly

# sea level rise of 67 cm
v_slr67_channel <- treat_salinity_67cm %>% rename(S_ppm = S_ppm_SLR_calibrate) %>% 
  calibrate_salinity_model(v_ab_channel_slr67, control = list(trace = FALSE))
#> Calibrating using Nelder-Mead method...
#> Warning in stats::optim(par = v_init[is.na(v)], fn = sse_wrapper, hydro_data = hydro_data, : one-dimensional optimization by Nelder-Mead is unreliable:
#> use "Brent" or optimize() directly
v_slr67_gw <- treat_salinity_67cm %>% rename(S_ppm = S_ppm_SLR_calibrate) %>% 
  calibrate_salinity_model(v_ab_gw_slr67, control = list(trace = FALSE))
#> Calibrating using Nelder-Mead method...
#> Warning in stats::optim(par = v_init[is.na(v)], fn = sse_wrapper, hydro_data = hydro_data, : one-dimensional optimization by Nelder-Mead is unreliable:
#> use "Brent" or optimize() directly
# get_ganges_slr_params <- function(hydro_data, v, SLR_m, control_volume) {
#   v_ab_channel <- update_ab_SLR(v = v, SLR_m = SLR_m, control_volume = control_volume)
#   
#   v_slr <- hydro_data %>% rename(S_ppm = S_ppm_SLR_calibrate) %>% 
#     calibrate_salinity_model(v_ab, control = list(trace = FALSE))
# }

Calibrate to Akter et al,

R. Akter, M. Sakib, M. Rahman, Sumaiya, A. Haque, M.M. Rahman, M.R. Islam, CLIMATIC AND CYCLONE INDUCED STORM SURGE IMPACT ON SALINITY INTRUSION ALONG THE BANGLADESH COAST, in: Proc. 6th Int. Conf. Appl. Phys. Model. Coast. Port Eng. Sci., Ottawa, Canada, 2016.

In this paper, the authors build a salinity model for the GBM delta and simulate salnity throughout the delta in 2011 under current conditions and in 2092 with a temperature increase of 4\(^\circ\)C and a sea level rise of 0.54 m. In both cases, streamflow data from 2011 was used.

They simulate two scenarios including 2011 and 2092:

  • Baseline (2011) – June 6 salinity at Khulna is 5 ppt
  • Climate change with 54 cm SLR – June 6 salinity is 13.011611 ppt, or an increase of 160.2322206%.

The calibration is done in a separate file with 2011 streamflow to achieve the results below.

v_slr54_channel <- c(-12.6496881185107,-4.25847441165473,-6.64196860125567,10.4631033404715)
v_slr54_gw <-      c(-12.6090585920122,-4.25588413142161,-6.64803987054384,10.4631033404715)

Combine calibration results

slr_data <- as.data.frame(matrix(c(ganges_params$param, ganges_params$param, v_slr35_channel, v_slr35_gw, v_slr67_channel, v_slr67_gw, v_slr54_channel, v_slr54_gw), nrow = 8, byrow = FALSE)) %>% 
  setNames(c("SLR_Baseline_0", "SLR_Dasgupta2014_35","SLR_Dasgupta2014_67","SLR_Akter2016_54")) %>%
  bind_cols(log_var = rep(c("log(a)","log(b)","log(d)","log(C_d)"),2), geometry = rep(c("channel","gw"), each =4)) %>% 
  pivot_longer(starts_with("SLR_"),names_to = "source_slr", names_prefix = "SLR_", values_to = "log_param") %>%
  separate(source_slr,into = c("source","SLR_cm"), sep="_") %>%
  mutate(SLR_cm = as.numeric(SLR_cm),
         param = exp(log_param),
         var = gsub(".*\\((.*?)\\)","\\1",log_var))

The calibrated \(d\) is very similar for the channel control volume and groundwater-connected control volume. the difference between the two is less than 1% in all cases (see below). We therefore use the minimum and maximum slope for \(d\) from the combined calibrations and assume that they apply in all cases.

slr_data %>% filter(var == "d") %>%
  mutate(d_0 = param[SLR_cm == 0][1]) %>% select(-log_param) %>%
  filter(SLR_cm != 0) %>% pivot_wider(names_from = c("geometry","var"), values_from = "param") %>%
  mutate(pct_diff = (gw_d - channel_d) / channel_d) %>%
  select(SLR_cm, `d, % diff between gw and channel` = pct_diff)
#> # A tibble: 3 x 2
#>   SLR_cm `d, % diff between gw and channel`
#>    <dbl>                              <dbl>
#> 1     35                          -0.000450
#> 2     67                          -0.00180 
#> 3     54                          -0.00605
d_bounds <- slr_data %>% filter(var == "d") %>%
  mutate(d_0 = param[SLR_cm == 0][1]) %>% select(-log_param) %>%
  filter(SLR_cm != 0) %>%
  mutate(d_slope = (param - d_0) / SLR_cm) %>%
  filter(d_slope == min(d_slope) | d_slope == max(d_slope)) %>%
  select(d_0, d_slope) %>%
  mutate(slope = if_else(d_slope == max(d_slope), "d_slope_max","d_slope_min")) %>%
  pivot_wider(names_from = "slope", values_from = "d_slope")
d_bounds
#> # A tibble: 1 x 3
#>       d_0 d_slope_max d_slope_min
#>     <dbl>       <dbl>       <dbl>
#> 1 0.00206 -0.00000724  -0.0000141
d_ribbon <- d_bounds %>% 
  crossing(SLR_cm = seq(min(slr_data$SLR_cm), max(slr_data$SLR_cm), length.out = 2)) %>%
  mutate(d_min = d_0 + d_slope_min * SLR_cm,
         d_max = d_0 + d_slope_max * SLR_cm,
         var = "d")
p_slr <- ggplot(slr_data %>% filter(var %in% c("a","b","d"))) + 
  geom_point(aes(SLR_cm, param, shape = source, color = geometry), size = 1, stroke = 1) + 
  geom_ribbon(data = d_ribbon, aes(SLR_cm, ymin = d_min, ymax = d_max), alpha = 0.5) +
  scale_shape_manual(values = c(4,1,3)) +
  facet_wrap(~var, scales = "free_y")
p_slr



# ggsave("slr_parameters.png",p_slr, path = "~/Desktop", width = 8, height = 2)