# 4. faza: Napredna analiza podatkov

source("lib/libraries.r")

# ==============================================================================
# Uporabljene funkcije pri napredni analizi:
# ==============================================================================

# funkcije uporabljene pri razvrščanju v skupine:

hc.kolena = function(dendrogram, od = 1, do = NULL, eps = 0.5) {
  # število primerov in nastavitev parametra do
  n = length(dendrogram$height) + 1
  if (is.null(do)) {
    do = n - 1
  }
  # k.visina je tabela s štirimi stolpci
  # (1) k, število skupin
  # (2) višina združevanja
  # (3) sprememba višine pri združevanju
  # (4) koleno: ali je točka koleno?
  k.visina = tibble(
    k = as.ordered(od:do),
    visina = dendrogram$height[do:od]
  ) %>%
    # sprememba višine
    mutate(
      dvisina = visina - lag(visina)
    ) %>%
    # ali se je intenziteta spremembe dovolj spremenila?
    mutate(
      koleno = lead(dvisina) - dvisina > eps
    )
  k.visina
}

# iz tabele k.visina vrne seznam vrednosti k,
# pri katerih opazujemo koleno
hc.kolena.k = function(k.visina) {
  k.visina %>%
    filter(koleno) %>%
    dplyr::select(k) %>%
    unlist() %>%
    as.character() %>%
    as.integer()
}

# narišemo diagram višin združevanja
diagram.kolena = function(k.visina) {
  k.visina %>% ggplot() +
    geom_point(
      mapping = aes(x = k, y = visina),
      color = "black"
    )+
    geom_line(
      mapping = aes(x = as.integer(k), y = visina),
      color = "black"
    )+
    geom_point(
      data = k.visina %>% filter(koleno),
      mapping = aes(x = k, y = visina),
      color = "red", size = 2.5
    )+
    ggtitle(paste("Kolena:", paste(hc.kolena.k(k.visina), collapse = ", "))) +
    xlab("število skupin (k)") +
    ylab("razdalja pri združevanju skupin") +
    theme_classic()
}

diagram.skupine = function(podatki, oznake, skupine, k) {
  podatki = podatki %>%
    bind_cols(skupine) %>%
    rename(skupina = ...4)
  d = podatki %>%
    ggplot(
      mapping = aes(
        x = x, y = y, color = skupina
      )
    ) +
    geom_point() +
    geom_label(label = oznake, size = 2) +
    scale_color_hue(h = c(180, 270)) +
    xlab("") +
    ylab("") +
    theme_classic()
  for (i in 1:k) {
    d = d + geom_encircle(
      data = podatki %>%
        filter(skupina == i)
    )
  }
  d
}

obrisi = function(podatki, hc = TRUE, od = 2, do = NULL) {
  n = nrow(podatki)
  if (is.null(do)) {
    do = n - 1
  }
  razdalje = dist(podatki)
  k.obrisi = tibble()
  for (k in od:do) {
    if (hc) {
      o.k = hclust(razdalje) %>%
        cutree(k) %>%
        silhouette(razdalje)
    } else {
      set.seed(42) # zato, da so rezultati ponovljivi
      o.k = kmeans(podatki, k)$cluster %>%
        silhouette(razdalje)
    }
    k.obrisi = k.obrisi %>% bind_rows(
      tibble(
        k = rep(k, n),
        obrisi = o.k[, "sil_width"]
      )
    )
  }
  k.obrisi$k = as.ordered(k.obrisi$k)
  k.obrisi
}

obrisi.povprecje = function(k.obrisi) {
  k.obrisi.povprecje = k.obrisi %>%
    group_by(k) %>%
    summarize(obrisi = mean(obrisi))
}

obrisi.k = function(k.obrisi) {
  obrisi.povprecje(k.obrisi) %>%
    filter(obrisi == max(obrisi)) %>%
    summarize(k = min(k)) %>%
    unlist() %>%
    as.character() %>%
    as.integer()
}

diagram.obrisi = function(k.obrisi) {
  ggplot() +
    geom_boxplot(
      data = k.obrisi,
      mapping = aes(x = k, y = obrisi)
    ) +
    geom_point(
      data = obrisi.povprecje(k.obrisi),
      mapping = aes(x = k, y = obrisi),
      color = "red"
    ) +
    geom_line(
      data = obrisi.povprecje(k.obrisi),
      mapping = aes(x = as.integer(k), y = obrisi),
      color = "red"
    ) +
    geom_point(
      data = obrisi.povprecje(k.obrisi) %>%
        filter(obrisi == max(obrisi)) %>%
        filter(k == min(k)),
      mapping = aes(x = k, y = obrisi),
      color = "blue"
    ) +
    xlab("število skupin (k)") +
    ylab("obrisi (povprečje obrisov)") +
    ggtitle(paste("Maksimalno povprečje obrisov pri k =", obrisi.k(k.obrisi))) +
    theme_classic()
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# funkcije za prečno preverjanje:

ucenje = function(podatki, formula, algoritem) {
  switch(
    algoritem,
    lin.reg = lm(formula, data = podatki),
    log.reg = glm(formula, data = podatki, family = "binomial"),
    ng = ranger(formula, data = podatki)
  )
}

napovedi = function(podatki, model, algoritem) {
  switch(
    algoritem,
    lin.reg = predict(model, podatki),
    log.reg = ifelse(
      predict(model, podatki, type = "response") >= 0.5,
      1, -1
    ),
    ng = predict(model, podatki)$predictions
  )
}

napaka_regresije = function(podatki, model, algoritem) {
  podatki %>%
    bind_cols(yn.hat = napovedi(podatki, model, algoritem)) %>%
    mutate(
      izguba = (yn - yn.hat) ^ 2
    ) %>%
    select(izguba) %>%
    unlist() %>%
    mean()
}

napaka_razvrscanja = function(podatki, model, algoritem) {
  podatki %>%
    bind_cols(yd.hat = napovedi(podatki, model, algoritem)) %>%
    mutate(
      izguba = (yd != yd.hat)
    ) %>%
    select(izguba) %>%
    unlist() %>%
    mean()
}

# Razreži vektor x na k enako velikih kosov
razbitje = function(x, k) {
  # Razreži indekse vektorja na k intervalov
  razrez = cut(seq_along(x), k, labels = FALSE)
  # Razbij vektor na k seznamov
  # na osnovi razreza intervalov
  split(x, razrez)
}

pp.razbitje = function(n, k = 5, stratifikacija = NULL, seme = NULL) {
  # najprej nastavimo seme za naključna števila, če je podano
  if (!is.null(seme)) {
    set.seed(seme)
  }
  # če ne opravljamo stratifikacije, potem vrnemo navadno razbitje
  # funkcijo sample uporabimo zato, da naključno premešamo primere
  if (is.null(stratifikacija)) {
    return(razbitje(sample(1:n), k))
  }
  # če pa opravljamo stratifikacijo, razbitje izvedemo za vsako
  # vrednost spremenljive stratifikacija posebej in nato
  # podmnožice združimo v skupno razbitje
  r = NULL
  for (v in levels(stratifikacija)) {
    # Če smo pri prvi vrednosti vzpostavimo razbitje
    if (is.null(r)) {
      # opravimo razbitje samo za primere z vrednostjo v
      r = razbitje(sample(which(stratifikacija == v)), k)
    } else {
      # opravimo razbitje za vrednost v
      # in podmnožice združimo s trenutnim razbitjem
      r.v = razbitje(sample(which(stratifikacija == v)), k)
      for (i in 1:k) {
        r[[i]] = c(r[[i]], r.v[[i]])
      }
    }
  }
  r
}

precno.preverjanje = function(podatki, razbitje, formula, algoritem, razvrscanje) {
  # pripravimo vektor za napovedi
  if (razvrscanje) {
    pp.napovedi = factor(rep(1, nrow(podatki)), levels = c(-1,1))
  } else {
    pp.napovedi = rep(0, nrow(podatki))
  }
  # gremo čez vse podmnožice Si razbitja S
  for (i in 1:length(razbitje)) {
    # naučimo se modela na množici S \ Si
    model = podatki[ -razbitje[[i]], ] %>% ucenje(formula, algoritem)
    # naučen model uporabimo za napovedi na Si
    pp.napovedi[ razbitje[[i]] ] = podatki[ razbitje[[i]], ] %>% napovedi(model, algoritem)
  }
  if (razvrscanje) {
    mean(pp.napovedi != podatki$pricak)
  } else {
    mean((pp.napovedi - podatki$pricak) ^ 2)
  }
}

# Funkciji Predictor$new moramo povedati kako napovedujemo z modelom naključnih gozdov
pfun = function(model, newdata) {
  predict(model, data = newdata, predict.all = FALSE)$predictions
}

# ==============================================================================
# RAZVRSTITEV V SKUPINE
# ==============================================================================

# 1) k-means

# koliko skupin naj izberem pri razdelitvi z metodo voditeljev:
r.hc = TURIZEM.EVROPA[, -1] %>% obrisi(hc = TRUE)
r.km = TURIZEM.EVROPA[, -1] %>% obrisi(hc = FALSE)

r.hc.plt <- diagram.obrisi(r.hc)
r.km.plt <- diagram.obrisi(r.km)
# oba predlagata dve skupini

# - - - - - - - - -

set.seed(42)

k = 3
skupine <- TURIZEM.EVROPA[,-1] %>%
  kmeans(centers = k) %>%
  getElement("cluster") %>%
  as.ordered()
# print(skupine)

tabela.skupine.k.means <- TURIZEM.EVROPA %>%
  mutate(Skupine = as.numeric(skupine))

# uvozim zemljevid, ki ga bom potrebovala tudi v nadaljevanju. Prikazala se bo samo Evropa
zemljevid <-
  uvozi.zemljevid(
    "http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/50m/cultural/ne_50m_admin_0_countries.zip",
    "ne_50m_admin_0_countries",
    mapa = "zemljevidi",
    pot.zemljevida = "",
    encoding = "UTF-8"
  ) %>%
  fortify() %>%
  filter(CONTINENT %in% c("Europe"),
         long < 50 & long > -25 & lat > 35 & lat < 85)

names(tabela.skupine.k.means)[1] <- "ADMIN"

zemljevid.kmeans <- ggplot() +
  aes(x = long, y = lat, group = group, fill = factor(Skupine)) +
  geom_polygon(data = tabela.skupine.k.means %>%
                 right_join(zemljevid, by = "ADMIN"),
               color = "grey38", size = 0.2) +
  xlab("") +
  ylab("") +
  ggtitle("Razvrstitev Evropskih držav v tri skupine z metodo k-tih voditeljev") +
  coord_fixed(ratio = 1) +
  guides(fill=guide_legend(title="Skupina")) +
  theme_bw() +
  theme(plot.caption.position = "plot")  +
  scale_fill_brewer(palette = "YlGnBu",
                    na.translate = F,
                    na.value="white")

zemljevid.kmeans

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

set.seed(42)

k = 4
skupine4 <- TURIZEM.EVROPA[,-1] %>%
  kmeans(centers = k) %>%
  getElement("cluster") %>%
  as.ordered()
# print(skupine)

tabela.skupine.k.means4 <- TURIZEM.EVROPA %>%
  mutate(Skupine = as.numeric(skupine4))

# uvozim zemljevid, ki ga bom potrebovala tudi v nadaljevanju. Prikazala se bo samo Evropa
zemljevid <-
  uvozi.zemljevid(
    "http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/50m/cultural/ne_50m_admin_0_countries.zip",
    "ne_50m_admin_0_countries",
    mapa = "zemljevidi",
    pot.zemljevida = "",
    encoding = "UTF-8"
  ) %>%
  fortify() %>%
  filter(CONTINENT %in% c("Europe"),
         long < 50 & long > -25 & lat > 35 & lat < 85)

names(tabela.skupine.k.means4)[1] <- "ADMIN"

zemljevid.kmeans4 <- ggplot() +
  aes(x = long, y = lat, group = group, fill = factor(Skupine)) +
  geom_polygon(data = tabela.skupine.k.means4 %>%
                 right_join(zemljevid, by = "ADMIN"),
               color = "grey38", size = 0.2) +
  xlab("") +
  ylab("") +
  ggtitle("Razvrstitev Evropskih držav v štiri skupine z metodo k-tih voditeljev") +
  coord_fixed(ratio = 1) +
  guides(fill=guide_legend(title="Skupina")) +
  theme_bw() +
  theme(plot.caption.position = "plot")  +
  scale_fill_brewer(palette = "YlGnBu",
                    na.translate = F,
                    na.value="white")

zemljevid.kmeans4

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# 2) HIERARHIČNO RAZVRŠČANJE

X <- TURIZEM.EVROPA[,-1] %>% as.matrix() %>% scale()
drzave <- TURIZEM.EVROPA[, 1] %>% unlist()
razdalje <- TURIZEM.EVROPA[, -1] %>% dist()
dendrogram  <- dist(X) %>% hclust(method = "ward.D")
# plot(dendrogram,
#      labels = TURIZEM.EVROPA$Drzava,
#      ylab = "višina",
#      main = NULL)

# izračun kolen:
r = hc.kolena(dendrogram)
diagram.kolena(r)

# za kolena predlaga: 2, 3, 4, 6

drzave.x.y <-
  as_tibble(razdalje %>% cmdscale(k = 2)) %>%
  bind_cols(drzave) %>%
  dplyr::select(drzava = ...3, x = V1, y = V2)

# izberem število skupin:
k = 4

skupine <- TURIZEM.EVROPA[, 2] %>%
  dist() %>%
  hclust(method = "ward.D") %>%
  cutree(k = k) %>%
  as.ordered()

tabela.skupine.hierarh <- TURIZEM.EVROPA %>%
  mutate(Skupine = as.numeric(skupine))

names(tabela.skupine.hierarh)[1] <- "ADMIN"

zemljevid.hierarh <- ggplot() +
  aes(x = long, y = lat, group = group, fill = factor(Skupine)) +
  geom_polygon(data = tabela.skupine.hierarh %>%
                 right_join(zemljevid, by = "ADMIN"),
               color = "grey38", size = 0.2) +
  xlab("") +
  ylab("") +
  ggtitle("Razvrstitev Evropskih držav v štiri skupine po Wardovi metodi") +
  coord_fixed(ratio = 1) +
  guides(fill=guide_legend(title="Skupina")) +
  theme_bw() +
  theme(plot.caption.position = "plot") +
  scale_fill_brewer(palette = "YlGnBu",
                    na.translate = F,
                    na.value="white")

zemljevid.hierarh

# v skupini s Slovenijo:
# tabela.skupine.hierarh[tabela.skupine.hierarh$Skupine == 1, 1]

# ==============================================================================
# NAPOVEDNI MODEL
# ==============================================================================

# priprava tabele:
slovenija.turizem <- read_csv("podatki/turizem_svetovno.csv",
                             skip = 4,
                             locale = locale(encoding = "Windows-1250"),
                             col_names=TRUE,
                             col_types = cols(.default = col_guess()))
slovenija.turizem <- slovenija.turizem[slovenija.turizem$`Country Name` == "Slovenia",-c(2:39)]
slovenija.turizem <- slovenija.turizem %>%
  pivot_longer(
    cols = colnames(slovenija.turizem)[-1],
    names_to = "Leto",
    values_to = "Stevilo"
  ) %>%
  na.omit() %>%
  dplyr::select(Leto, "Stevilo")
slovenija.turizem$Stevilo <- as.integer(slovenija.turizem$Stevilo)

# tabela in dva stolpca, prvi so leta, drugi pa število turističnih obiskov

zamakni <- function(x, n){c(rep(NA, n), x)[1:length(x)]}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# IZBIRA napovednega modela (z napako): med linearno regrasijo in naključnimi gozdovi
#   pogledala bom napake obeh modelov
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#     1) glede na pretekla 4 leta:
naredi.df.4 <- function(x){
  data.frame(pricak  = x,
             "Leto 2020"  = zamakni(x, 1),
             "Leto 2019" = zamakni(x, 2),
             "Leto 2018" = zamakni(x, 3),
             "Leto 2017" = zamakni(x, 4))
}

df.4 <- drop_na(naredi.df.4(slovenija.turizem$Stevilo))
ucni.4 = pp.razbitje(nrow(df.4))

#       - naključni gozdovi:
precno.preverjanje(df.4, ucni.4, pricak ~ ., "ng", FALSE)

#       - linearna regrasija:
precno.preverjanje(df.4, ucni.4, pricak ~ ., "lin.reg", FALSE)

# napaka z naključnimi gozdovi je manjša

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#     2) glede na pretekla 3 leta:
naredi.df.3 <- function(x){
  data.frame(pricak  = x,
             "Leto 2020"  = zamakni(x, 1),
             "Leto 2019" = zamakni(x, 2),
             "Leto 2018" = zamakni(x, 3))
}

df.3 <- drop_na(naredi.df.3(slovenija.turizem$Stevilo))
ucni.3 = pp.razbitje(nrow(df.4))

#       - naključni gozdovi:
precno.preverjanje(df.3, ucni.3, pricak ~ ., "ng", FALSE)

#       - linearna regrasija:
precno.preverjanje(df.3, ucni.3, pricak ~ ., "lin.reg", FALSE)

# napaka z naključnimi gozdovi je manjša

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# NAPOVEDOVANJE Z METODO NAKLJUČNIH GOZDOV

# napovedovanje:
zamakni <- function(x, n){c(rep(NA, n), x)[1:length(x)]}

# napovedovanje glede na prejšnja 4 leta:

naredi.df.4 <- function(x){
  data.frame(pricak  = x,
             "Leto 2020"  = zamakni(x, 1),
             "Leto 2019" = zamakni(x, 2),
             "Leto 2018" = zamakni(x, 3),
             "Leto 2017" = zamakni(x, 4))
}

df.4 <- naredi.df.4(slovenija.turizem$Stevilo)
model = ranger(formula = pricak ~ ., data = df.4 %>% drop_na())

n = nrow(df.4)

df.4.2 = df.4
for(i in 1:3){
  df.4.2 = naredi.df.4(c(df.4.2$pricak, NA))
  napoved = predict(model, data = df.4.2[n+i,])$predictions
  df.4.2[n+i, 1] = napoved
}

# napovedi za naslednja 3 leta:
napovedi = df.4.2[(n+1):(n+3),1]

# s temi podaki bom dopolnila tabelo slovenija.turizem
slovenija.turizem.z.napovednjo <- slovenija.turizem
slovenija.turizem.z.napovednjo$Leto <- as.integer(slovenija.turizem.z.napovednjo$Leto)
slovenija.turizem.z.napovednjo$Stevilo <- as.numeric(slovenija.turizem.z.napovednjo$Stevilo)
zadnje_leto <- 2020
for (i in 1:3) {
  novo_leto <- as.integer(zadnje_leto + i)
  nov_podatek <- as.integer(napovedi[i])
  slovenija.turizem.z.napovednjo[nrow(slovenija.turizem.z.napovednjo) + 1, ] <-
    list(novo_leto, nov_podatek)
}

Leto <- 1995:2023
napovedovanje.graf <- ggplot(slovenija.turizem.z.napovednjo,
                             aes(x = Leto, y = Stevilo)) +
  geom_line(color = "black") +
  slovenija.turizem.z.napovednjo %>%
  filter(Leto %in% c(2020:2023)) %>%
  geom_line(
    mapping = aes(x = Leto, y = Stevilo),
    color = "red"
  ) +
  labs(
    x = "Leto",
    y = "Stevilo turističnih obiskov",
    title = "Stevilo turističnih obiskov Slovenija ob leta 1995 do 2020 z napovedjo \nza leta 2021, 2022 in 2023 "
  ) +
  theme_bw() +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5)) +
  scale_x_continuous("Leto", labels = as.character(Leto), breaks = Leto)

napovedovanje.graf # ni vključen v poročilo

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# prečno preverjanje napovedovanja glede na pretekla 4 leta:

# vrednosti NA motijo delovanje funckije

df.4 <- df.4 %>% drop_na()

ucni <- pp.razbitje(df.4, stratifikacija = df.4$pricak)

# Pripravimo le napovedne spremenljivke, ki jih rabi funkcija Predictor$new
X <- df.4[,-1]

# pripravimo najprej objekt razreda Prediktor
# prvi argument funkcije je model,
# drugi in tretji so podatki o napovednih
# in ciljni spremenljivki,
# zadnji pa funkcija za napovedovanje

reg.pred = Predictor$new(
  model,
  data = X, y = df.4$pricak,
  predict.fun = pfun
)

# na koncu uporabimo funkcijo FeatureImp$new
reg.moci = FeatureImp$new(reg.pred, loss = "mse")

reg.moci.4.plot <- plot(reg.moci) + theme_bw()
reg.moci.4.plot

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# napovedovanje glede na prejšnje 3 leta:

naredi.df.3 <- function(x){
  data.frame(pricak  = x,
             "Leto 2020"  = zamakni(x, 1),
             "Leto 2019" = zamakni(x, 2),
             "Leto 2018" = zamakni(x, 3))
}

df.3 <- naredi.df.3(slovenija.turizem$Stevilo)
model.3 = ranger(formula = pricak ~ ., data = df.3 %>% drop_na())

n = nrow(df.3)

df.3.2 = df.3
for(i in 1:3){
  df.3.2 = naredi.df.3(c(df.3.2$pricak, NA))
  napoved = predict(model.3, data = df.3.2[n+i,])$predictions
  df.3.2[n+i, 1] = napoved
}

# napovedi za naslednja 3 leta:
napovedi.3 = df.3.2[(n+1):(n+3),1]

# s temi podaki bom dopolnila tabelo slovenija.turizem
slovenija.turizem.z.napovednjo.3 <- slovenija.turizem
slovenija.turizem.z.napovednjo.3$Leto <- as.integer(slovenija.turizem.z.napovednjo.3$Leto)
slovenija.turizem.z.napovednjo.3$Stevilo <- as.integer(slovenija.turizem.z.napovednjo.3$Stevilo)
zadnje_leto <- 2020
for (i in 1:3) {
  novo_leto <- as.integer(zadnje_leto + i)
  nov_podatek <- as.integer(napovedi.3[i])
  slovenija.turizem.z.napovednjo.3[nrow(slovenija.turizem.z.napovednjo.3) + 1, ] <-
    list(novo_leto, nov_podatek)
}

Leto <- 1995:2023
napovedovanje.graf.3 <- ggplot(slovenija.turizem.z.napovednjo.3,
                             aes(x = Leto, y = Stevilo)) +
  geom_line(color = "black") +
  slovenija.turizem.z.napovednjo.3 %>%
  filter(Leto %in% c(2020:2023)) %>%
  geom_line(
    mapping = aes(x = Leto, y = Stevilo),
    color = "red"
  ) +
  labs(
    x = "Leto",
    y = "Stevilo turističnih obiskov",
    title = "Stevilo turističnih obiskov Slovenija ob leta 1995 do 2020 z napovedjo \nza leta 2021, 2022 in 2023 "
  ) +
  theme_bw() +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5)) +
  scale_x_continuous("Leto", labels = as.character(Leto), breaks = Leto)

napovedovanje.graf.3 # ni vključen v poročilo

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# prečno preverjanje napovedovanja glede na pretekla 3 leta:

# vrednosti NA motijo delovanje funckije
df.3 <- df.3 %>% na.omit()

ucni.3 <- pp.razbitje(df.3, stratifikacija = df.3$pricak)

# Pripravimo le napovedne spremenljivke, ki jih rabi funkcija Predictor$new
X.3 <- df.3[,-1]

# pripravimo najprej objekt razreda Prediktor
# prvi argument funkcije je model,
# drugi in tretji so podatki o napovednih
# in ciljni spremenljivki,
# zadnji pa funkcija za napovedovanje

reg.pred.3 = Predictor$new(
  model.3,
  data = X.3, y = df.3$pricak,
  predict.fun = pfun
)

# na koncu uporabimo funkcijo FeatureImp$new
reg.moci.3 = FeatureImp$new(reg.pred.3, loss = "mse")

reg.moci.3.plot <- plot(reg.moci.3) + theme_bw()
reg.moci.3.plot

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# skupni graf napovedovanja (za poročilo):
Leto <- 1995:2023
napovedovanje.graf.skupaj <- ggplot(slovenija.turizem.z.napovednjo,
                             aes(x = Leto, y = Stevilo)) +
  geom_line(color = "black") +
  slovenija.turizem.z.napovednjo %>%
  filter(Leto %in% c(2020:2023)) %>%
  geom_line(
    mapping = aes(x = Leto, y = Stevilo),
    color = "goldenrod1",
    size = 0.75
  ) +
  slovenija.turizem.z.napovednjo.3 %>%
  filter(Leto %in% c(2020:2023)) %>%
  geom_line(
    mapping = aes(x = Leto, y = Stevilo),
    color = "red",
    linetype = "dotdash",
    size = 0.75
  ) +
  labs(
    x = "Leto",
    y = "Število turističnih obiskov",
    title = "Število turističnih obiskov Slovenija ob leta 1995 do 2020 z napovedjo \nza leta 2021, 2022 in 2023 "
  ) +
  theme_bw() +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5)) +
  scale_x_continuous("Leto", labels = as.character(Leto), breaks = Leto) +
  annotate(geom="text", x=2022, y=3000000, label="napoved glede \nna pretekla \n4 leta",
           color="goldenrod1", size = 2.5) +
  annotate(geom="text", x=2022.5, y=1900000, label="napoved \nglede na \npretekla \n3 leta",
           color="red", size = 2.5)

napovedovanje.graf.skupaj

# ==============================================================================
# LINEARNA REGRESIJA
# ==============================================================================

linearna.regrasija.graf <-
  ggplot(slovenci.prenocitve, aes(x = Leto, y = Stevilo)) +
  geom_point(stat='identity', position='identity', aes(colour=Stevilo),size=1.3) +
  scale_colour_gradient(low='yellow', high='#de2d26') +
  labs(
    x = "Leto",
    y = "Število prenočitev",
  ) +
  ggtitle("Število prenočitev Slovencev v Sloveniji od leta 2010 do leta 2022") +
  theme_bw() +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5),
        legend.position="none") +
  geom_smooth(method = "lm", formula = y ~ x, colour="black", size=0.7)

linearna.regrasija.graf


# Copyright (c) 2020, Zolisa Bleki
# SPDX-License-Identifier: BSD-3-Clause

matrix_type <- c("regular", "diagonal", "identity")

validate_output <- function(res) {

    if (res$info < 0) {
        stop("Possible illegal value in one of the inputs")
    }
    else if (res$info > 0) {
        stop(
            "Either the leading minor of the ", res$info, "'th order is not
            positive definite (meaning the covariance matrix is also not
            positive definite), or factorization of one of the inputs
            returned a factor with a zero in the ", res$info, "'th diagonal."
        )
    }
    else {
        res$out
    }
}

hptrunc_mvn <- function(rng, mean, cov, g, r, diag, out) {

    stopifnot(
        is.vector(mean),
        is.matrix(cov),
        is.matrix(g),
        is.vector(r),
        is.logical(diag),
        length(mean) == nrow(cov),
        nrow(cov) == ncol(cov),
        nrow(cov) == ncol(g)
    )

    if (is.null(out))
        out <- rep(0, length(mean))

    res <- .Call(C_hpmvn, rng, mean, cov, g, r, diag, out, PACKAGE = "htnorm")

    validate_output(res)
}

strprec_mvn <- function(rng, mean, a, phi, omega, str_mean, a_id, o_id, out) {

    stopifnot(
        is.vector(mean),
        is.matrix(a),
        is.matrix(phi),
        is.matrix(omega),
        is.logical(str_mean),
        nrow(omega) == ncol(omega),
        ncol(omega) == nrow(phi),
        length(mean) == nrow(a),
        nrow(a) == ncol(a)
    )

    if (!is.element(a_id, matrix_type) || !is.element(o_id, matrix_type))
        stop("`a_type` and `o_type` need to be one of {'regular', 'diagonal', 'identity'}")

    a_id <- switch(
        a_id,
        "regular" = as.integer(0),
        "diagonal" = as.integer(1),
        "identity" = as.integer(2)
    )
    o_id <- switch(
        o_id,
        "regular" = as.integer(0),
        "diagonal" = as.integer(1),
        "identity" = as.integer(2)
    )

    if (is.null(out))
        out <- rep(0, length(mean))

    res <- .Call(
        C_spmvn,
        rng,
        mean,
        a,
        phi,
        omega,
        str_mean,
        a_id,
        o_id,
        out,
        PACKAGE = "htnorm"
    )

    validate_output(res)
}

#' Sample from a multivariate normal truncated on a hyperplane or a multivariate
#' normal with a structured precision. `HTNGenerator` returns an object that
#' can be used to sample from such a distribution.
#'
#' @param seed A random seed. It must be a positive integer. If not specified
#'  then it defaults to NULL, which means a random seed is used.
#' @param gen The type of random number generator to use internally. It must
#'  either "xrs128p" (Xoroshiro128plus) or "pcg64" (PCG64). If not specified
#'  then this parameter defaults to "xrs128p".
#' @return A generator object that can be used to sample from the supported
#'  distributions.
#' @references Cong, Yulai; Chen, Bo; Zhou, Mingyuan. Fast Simulation of
#'  Hyperplane-Truncated Multivariate Normal Distributions. Bayesian Anal.
#'  12 (2017), no. 4, 1017--1037. doi:10.1214/17-BA1052.
#'  https://projecteuclid.org/euclid.ba/1488337478
#'
#' @examples
#' mean <- rnorm(1000)
#' cov <- matrix(rnorm(1000 * 1000), ncol=1000)
#' cov <- cov %*% t(cov)
#' G <- matrix(rep(1, 1000), ncol=1000)
#' r <- c(0)
#' rng <- HTNGenerator(seed=12345, gen="pcg64")
#' samples <- rng$hyperplane_truncated_mvnorm(mean, cov, G, r)
#' # verify if sampled values sum to zero
#' sum(samples)
#'
#' out <- rep(0, 1000)
#' eig <- eigen(cov)
#' phi <- eig$vectors
#' omega <- diag(eig$values)
#' a <- diag(runif(length(mean)))
#' rng$structured_precision_mvnorm(mean, a, phi, omega, a_type = "diagonal", out = out)
HTNGenerator <- function(seed = NULL, gen = "xrs128p") {

    if (is.numeric(seed)) {
        if (seed < 0)
            stop("`seed` cannot be negative.")
        seed <- as.integer(seed)
        if (!is.finite(seed))
            stop("`seed` cannot be converted to an integer")
    }
    else if (!is.null(seed)) {
        stop("`seed` cannot be a non-numeric value")
    }

    gen <- switch(gen, "xrs128p" = as.integer(0), "pcg64" = as.integer(1))
    if (is.null(gen))
        stop("`gen` needs to one of {'xrs128p', 'pcg64'}")

    res <- list("rng" = .Call(C_get_rng, seed, gen, PACKAGE = "htnorm"))
    class(res) <- "HTNGenerator"

    res$hyperplane_truncated_mvnorm <- function(
        mean, cov, g, r, diag = FALSE, out = NULL
    ) {
        if (is.null(out)) {
            hptrunc_mvn(res$rng, mean, cov, g, r, diag, out)
        }
        else {
            invisible(hptrunc_mvn(res$rng, mean, cov, g, r, diag, out))
        }
    }

    res$structured_precision_mvnorm <- function(
        mean, a, phi, omega, str_mean = FALSE, a_type = "regular", o_type = "regular", out= NULL
    ) {
        if (is.null(out)) {
            strprec_mvn(
                res$rng, mean, a, phi, omega, str_mean, a_type, o_type, out
            )
        }
        else {
            invisible(
                strprec_mvn(
                    res$rng, mean, a, phi, omega, str_mean, a_type, o_type, out
                )
            )
        }
    }

    res
}


########################################################
# The MIT License (MIT)
#
# Copyright (c) 2014 Florian D. Schneider & Sonia Kéfi
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
########################################################

### grazing model: Schneider and Kéfi (2015, in review)

grazing <- list()
class(grazing) <- "ca_model"
grazing$name <- "Spatial Grazing Model"
grazing$ref <- "Schneider and Kéfi 2015, in review"
grazing$states <- c("+", "0", "-")
grazing$cols <- grayscale(3)
grazing$parms <- list(
  del = 0.9, # local seed dispersal
  b = 0.2, # environmental quality
  c_ = 0.2, # global competition
  m0 = 0.05, # intrinsic mortality
  g = 0.2, # grazing pressure
  r = 0.01, # regeneration rate of degraded cells
  f = 0.9, # local facilitation
  d = 0.2, # intrinsic degradation rate
  protect = 0.9 # associational resistance against grazing
)
grazing$update <- function(x_old, parms_temp, delta = 0.2, subs = 10, timestep = NA) {

  x_new <- x_old

  for(s in 1:subs) {

    parms_temp$rho_plus <- sum(x_old$cells == "+")/(x_old$dim[1]*x_old$dim[2]) # get initial vegetation cover
    parms_temp$Q_plus <- count(x_old, "+")/4  # count local density of occupied fields for each cell:

    # 2 - drawing random numbers
    rnum <- runif(x_old$dim[1]*x_old$dim[2]) # one random number between 0 and 1 for each cell

    # 3 - setting transition probabilities

    if(parms_temp$rho_plus > 0) {
      recolonisation <- with(parms_temp, (del*rho_plus+(1-del)*Q_plus)*(b-c_*rho_plus)*1/subs)

      death <- with(parms_temp, (m0+g*(1-protect))*1/subs)
      death[death > 1] <- 1
    } else {
      recolonisation <- 0
      death <- 1
    }

    regeneration <- with(parms_temp, (r + f*Q_plus)*1/subs)

    degradation <- with(parms_temp, (d*1/subs))

    # check for sum of probabilities to be inferior 1 and superior 0
    if(any(c(recolonisation+degradation, death, regeneration) > 1 )) warning(paste("a set probability is exceeding 1 in  time step", timestep, "! decrease number of substeps!!!"))
    if(any(recolonisation < 0)) warning(paste("recolonisation falls below 0 in time step",timestep, "! balance parameters!!!"))
    if(any(degradation < 0)) warning(paste("degradation falls below 0 in time step",timestep, "! balance parameters!!!"))
    if(any( death < 0)) warning(paste("death falls below 0 in time step",timestep, "! balance parameters!!!"))
    if(any(regeneration < 0)) warning(paste("regeneration falls below 0 in time step",timestep, "! balance parameters!!!"))

    # 4 - apply transition probabilities

    x_new$cells[which(x_old$cells == "0" & rnum <= recolonisation)] <- "+"
    x_new$cells[which(x_old$cells == "+"  & rnum <= death)] <- "0"
    x_new$cells[which(x_old$cells == "0"  & rnum > recolonisation & rnum <= recolonisation+degradation)] <- "-"
    x_new$cells[which(x_old$cells == "-"   & rnum <= regeneration)] <- "0"

    # 5- store x_new as next x_old

    x_old <- x_new

  }

  ## end of single update call
  return(x_new)
}


## --------------------
## --------------------
## --------------------
## This file contains functions for Adaptive Penalized Tensor Decomposition.
## Most function are only suitable for three dimension tensor. For higher-order tensor, codes should be modified adaptively.
## --------------------
## --------------------
## --------------------

## --------------------
## Generate tensor given d u v w.
## d @list
## u @matrix
## v @matrix
## w @matrix
## --------------------
generate_tensor <- function(d, u, v, w) {
    if (length(d) == 1) {
        u <- t(matrix(as.numeric(u)))
        v <- t(matrix(as.numeric(v)))
        w <- t(matrix(as.numeric(w)))
        make_list <- list("mat" = t(u), "mat2" = t(v), "mat3" = t(w))
        aux <- ttl(as.tensor(array(1, c(1, 1, 1))), make_list, ms = c(1, 2, 3))
        return(d * aux)
    } else {
        temp <- NULL
        for (k0 in 1:length(d)) {
            make_list <- list("mat" = as.matrix(u[k0, ]), "mat2" = as.matrix(v[k0, ]), "mat3" = as.matrix(w[k0, ]))
            aux <- ttl(as.tensor(array(1, c(1, 1, 1))), make_list, ms = c(1, 2, 3))
            if (is.null(temp)) {
                temp <- as.tensor(array(0, dim(aux)))
            }
            temp <- temp + d[k0] * aux
        }
        return(temp)
    }
}

## --------------------
## Generate arbitrary penalty matrix.
## --------------------
create_D <- function(x, ord = 2, gamma = 1) {
    D <- list()
    if (is.null(nrow(x))) {
        m <- 1
        n <- length(x)
    } else {
        m <- nrow(x)
        n <- ncol(x)
    }
    for (i in 1:m) {
        D[[i]] <- diag(1 / abs(as.numeric(diff(diag(n), differences = ord) %*% as.matrix(x[i, ])))^gamma) %*% diff(diag(n), differences = ord)
    }
    return(D)
}

## --------------------
## Generate sequences on log scale.
## --------------------
lseq <- function(from, to, length, decreasing = FALSE) {
    stopifnot(from > 0)
    out <- 10^(seq(log10(from), log10(to), length.out = length))
    out <- out[order(out, decreasing = decreasing)]
    return(out)
}

## --------------------
## Calculate the mse if forecasting method is GAM, which is used in cross-validation function to select the best tuning parameters.
## --------------------
cal_pmse_gam_adj <- function(k, year, bs = "tp", return_pred = FALSE) {
    u <- tempresult[[k]]$u
    v <- tempresult[[k]]$v
    w <- tempresult[[k]]$w
    w_pred <- matrix(NA, nrow = num_components, ncol = year)

    for (k0 in 1:num_components) {
        model1 <- gam(y ~ s(x, bs = bs), data = data.frame(y = w[k0, ], x = seq(1, length(w[1, ]))))
        w_pred[k0, ] <- predict(model1, newdata = data.frame(x = seq((length(w[k0, ]) + 1), (length(w[k0, ]) + year))))
    }

    aux <- 0
    for (k0 in 1:num_components) {
        lizt <- list("mat1" = t(t(u[k0, ])), "mat2" = t(t(v[k0, ])), "mat3" = t(t(w_pred[k0, ])))
        # ggg<-as.array(g[k0])
        # dim(ggg)<-c(1,1,1)
        aux1 <- ttl(as.tensor(array(1, c(1, 1, 1))), lizt, ms = c(1, 2, 3))
        aux <- aux + tempresult[[k]]$d[k0] * aux1
    }
    # convert it back
    aux <- aux * sd1[[k]] + as.tensor(array(mean1[[k]], dim(aux)))

    e <- sqrt(fnorm(aux - V1[[k]])^2)
    if (!return_pred) {
          return(e)
      }
    if (return_pred) {
          return(list(e = e, w_pred = w_pred))
      }
}

## --------------------
## Calculate the mse if forecasting method is ARIMA, which is used in cross-validation function to select the best tuning parameters.
## --------------------
cal_pmse_arima_adj <- function(k, year, return_pred = FALSE) {
    u <- tempresult[[k]]$u
    v <- tempresult[[k]]$v
    w <- tempresult[[k]]$w
    w_pred <- matrix(NA, nrow = num_components, ncol = year)

    for (k0 in 1:num_components) {
        w_pred1 <- rwf(as.matrix(w)[k0, ], h = year, drift = TRUE)
        w_pred[k0, ] <- as.data.frame(w_pred1)[, 1]
    }

    aux <- 0
    for (k0 in 1:num_components) {
        lizt <- list("mat1" = t(t(u[k0, ])), "mat2" = t(t(v[k0, ])), "mat3" = t(t(w_pred[k0, ])))
        # ggg<-as.array(g[k0])
        # dim(ggg)<-c(1,1,1)
        aux1 <- ttl(as.tensor(array(1, c(1, 1, 1))), lizt, ms = c(1, 2, 3))
        aux <- aux + tempresult[[k]]$d[k0] * aux1
    }

    #### convert back
    aux <- aux * sd1[[k]] + as.tensor(array(mean1[[k]], dim(aux)))

    e <- sqrt(fnorm(aux - V1[[k]])^2)
    if (!return_pred) {
          return(e)
      }
    if (return_pred) {
          return(list(e = e, w_pred = w_pred))
      }
}

## --------------------
## Calculate the mse if forecasting method is Linear extrapolation, which is used in cross-validation function to select the best tuning parameters.
## --------------------
cal_pmse_linearextra_adj <- function(k, year, return_pred = FALSE) {
    u <- tempresult[[k]]$u
    v <- tempresult[[k]]$v
    w <- tempresult[[k]]$w
    w_pred <- matrix(NA, nrow = num_components, ncol = year)

    for (k0 in 1:num_components) {
        w_pred[k0, ] <- approxExtrap(x = seq(1, length(w[1, ])), y = w[k0, ], xout = seq(length(w[k0, ]) + 1, by = 1, length = year))$y
    }

    aux <- 0
    for (k0 in 1:num_components) {
        lizt <- list("mat1" = t(t(u[k0, ])), "mat2" = t(t(v[k0, ])), "mat3" = t(t(w_pred[k0, ])))
        # ggg<-as.array(g[k0])
        # dim(ggg)<-c(1,1,1)
        aux1 <- ttl(as.tensor(array(1, c(1, 1, 1))), lizt, ms = c(1, 2, 3))
        aux <- aux + tempresult[[k]]$d[k0] * aux1
    }

    #### convert back
    aux <- aux * sd1[[k]] + as.tensor(array(mean1[[k]], dim(aux)))

    e <- sqrt(fnorm(aux - V1[[k]])^2)
    if (!return_pred) {
          return(e)
      }
    if (return_pred) {
          return(list(e = e, w_pred = w_pred))
      }
}

## --------------------
## Penalized Tensor Decomposition(trend-filtering) for single factor.
## --------------------
PTD_tf <- function(tnsr, c1, c2, c3, Niter = 1000, tol = 1e-5, ku, kv, kw, u_init = NULL, v_init = NULL, w_init = NULL) {
    # tuning parameters can be 0
    # trendfiltering
    # penalty matrix D is defined by default
    if (is.null(u_init)) {
        temp <- gen_startvals(1, tnsr = tnsr)
        u_init <- temp$u
        v_init <- temp$v
        w_init <- temp$w
    }
    counter <- 0
    diff <- 10
    cw_u <- u_init
    cw_v <- v_init
    cw_w <- w_init
    u <- t(matrix(as.numeric(u_init)))
    v <- t(matrix(as.numeric(v_init)))
    w <- t(matrix(as.numeric(w_init)))

    while (counter < Niter & diff > tol) {

        # update w
        lizt <- list("mat2" = u, "mat3" = v)
        w <- ttl(tnsr, lizt, ms = c(1, 2))
        if (c3 != 0) {
            w <- glmgen::trendfilter(as.vector(w@data), k = kw, family = "gaussian", method = "admm", lambda = c3)
            w <- predict(w, lambda = c3)
            w <- t(as.matrix(w))
            w <- w / norm(w, "F")
        } else {
            w <- t(as.matrix(w@data))
            w <- w / norm(w, "F")
        }

        # update u
        lizt <- list("mat2" = v, "mat3" = w)
        u <- ttl(tnsr, lizt, ms = c(2, 3))
        if (c1 != 0) {
            u <- glmgen::trendfilter(as.vector(u@data), k = ku, family = "gaussian", method = "admm", lambda = c1)
            u <- predict(u, lambda = c1)
            u <- t(as.matrix(u))
            u <- u / norm(u, "F")
        } else {
            u <- t(as.matrix(u@data))
            u <- u / norm(u, "F")
        }

        # update v
        lizt <- list("mat2" = u, "mat3" = w)
        v <- ttl(tnsr, lizt, ms = c(1, 3))
        if (c2 != 0) {
            v <- glmgen::trendfilter(as.vector(v@data), k = kv, family = "gaussian", method = "admm", lambda = c2)
            v <- predict(v, lambda = c2)
            v <- t(as.matrix(v))
            v <- v / norm(v, "F")
        } else {
            v <- t(as.matrix(v@data))
            v <- v / norm(v, "F")
        }

        diff <- mean(c(as.numeric(abs(cw_u - u)), as.numeric(abs(cw_v - v)), as.numeric(abs(cw_w - w))))
        cw_u <- u
        cw_v <- v
        cw_w <- w
        counter <- counter + 1
    }

    lizt <- list("mat1" = u, "mat2" = v, "mat3" = w)
    d <- ttl(tnsr, lizt, ms = c(1, 2, 3))

    return(list(u = u, v = v, w = w, d = as.numeric(d@data)))
}

## --------------------
## Penalized Tensor Decomposition(trend-filtering) for multiple factors.
## --------------------
multiple_tf_simple <- function(tnsr, num_components, d_hat = NULL, u_init = NULL, v_init = NULL, w_init = NULL, c1 = NULL, c2 = NULL, c3 = NULL, Niter = 1500, tol = 1e-05, ku, kv, kw, trace = TRUE) {
    ## specific tuning parameters
    if (is.null(u_init)) {
        temp <- gen_startvals(num_components, tnsr = tnsr)
        u_init <- temp$u
        v_init <- temp$v
        w_init <- temp$w
    }
    if (is.null(c1)) {
          c1 <- rep(0, num_components)
      }
    if (is.null(c2)) {
          c2 <- rep(0, num_components)
      }
    if (is.null(c3)) {
          c3 <- rep(0, num_components)
      }

    if (identical(d_hat, rep(0, num_components)) | is.null(d_hat)) {
        for (j in 1:num_components) {
            d_hat[j] <- product(tnsr, c(1, 2, 3), u_init[j, ], v_init[j, ], w_init[j, ])
        }
    }

    Y <- as.tensor(array(0, dim(tnsr)))

    for (j in 1:num_components) {
        lizt <- list("mat" = as.matrix(u_init[j, ]), "mat2" = as.matrix(v_init[j, ]), "mat3" = as.matrix(w_init[j, ]))
        aux <- ttl(as.tensor(array(1, c(1, 1, 1))), lizt, ms = c(1, 2, 3))

        Y <- Y + d_hat[j] * aux
    } ##  closing for j

    ################## 333
    counter <- 0
    diff <- 10
    cw_u <- u_init
    cw_v <- v_init
    cw_w <- w_init
    ######

    while (diff > tol & counter < Niter) {
        for (j in 1:num_components)
        {
            lizt <- list("mat" = as.matrix(u_init[j, ]), "mat2" = as.matrix(v_init[j, ]), "mat3" = as.matrix(w_init[j, ]))
            aux <- ttl(as.tensor(array(1, c(1, 1, 1))), lizt, ms = c(1, 2, 3))

            Y <- Y - d_hat[j] * aux
            ##############################################

            Z <- tnsr - Y

            # if(iter==1)
            #
            temp <- PTD_tf(Z, c1[j], c2[j], c3[j], Niter = Niter, tol = tol, ku = ku, kv = kv, kw = kw, u_init = u_init[j, , drop = FALSE], v_init = v_init[j, , drop = FALSE], w_init = w_init[j, , drop = FALSE])

            ##############################################
            u_init[j, ] <- as.vector(temp$u)
            v_init[j, ] <- as.vector(temp$v)
            w_init[j, ] <- as.vector(temp$w)
            d_hat[j] <- product(Z, c(1, 2, 3), u_init[j, ], v_init[j, ], w_init[j, ])

            lizt <- list("mat" = as.matrix(u_init[j, ]), "mat2" = as.matrix(v_init[j, ]), "mat3" = as.matrix(w_init[j, ]))
            aux <- ttl(as.tensor(array(1, c(1, 1, 1))), lizt, ms = c(1, 2, 3))

            Y <- Y + d_hat[j] * aux
        }

        diff <- mean(c(as.numeric(abs(u_init - cw_u)), as.numeric(abs(v_init - cw_v)), as.numeric(abs(w_init - cw_w))))
        if (trace) {
              message("Iteration: ", counter, "\t Difference: ", round(diff, 5))
          }

        cw_u <- u_init
        cw_v <- v_init
        cw_w <- w_init
        counter <- counter + 1
    }

    out <- list(u = u_init, v = v_init, w = w_init, d = d_hat, diff = diff)
    return(out)
}

## --------------------
## Adaptive Penalized Tensor Decomposition for single factor.
## --------------------
PTD_D <- function(tnsr, c1, c2, c3, Niter = 1500, tol = 1e-5, Du, Dv, Dw, u_init, v_init, w_init) {
    # tuning parameters can be 0
    # Arbitrary penalty matrix
    # init must be unpenalized results
    counter <- 0
    diff <- 10

    u <- cw_u <- u_init
    v <- cw_v <- v_init
    w <- cw_w <- w_init

    while (counter < Niter & diff > tol) {

        # update w
        lizt <- list("mat2" = u, "mat3" = v)
        w <- ttl(tnsr, lizt, ms = c(1, 2))
        if (c3 != 0) {
            w <- genlasso(as.vector(w@data), D = Dw)
            w <- predict(w, lambda = c3)$fit
            w <- t(as.matrix(w))
            w <- w / norm(w, "F")
        } else {
            lizt <- list("mat2" = u, "mat3" = v)
            w <- ttl(tnsr, lizt, ms = c(1, 2))
            w <- t(as.matrix(w@data))
            w <- w / norm(w, "F")
        }

        # update u
        lizt <- list("mat2" = v, "mat3" = w)
        u <- ttl(tnsr, lizt, ms = c(2, 3))
        if (c1 != 0) {
            u <- genlasso(as.vector(u@data), D = Du)
            u <- predict(u, lambda = c3)$fit
            u <- t(as.matrix(u))
            u <- u / norm(u, "F")
        } else {
            lizt <- list("mat2" = v, "mat3" = w)
            u <- ttl(tnsr, lizt, ms = c(2, 3))
            u <- t(as.matrix(u@data))
            u <- u / norm(u, "F")
        }

        # update v
        lizt <- list("mat2" = u, "mat3" = w)
        v <- ttl(tnsr, lizt, ms = c(1, 3))
        if (c2 != 0) {
            v <- genlasso(as.vector(v@data), D = Dv)
            v <- predict(v, lambda = c2)$fit
            v <- t(as.matrix(v))
            v <- v / norm(v, "F")
        } else {
            lizt <- list("mat2" = u, "mat3" = w)
            v <- ttl(tnsr, lizt, ms = c(1, 3))
            v <- t(as.matrix(v@data))
            v <- v / norm(v, "F")
        }

        diff <- mean(c(as.numeric(abs(cw_u - u)), as.numeric(abs(cw_v - v)), as.numeric(abs(cw_w - w))))
        cw_u <- u
        cw_v <- v
        cw_w <- w
        counter <- counter + 1
    }

    lizt <- list("mat1" = u, "mat2" = v, "mat3" = w)
    d <- ttl(tnsr, lizt, ms = c(1, 2, 3))

    return(list(u = u, v = v, w = w, d = as.numeric(d@data)))
}

## --------------------
## Adaptive Penalized Tensor Decomposition for single factor.
## --------------------
multiple_D_adaptive <- function(tnsr, num_components, d_hat = NULL, u_init, v_init, w_init, c1 = NULL, c2 = NULL, c3 = NULL, Niter = 15, tol = 1e-05, Du, Dv, Dw, trace = TRUE)
                                # tuning parameters for each dimension are the same
                                # inital values cant be null
                                # Dv and Dw should be a list of matrices
# to save computation time temp , only run 15 times
{
    ## specific tuning parameters
    if (!is.list(Du)) {
        Du <- list(Du)
    }
    if (!is.list(Dv)) {
        Dv <- list(Dv)
    }
    if (!is.list(Dw)) {
        Dw <- list(Dw)
    }
    if (length(Dv) != num_components & length(Dw) != num_components & length(Du) != num_components) {
        return(print("wrong penalty matrix"))
    }
    if (is.null(d_hat)) {
        for (j in 1:num_components) {
            d_hat[j] <- product(tnsr, c(1, 2, 3), u_init[j, ], v_init[j, ], w_init[j, ])
        }
    }
    Y <- as.tensor(array(0, dim(tnsr)))

    for (j in 1:num_components) {
        lizt <- list("mat" = as.matrix(u_init[j, ]), "mat2" = as.matrix(v_init[j, ]), "mat3" = as.matrix(w_init[j, ]))
        aux <- ttl(as.tensor(array(1, c(1, 1, 1))), lizt, ms = c(1, 2, 3))

        Y <- Y + d_hat[j] * aux
    } ##  closing for j

    ################## 333
    cw_u <- u_init
    cw_v <- v_init
    cw_w <- w_init
    counter <- 0
    diff <- 10

    ######

    while (diff > tol & counter < Niter) {
        for (j in 1:num_components)
        {
            lizt <- list("mat" = as.matrix(u_init[j, ]), "mat2" = as.matrix(v_init[j, ]), "mat3" = as.matrix(w_init[j, ]))
            aux <- ttl(as.tensor(array(1, c(1, 1, 1))), lizt, ms = c(1, 2, 3))

            Y <- Y - d_hat[j] * aux
            ##############################################

            Z <- tnsr - Y

            # if(iter==1)
            #
            temp <- PTD_D(Z, c1[j], c2[j], c3[j],
                Niter = Niter, tol = tol, Du = Du[[j]], Dv = Dv[[j]], Dw = Dw[[j]],
                u_init = u_init[j, , drop = FALSE], v_init = v_init[j, , drop = FALSE], w = w_init[j, , drop = FALSE]
            )

            ##############################################
            u_init[j, ] <- as.vector(temp$u)
            v_init[j, ] <- as.vector(temp$v)
            w_init[j, ] <- as.vector(temp$w)
            d_hat[j] <- temp$d

            lizt <- list("mat" = as.matrix(u_init[j, ]), "mat2" = as.matrix(v_init[j, ]), "mat3" = as.matrix(w_init[j, ]))
            aux <- ttl(as.tensor(array(1, c(1, 1, 1))), lizt, ms = c(1, 2, 3))

            Y <- Y + d_hat[j] * aux
        }

        diff <- mean(c(as.numeric(abs(u_init - cw_u)), as.numeric(abs(v_init - cw_v)), as.numeric(abs(w_init - cw_w))))
        if (trace) {
              message("Iteration: ", counter, "\t Difference: ", round(diff, 5))
          }

        cw_u <- u_init
        cw_v <- v_init
        cw_w <- w_init
        counter <- counter + 1
    }

    out <- list(u = u_init, v = v_init, w = w_init, d = d_hat)
    return(out)
}

## --------------------
## Prediction based on ATPD result.
## Forecasting method is GAM.
## --------------------
gam_predict <- function(result, year, bs = "tp") {
    u <- result$u
    v <- result$v
    w <- result$w
    d <- result$d
    w_pred <- matrix(NA, nrow = nrow(u), ncol = year)
    num_components <- length(d)
    for (k0 in 1:num_components) {
        model1 <- mgcv::gam(y ~ s(x, bs = bs), data = data.frame(y = w[k0, ], x = seq(1, length(w[1, ]))))
        w_pred[k0, ] <- predict(model1, newdata = data.frame(x = seq((length(w[k0, ]) + 1), (length(w[k0, ]) + year))))
    }

    aux <- 0
    for (k0 in 1:num_components) {
        lizt <- list("mat1" = t(t(u[k0, ])), "mat2" = t(t(v[k0, ])), "mat3" = t(t(w_pred[k0, ])))
        # ggg<-as.array(g[k0])
        # dim(ggg)<-c(1,1,1)
        aux1 <- ttl(as.tensor(array(1, c(1, 1, 1))), lizt, ms = c(1, 2, 3))
        aux <- aux + d[k0] * aux1
    }
    return(aux)
}

## --------------------
## Prediction based on ATPD result.
## Forecasting method is ARIMA.
## --------------------
arima_predict <- function(result, year) {
    u <- result$u
    v <- result$v
    w <- result$w
    d <- result$d
    w_pred <- matrix(NA, nrow = nrow(u), ncol = year)
    num_components <- length(d)

    for (k0 in 1:num_components) {
        w_pred1 <- rwf(as.matrix(w)[k0, ], h = year, drift = TRUE)
        w_pred[k0, ] <- as.data.frame(w_pred1)[, 1]
    }

    aux <- 0
    for (k0 in 1:num_components) {
        lizt <- list("mat1" = t(t(u[k0, ])), "mat2" = t(t(v[k0, ])), "mat3" = t(t(w_pred[k0, ])))
        # ggg<-as.array(g[k0])
        # dim(ggg)<-c(1,1,1)
        aux1 <- ttl(as.tensor(array(1, c(1, 1, 1))), lizt, ms = c(1, 2, 3))
        aux <- aux + d[k0] * aux1
    }

    return(aux)
}

## --------------------
## Prediction based on ATPD result.
## Forecasting method is Linear Extrapolation.
## --------------------
linextrap_predict <- function(result, year) {
    u <- result$u
    v <- result$v
    w <- result$w
    d <- result$d
    w_pred <- matrix(NA, nrow = nrow(u), ncol = year)
    num_components <- length(d)

    for (k0 in 1:num_components) {
        w_pred[k0, ] <- approxExtrap(x = seq(1, length(w[1, ])), y = w[k0, ], xout = seq(length(w[k0, ]) + 1, by = 1, length = year))$y
    }

    aux <- 0
    for (k0 in 1:num_components) {
        lizt <- list("mat1" = t(t(u[k0, ])), "mat2" = t(t(v[k0, ])), "mat3" = t(t(w_pred[k0, ])))
        # ggg<-as.array(g[k0])
        # dim(ggg)<-c(1,1,1)
        aux1 <- ttl(as.tensor(array(1, c(1, 1, 1))), lizt, ms = c(1, 2, 3))
        aux <- aux + d[k0] * aux1
    }

    return(aux)
}


library(tidyverse)
library(BBmisc)
library(caTools)

simulate_viral_load <- function(ids, ct_alpha, ct_beta, ct_max, ct_min, e_alpha, e_beta, e_max, e_min, rct, gamma_shape=2, gamma_scale=2)
{
	n <- nrow(ids)
	ct <- rbeta(n, ct_alpha, ct_beta) * (ct_max - ct_min) + ct_min
	e <- rbeta(n, e_alpha, e_beta) * (e_max-e_min) + e_min
	vl <- rct / (1+e)^ct
	ids$id_E <- e
	ids$id_ct <- ct
	ids$id_vl <- vl

	vlshape <- tibble(
		t=seq(0,21,length.out=2101),
		vlshape=dgamma(t, shape=gamma_shape, scale=gamma_scale)
	)
	vlshape$vlshape <- vlshape$vlshape / max(vlshape$vlshape)
	len <- nrow(subset(vlshape, t <= 1))
	i1 <- sample(1:(which(vlshape$t == 18)[1]), n, replace=TRUE)
	i2 <- i1 + len * 3
	max_vl <- rct / (1 + e)^ct

	vl1 <- max_vl * vlshape$vlshape[i1]
	vl2 <- max_vl * vlshape$vlshape[i2]

	ct1 <- log(rct/vl1) / log(1+e)
	ct2 <- log(rct/vl2) / log(1+e)

	ids$id_ct1 <- ct1
	ids$id_ct2 <- ct2
	ids$id_vl1 <- vl1
	ids$id_vl2 <- vl2
	return(ids)
}

simulate_infection <- function(ids, prevalence, spread, containment)
{
	# Sample initial individuals
	dat <- tibble(
		id = sample(ids$id, nrow(ids) * prevalence, replace=FALSE),
		spread = rpois(length(id), spread)
		) %>%
		inner_join(ids, ., by="id")

	new_infecteds <- lapply(1:nrow(dat), function(i)
	{
		loc <- sample(1:3, dat$spread[i], replace=TRUE, prob=containment)
		return(c(
			sample(subset(ids, circle == dat$circle[i])$id, min(dat$count[i], sum(loc==1)), replace=FALSE),
			sample(subset(ids, location == dat$location[i])$id, sum(loc==2), replace=FALSE),
			sample(ids$id, sum(loc==3), replace=FALSE)
		))
	}) %>% unlist %>% unique
	ids$infected <- ids$id %in% c(dat$id, new_infecteds)
	ids <- ids %>%
		group_by(location) %>%
		mutate(location_outbreak = sum(infected >= 2)) %>%
		ungroup()

	# remove vl for anyone not infected
	ids$id_vl[ids$infected==FALSE] <- 0
	ids$id_vl1[ids$infected==FALSE] <- 0
	ids$id_vl2[ids$infected==FALSE] <- 0
	# ids$cvl[ids$infected==FALSE] <- 0

	return(ids)
}

allocate_pools <- function(ids, pool_size, random=FALSE)
{
	random_pools <- function(x, pool_size)
	{
		npool <- ceiling(length(x) / pool_size)
		rep(1:npool, each=pool_size)[1:length(x)] %>% sample
	}

	if(random)
	{
		ids$assay_pool <- random_pools(1:nrow(ids), pool_size)
		ids <- ids %>%
			group_by(assay_pool) %>%
			mutate(poolsize=n()) %>%
			ungroup()
		return(ids)
	}

	# First break up large circles
	temp1 <- subset(ids, count > pool_size) %>%
		group_by(circle) %>%
		mutate(
			poolbreak = cut(1:n(), ceiling(n()/pool_size)) %>% as.numeric(),
			pool = paste0(circle, "-", poolbreak)
		) %>% ungroup()
	temp2 <- subset(ids, count <= pool_size) %>%
		mutate(
			poolbreak = 1,
			pool = paste0(circle, "-", poolbreak)
		)
	ids <- bind_rows(temp1, temp2)
	dat <- ids %>%
		group_by(pool) %>%
		summarise(poolsize=n(), .groups="drop")
	# Note that `cut` tries to make even cuts. this might not be optimal
	# Use bin packing algorithm to assign to pools
	dat$assay_pool <- BBmisc::binPack(dat$poolsize, pool_size)
	ids <- inner_join(ids, dat, by="pool") %>%
		group_by(assay_pool) %>%
		mutate(poolsize = n()) %>% ungroup()
	return(ids)
}

assay_sensitivity_quad <- function(dilution, baseline, beta)
{
	vec <- -beta * (dilution-1)^2 + baseline
	vec[vec < 0] <- 0
	vec
}

assay_sensitivity_decay <- function(dilution, baseline, beta)
{
	exp(-(dilution-1) * beta) * baseline
}

assay_sensitivity_sigmoid <- function(dilution)
{
	1 / (1 + exp(-dilution/4+4)) * -1 + 1
}

assay_ppv <- function(sensitivity, prevalence, specificity)
{
	sensitivity * prevalence / (sensitivity * prevalence + (1-specificity) * (1-prevalence))
}

lfd_prob <- function(x, asym, xmid, scal)
{
	asym / (1 + exp((xmid - x)/scal))
}

simulate_testing <- function(ids, ctthresh, rct, pcr_fp, lfd_Asym, lfd_xmid, lfd_scal, lfd_fp)
{
	ids$id_detected <- ids$id_ct < ctthresh
	ids$id_detected1 <- ids$id_ct1 < ctthresh
	ids$id_detected2 <- ids$id_ct2 < ctthresh
	ids$id_detected[!ids$infected] <- rbinom(sum(!ids$infected), 1, pcr_fp)
	ids$id_detected1[!ids$infected] <- rbinom(sum(!ids$infected), 1, pcr_fp)
	ids$id_detected2[!ids$infected] <- rbinom(sum(!ids$infected), 1, pcr_fp)
	ids$id_lfd_detected <- rbinom(nrow(ids), 1, lfd_prob(ids$id_ct, lfd_Asym, lfd_xmid, lfd_scal))
	ids$id_lfd_detected1 <- rbinom(nrow(ids), 1, lfd_prob(ids$id_ct1, lfd_Asym, lfd_xmid, lfd_scal))
	ids$id_lfd_detected2 <- rbinom(nrow(ids), 1, lfd_prob(ids$id_ct2, lfd_Asym, lfd_xmid, lfd_scal))
	ids$id_lfd_detected[!ids$infected] <- rbinom(sum(!ids$infected), 1, lfd_fp)
	ids$id_lfd_detected1[!ids$infected] <- rbinom(sum(!ids$infected), 1, lfd_fp)
	ids$id_lfd_detected2[!ids$infected] <- rbinom(sum(!ids$infected), 1, lfd_fp)

	detected_pools <- ids %>% group_by(assay_pool) %>%
		summarise(
			pool_ninfected = sum(infected),
			pool_infected = pool_ninfected > 0,
			pool_E = min(id_E),
			pool_vl = sum(id_vl),
			pool_vl1 = sum(id_vl1),
			pool_vl2 = sum(id_vl2),
			pool_Rn = pool_vl / n() * (1 + pool_E)^ctthresh,
			pool_Rn1 = pool_vl1 / n() * (1 + pool_E)^ctthresh,
			pool_Rn2 = pool_vl2 / n() * (1 + pool_E)^ctthresh,
			pool_ct = log(rct / pool_vl) / log((1 + pool_E)),
			pool_ct1 = log(rct / pool_vl1) / log((1 + pool_E)),
			pool_ct2 = log(rct / pool_vl2) / log((1 + pool_E)),
			pool_detected = pool_ct < ctthresh,
			pool_detected1 = pool_ct1 < ctthresh,
			pool_detected2 = pool_ct2 < ctthresh,
			.groups="drop"
		)
	detected_pools$pool_detected[!detected_pools$pool_infected] <- rbinom(sum(!detected_pools$pool_infected), 1, pcr_fp)
	detected_pools$pool_detected1[!detected_pools$pool_infected] <- rbinom(sum(!detected_pools$pool_infected), 1, pcr_fp)
	detected_pools$pool_detected2[!detected_pools$pool_infected] <- rbinom(sum(!detected_pools$pool_infected), 1, pcr_fp)
	ids <- inner_join(ids, detected_pools, by="assay_pool")
	ids <- ids %>% group_by(circle) %>%
		mutate(
			circle_detected = any(pool_detected),
			circle_detected1 = any(pool_detected1),
			circle_detected2 = any(pool_detected2)
		)
	ids <- ids %>%
		group_by(location) %>%
		mutate(
			ind_outbreak_detected = sum(id_detected) > 2,
			pool_outbreak_detected = sum(pool_detected) > 2,
			poolfollowup_outbreak_detected = sum(pool_detected * id_detected) > 2
		) %>% ungroup()
	return(ids)
}

summarise_simulations <- function(ids, cost_samplingkit, cost_test, cost_lfd)
{
	npool <- length(unique(ids$assay_pool))
	nfollowup <- sum(ids$pool_detected)

	ob <- group_by(ids, location) %>%
		summarise(
			outbreak = any(location_outbreak),
			ind_outbreak_detected = any(ind_outbreak_detected),
			pool_outbreak_detected = any(pool_outbreak_detected),
			poolfollowup_outbreak_detected = any(poolfollowup_outbreak_detected),
			.groups="drop"
		)

	tibble(
		nstudents = nrow(ids),
		true_prevalence = sum(ids$infected) / nrow(ids),
		prevalence.ind_tests = sum(ids$id_detected) / nstudents,
		prevalence.ind_tests1 = sum(ids$id_detected1) / nstudents,
		prevalence.ind_tests2 = sum(ids$id_detected2) / nstudents,
		prevalence.pool_tests = sum(ids$pool_detected) / nstudents,
		prevalence.pool_tests1 = sum(ids$pool_detected1) / nstudents,
		prevalence.pool_tests2 = sum(ids$pool_detected2) / nstudents,
		prevalence.poolfollowup_tests = sum(ids$pool_detected & ids$id_detected) / nstudents,
		prevalence.poolfollowup_tests1 = sum(ids$pool_detected1 & ids$id_detected1) / nstudents,
		prevalence.poolfollowup_tests2 = sum(ids$pool_detected2 & ids$id_detected2) / nstudents,

		cost.ind_tests = nrow(ids) * cost_samplingkit + nrow(ids) * cost_test,
		cost.pool_tests = nrow(ids) * cost_samplingkit + npool * cost_test,
		cost.poolfollowup_tests = cost.pool_tests + nfollowup * cost_test,
		cost.lfd = nrow(ids) * cost_lfd,

		reagentuse.ind_tests = nrow(ids),
		reagentuse.pool_tests = npool,
		reagentuse.poolfollowup_tests = npool + nfollowup,

		sensitivity.ind_tests = sum(ids$infected & ids$id_detected) / sum(ids$infected),
		sensitivity.ind_tests1 = sum(ids$infected & ids$id_detected1) / sum(ids$infected),
		sensitivity.ind_tests2 = sum(ids$infected & ids$id_detected2) / sum(ids$infected),
		sensitivity.pool_tests = sum(ids$pool_detected * ids$infected) / sum(ids$infected),
		sensitivity.pool_tests1 = sum(ids$pool_detected1 * ids$infected) / sum(ids$infected),
		sensitivity.pool_tests2 = sum(ids$pool_detected2 * ids$infected) / sum(ids$infected),
		sensitivity.poolfollowup_tests = sum(ids$pool_detected * ids$infected * ids$id_detected) / sum(ids$infected),
		sensitivity.poolfollowup_tests1 = sum(ids$pool_detected1 * ids$infected * ids$id_detected1) / sum(ids$infected),
		sensitivity.poolfollowup_tests2 = sum(ids$pool_detected2 * ids$infected * ids$id_detected2) / sum(ids$infected),
		sensitivity.lfd = sum(ids$infected & ids$id_lfd_detected) / sum(ids$infected),
		sensitivity.lfd1 = sum(ids$infected & ids$id_lfd_detected1) / sum(ids$infected),
		sensitivity.lfd2 = sum(ids$infected & ids$id_lfd_detected2) / sum(ids$infected),
		sensitivity.lfd_ever = sum(ids$infected & (ids$id_lfd_detected1 | ids$id_lfd_detected2)) / sum(ids$infected),

		specificity.ind_tests = sum(!ids$id_detected & !ids$infected) / sum(!ids$infected),
		specificity.ind_tests1 = sum(!ids$id_detected1 & !ids$infected) / sum(!ids$infected),
		specificity.ind_tests2 = sum(!ids$id_detected2 & !ids$infected) / sum(!ids$infected),
		specificity.pool_tests = sum(!ids$pool_detected & !ids$infected) / sum(!ids$infected),
		specificity.pool_tests1 = sum(!ids$pool_detected1 & !ids$infected) / sum(!ids$infected),
		specificity.pool_tests2 = sum(!ids$pool_detected2 & !ids$infected) / sum(!ids$infected),
		specificity.poolfollowup_tests = 1 - sum(ids$pool_detected & ids$id_detected & !ids$infected) / sum(!ids$infected),
		specificity.poolfollowup_tests1 = 1 - sum(ids$pool_detected1 & ids$id_detected1 & !ids$infected) / sum(!ids$infected),
		specificity.poolfollowup_tests2 = 1 - sum(ids$pool_detected2 & ids$id_detected2 & !ids$infected) / sum(!ids$infected),
		specificity.lfd = sum(!ids$id_lfd_detected & !ids$infected) / sum(!ids$infected),
		specificity.lfd1 = sum(!ids$id_lfd_detected1 & !ids$infected) / sum(!ids$infected),
		specificity.lfd2 = sum(!ids$id_lfd_detected2 & !ids$infected) / sum(!ids$infected),
		specificity.lfd_ever = sum(!(ids$id_lfd_detected1 | ids$id_lfd_detected2) & !ids$infected) / sum(!ids$infected),

		sensitivity.outbreak.ind_tests = sum(ob$ind_outbreak_detected) / sum(ob$outbreak),
		sensitivity.outbreak.pool_outbreak_detected = sum(ob$pool_outbreak_detected) / sum(ob$outbreak),
		sensitivity.outbreak.poolfollowup_outbreak_detected = sum(ob$poolfollowup_outbreak_detected) / sum(ob$outbreak),

		specificity.outbreak.ind_tests = sum(!ob$ind_outbreak_detected & !ob$outbreak) / sum(!ob$outbreak),
		specificity.outbreak.pool_outbreak_detected = sum(!ob$pool_outbreak_detected & !ob$outbreak) / sum(!ob$outbreak),
		specificity.outbreak.poolfollowup_outbreak_detected = sum(!ob$poolfollowup_outbreak_detected & !ob$outbreak) / sum(!ob$outbreak)
	)
}

run_simulation <- function(ids, param, containment)
{
	stopifnot(nrow(param) == 1)
	stopifnot(param$containment %in% names(containment))
	cont <- containment[[param$containment]]

	ids <- simulate_viral_load(ids, param$ct_alpha, param$ct_beta, param$ct_max, param$ct_min, param$e_alpha, param$e_beta, param$e_max, param$e_min, param$rct)
	ids <- simulate_infection(ids, param$prevalence, spread=param$spread, cont)
	ids <- allocate_pools(ids, param$pool_size, random=param$random_pooling)
	ids <- simulate_testing(ids, param$ctthresh, param$rct, param$pcr_fp, param$lfd_Asym, param$lfd_xmid, param$lfd_scal, param$lfd_fp)
	res <- summarise_simulations(ids, param$cost_samplingkit, param$cost_test, param$lfd_cost)
	return(bind_cols(param, res))
}


## by peter m crosta: pmcrosta at gmail
### Functions for oaxac decompositions in R given sample means and regression coefficients as inputs.

setwd('~/CSVs')
# read in means file
meansraw <- read.csv('means.csv', header=T, stringsAsFactors=F)

# read in coeffs file
coeffsraw <- read.csv('coeffs.csv', header=T, stringsAsFactors=F)

meansraw <- meansraw[meansraw$X!="",]
coeffsraw <- coeffsraw[coeffsraw$X!="",]

coefnames <- coeffsraw$X
sumnames <- meansraw$X

coefcol <- colnames(coeffsraw)
sumcol <- colnames(meansraw)

oaxaca <- function(mods) UseMethod("oaxaca")

oaxaca.default <- function(mods) {
    ## Input is a charactervector. Likely taken from a list
    mod1 <- mods[1]
    mod2 <- mods[2]
    ### get model coefficients
    m1 <- as.numeric(coeffsraw[, which(mod1==coefcol)])
    m2 <- as.numeric(coeffsraw[, which(mod2==coefcol)])

    names(m1)<-coefnames
    names(m2)<-coefnames

    m1 <- na.omit(m1)
    m2 <- na.omit(m2)

    ## get means
    s1 <- as.numeric(meansraw[, which(mod1==sumcol)])
    s2 <- as.numeric(meansraw[, which(mod2==sumcol)])

    names(s1)<-sumnames
    names(s2)<-sumnames

    s1 <- na.omit(s1)
    s2 <- na.omit(s2)

    if (substr(mod1,1,1)=="r") depvar <- "readdep" else if (substr(mod1,1,1)=="m") depvar <- "mathdep"

    k <- NROW(m1)
    k1 <- NROW(m2)
    if (k != k1) stop("models are not the same size")

    W <- list("W = 1 (Oaxaca, 1973)" = diag(1, k),
              "W = 0 (Blinder, 1973)" = diag(0, k),
              "W = 0.5 (Reimers 1983)" = diag(0.5, k))

    mnames <- names(m1)[-which(names(m1)=="Constant")]
    X1 <- c(s1[mnames], 1)
    X2 <- c(s2[mnames], 1)

    Q <- sapply(W, function(w) crossprod(X1 - X2, w %*% m1 + (diag(1, k) - w) %*% m2))
    U <- sapply(W, function(w) (crossprod(X1, diag(1, k) - w) + crossprod(X2, w)) %*% (m1 - m2))

    attcoeff <- c("_Iattdec_2", "_Iattdec_3", "_Iattdec_4", "_Iattdec_5", "_Iattdec_6", "_Iattdec_7", "_Iattdec_8", "_Iattdec_9", "_Iattdec_10")

    #Qa <- sapply(W, function(w) crossprod(X1['lnattend'] - X2['lnattend'], w[1] %*% m1['lnattend'] + (diag(1, 1) - w[1]) %*% m2['lnattend']))
    #Ua <- sapply(W, function(w) (crossprod(X1['lnattend'], diag(1, 1) - w[1]) + crossprod(X2['lnattend'], w[1])) %*% (m1['lnattend'] - m2['lnattend']))

    Qa <- Ua <- 0
    for (ii in attcoeff) {
        Qa <- Qa + sapply(W, function(w) crossprod(X1[ii] - X2[ii], w[1] %*% m1[ii] + (diag(1, 1) - w[1]) %*% m2[ii]))
        Ua <- Ua + sapply(W, function(w) (crossprod(X1[ii], diag(1, 1) - w[1]) + crossprod(X2[ii], w[1])) %*% (m1[ii] - m2[ii]))
    }

    KK<-length(X1)
    Qdetail <- sapply(W, function(w) sapply(1:KK, function(jj) crossprod(X1[jj] - X2[jj], w[1] %*% m1[jj] + (diag(1, 1) - w[1]) %*% m2[jj])))
    Udetail <- sapply(W, function(w) sapply(1:KK, function(jj) (crossprod(X1[jj], diag(1, 1) - w[1]) + crossprod(X2[jj], w[1])) %*% (m1[jj] - m2[jj])))
    rownames(Qdetail) <- rownames(Udetail) <- c(mnames, "Constant")

    # Var-covar matrices
    setwd('~/outregs')
    VB1 <- as.matrix(read.table(paste(mod1, 'mat.txt', sep='')))
    VB2 <- as.matrix(read.table(paste(mod2, 'mat.txt', sep='')))

    VX1 <- as.matrix(read.table(paste(mod1, 'cor.txt', sep='')))
    VX2 <- as.matrix(read.table(paste(mod2, 'cor.txt', sep='')))

    dep1 <- s1[depvar]
    dep2 <- s2[depvar]
    Ddep <- dep1-dep2

    ans <- list(R = Ddep,
              VR = crossprod(X1, VB1) %*% X1 + crossprod(m1, VX1) %*% m1 + sum(diag(VX1 %*% VB1)) +
              crossprod(X2, VB2) %*% X2 + crossprod(m2, VX2) %*% m2 + sum(diag(VX2 %*% VB2)))

    VQ <- sapply(W, function(w)
              sum(diag((VX1 + VX2) %*% (w %*% tcrossprod(VB1, w) + (diag(1, k) - w) %*% tcrossprod(VB2, diag(1, k) - w)))) +
              crossprod(X1 - X2, w %*% tcrossprod(VB1, w) + (diag(1, k) - w) %*% tcrossprod(VB2, diag(1, k) - w)) %*% (X1 - X2) +
              crossprod(w %*% m1 + (diag(1, k) - w) %*% m2, VX1 + VX2) %*% (w %*% m1 + (diag(1, k) - w) %*% m2))
    VU <- sapply(W, function(w)
              sum(diag((crossprod(diag(1, k) - w, VX1) %*% (diag(1, k) - w) + crossprod(w, VX2) %*% w) %*% (VB1 + VB2))) +
              crossprod(crossprod(diag(1, k) - w, X1) + crossprod(w, X2), VB1 + VB2) %*% (crossprod(diag(1, k) - w, X1) + crossprod(w, X2))+
              crossprod(m1 - m2, crossprod(diag(1, k) - w, VX1) %*% (diag(1, k) - w) + crossprod(w, VX2) %*% w) %*% (m1 - m2))

    ans$Q <- Q
    ans$U <- U
    ans$VQ <- VQ
    ans$VU <- VU
    ans$W <- W
    ans$Qa <- Qa
    ans$Ua <- Ua
    ans$call <- match.call()
    ans$mod1 <- mod1
    ans$mod2 <- mod2
    ans$Qdetail <- Qdetail
    ans$Udetail <- Udetail
    class(ans) <- "oaxaca"
    ans

}

print.oaxaca <- function(x) {
  se <- sqrt(x$VQ)
  zval <- x$Q / se
  decomp <- cbind(Explained = x$Q, StdErr = se, "z-value" = zval, "Pr(>|z|)" = 2*pnorm(-abs(zval)))
  cat("\nBlinder-Oaxaca decomposition\n\nCall:\n")
  print(x$call)
  cat(x$mod1, ' ', x$mod2, "\n")
  decomp <- matrix(nrow = 1, ncol = 4)
  decomp[1, c(1, 2)] <- c(x$R, sqrt(x$VR))
  decomp[1, 3] <- c(decomp[, 1] / decomp[, 2])
  decomp[1, 4] <- c(2 * pnorm(-abs(decomp[, 3])))
  colnames(decomp) <- c("Difference", "StdErr", "z-value", "Pr(>|z|)")
  rownames(decomp) <- "Mean"
  cat("\n")
  print(zapsmall(decomp))
  cat("\nLinear decomposition:\n")
  for (i in 1:3) {
    cat(paste("\nWeight: ", names(x$W[i]), "\n"))
    decomp <- cbind(Difference = c(x$Q[i], x$U[i]), StdErr = c(sqrt(x$VQ[i]), sqrt(x$VU[i])))
    decomp <- cbind(decomp, "z-value" = decomp[, 1] / decomp[, 2])
    decomp <- cbind(decomp, "Pr(>|z|)" = 2 * pnorm(-abs(decomp[, 3])))
    decomp <- cbind(decomp, c(x$Qa[i], x$Ua[i]))
    colnames(decomp) <- c("Difference", "StdErr", "z-value", "Pr(>|z|)", "attendance")
    rownames(decomp) <- c("Explained", "Unexplained")
    print(zapsmall(decomp))
  }
  cat("\n")
}

write.oaxaca <- function(x) {
    if (class(x) != "oaxaca") stop("Input needs to be of class oaxaca")
  #cat(x$mod1, ' ', x$mod2, "\n")
  decomp <- matrix(nrow = 3, ncol = 5)
  decomp[1, c(1, 2)] <- c(x$R, sqrt(x$VR))
  decomp[1, 3] <- c(decomp[1, 1] / decomp[1, 2])
  decomp[1, 4] <- c(2 * pnorm(-abs(decomp[1, 3])))
  colnames(decomp) <- c("Difference", "StdErr", "z-value", "Pr(>|z|)", paste(x$mod1, x$mod2, sep=''))
  if (substr(x$mod1,1,1)=="r") depvar <- "Reading" else if (substr(x$mod1,1,1)=="m") depvar <- "Math"
  rownames(decomp) <- c(depvar, "Explained", "Unexplained")
    i<-3
    decomp[2,1] <- x$Q[i]
    decomp[3,1] <- x$U[i]
    decomp[2,2] <- sqrt(x$VQ[i])
    decomp[3,2] <- sqrt(x$VU[i])
    decomp[2,3] <- decomp[2, 1] / decomp[2, 2]
    decomp[3,3] <- decomp[3, 1] / decomp[3, 2]
    decomp[2,4] <- 2 * pnorm(-abs(decomp[2, 3]))
    decomp[3,4] <- 2 * pnorm(-abs(decomp[3, 3]))
    decomp[2,5] <- x$Qa[i]
    decomp[3,5] <- x$Ua[i]
    zapsmall(decomp)
}

detail.oaxaca <- function(x, depth=6) {
  if (class(x) != "oaxaca") stop("Input needs to be of class oaxaca")
  ##only deal with Cotton/Reimers
  Qdet <- sort(x$Qdetail[,3])
  Udet <- sort(x$Udetail[,3])
  Qh <- head(Qdet, depth)
  Qt <- tail(Qdet, depth)
  Uh <- head(Udet, depth)
  Ut <- tail(Udet, depth)
  Qsum <- cbind(Qh, names(Qt), Qt)
  Usum <- cbind(Uh, names(Ut), Ut)
  ret.list <- list(Qsum, Usum)
}

### Below is code specific to the analysis
### which decompositions do we want to do?
### coefcol[-1] lists the options:
### math, reading
### pooled, fixed effects
### racedum1-5, econdum1-4
### remember: race: amerindian, asian, black, hispanic, white
### remember: econ: not poor, free lunch, reduced lunch, other dis

## For pooled and FE models: white-black, white-hispanic, hispanic-black
##              notpoor-free, notpoor-reduced, notpoor-other

## This becomes: 5-3, 5-4, 4-3; 1-2, 1-3, 1-4
grouprace <- list(
 c('mpa4racedum5C', 'mpa4racedum3C'),
 c('rpa4racedum5C', 'rpa4racedum3C'),
 c('mfa4racedum5C', 'mfa4racedum3C'),
 c('rfa4racedum5C', 'rfa4racedum3C'),

 c('mpa4racedum5C', 'mpa4racedum4C'),
 c('rpa4racedum5C', 'rpa4racedum4C'),
 c('mfa4racedum5C', 'mfa4racedum4C'),
 c('rfa4racedum5C', 'rfa4racedum4C'),

 c('mpa4racedum4C', 'mpa4racedum3C'),
 c('rpa4racedum4C', 'rpa4racedum3C'),
 c('mfa4racedum4C', 'mfa4racedum3C'),
 c('rfa4racedum4C', 'rfa4racedum3C'))

groupecon <- list(
 c('mpa4econdum1C', 'mpa4econdum2C'),
 c('rpa4econdum1C', 'rpa4econdum2C'),
 c('mfa4econdum1C', 'mfa4econdum2C'),
 c('rfa4econdum1C', 'rfa4econdum2C'),

 c('mpa4econdum1C', 'mpa4econdum3C'),
 c('rpa4econdum1C', 'rpa4econdum3C'),
 c('mfa4econdum1C', 'mfa4econdum3C'),
 c('rfa4econdum1C', 'rfa4econdum3C'),

 c('mpa4econdum1C', 'mpa4econdum4C'),
 c('rpa4econdum1C', 'rpa4econdum4C'),
 c('mfa4econdum1C', 'mfa4econdum4C'),
 c('rfa4econdum1C', 'rfa4econdum4C'))

oax.race<-lapply(grouprace, oaxaca)
oax.econ<-lapply(groupecon, oaxaca)

## oaxaca print out function
lapply(oax.race, write.oaxaca)
lapply(oax.econ, write.oaxaca)

## print out detailed decomposition
race.det <- lapply(oax.race, detail.oaxaca)
names(race.det) <- sapply(grouprace, function(x) paste(x, collapse=''))
race.det

econ.det <- lapply(oax.econ, detail.oaxaca)
names(econ.det) <- sapply(groupecon, function(x) paste(x, collapse=''))
econ.det


# This tool presents a new method to identify the vent location of tephra fall deposits
# based on thickness or maximum clast size measurements.
# It is temporarily named "svl", which is short for Source Vent Locator.
# This file includes the associated R functions and their notations.

###
# Since the presented method gives estimate on the vent location and prevailing wind
# direction, it can be used to produce the input for our method to model the thickness
# or maximum clast size distribution of
# tephra fall deposits (Yang and Bursik, 2016; https://vhub.org/resources/3957).
###
# Author: Qingyuan Yang, Marcus Bursik, and E. Bruce Pitman.
###
# License: GPL. Use at your own risk.
###
# The work was supported by National Science Foundation Hazard SEES grant number 1521855 to
# G. Valentine, M. Bursik, E.B. Pitman and A.K. Patra, and National Science Foundation
# DMS grant number 1621853 to A.K. Patra, M. Bursik, and E.B. Pitman.
###
# We appreciate your comments, suggestions, and feedback.
# Please feel free to contact us through vhub or email: qyang5@buffalo.edu
# Notes:
#	The functions below are the version of the method that is coupled with the semi-
#	empirical model proposed by Yang and Bursik (2016).

###### Functions ######
#################################Intermediate functions#################################
#################### Function name: sp_par_cal ####################
# Function to calculate downwind and cross wind distance of the input sample sites given known
# source vent and wind direction.
# The name "sp_par_cal" is short for "spatial parameter calculation".
# Input:
	# obs:
	#	n-by-3 matrix of x, y, z, indicating coordinates and measured thickness
	#	or maximum clast size of each sample site. x and y should be in utm coordinates,
	#	and z should be in millimeter with a minimum value of 1 mm.
	# sv:
	#	a vector of two elements (utm coordinates) indicating the assumed vent location.
	# wind_dir
	# 	a numeric value of assumed wind direction (from north clockwise).
# Output:
	# an n-by-7 matrix,
	# The 1st and 2nd columns:
	#	original coordinates of sample sites;
	# The 3-5th columns:
	#	total distance (dist), downwind (dd), and crosswind (cd) distance
	# 	with respect to the source vent (sv).
	# The 6th column:
	#	difference between total distance and downwind distance.
	# The 7th column:
	#	thickness or grain size measurement at each sample site.
	###### Brief description ######
	# This function uses basic vector/matrix multiplication in R to prepare the data for
	# calculating and fitting the objective function, GIVEN ASSUMED FIXED source
	# vent location and wind direction.
sp_par_cal <- function(obs, sv, wind_dir){
	sv = as.vector(sv)
	zr <- obs[,3]
	xy <- as.matrix(obs[,c(1,2)])
	nr <- nrow(obs)
	sv_matrix <- matrix(ncol=2, nrow=nr)
	sv_matrix[,1] <- rep(sv[1], nr)
	sv_matrix[,2] <- rep(sv[2], nr)
	unit_vector <- as.matrix(c(sin(wind_dir*pi/180),cos(wind_dir*pi/180)))
	rel_xy <- xy - sv_matrix
	dist <- (rel_xy[,1]^2 + rel_xy[,2]^2)^0.5
	dd <- (rel_xy) %*% (unit_vector)
	cd <- (dist^2 - dd^2)^0.5
	output <- matrix(nrow = nr, ncol=7)
	output[,1:2] <- xy
	output[,3:5] <- cbind(dist, dd, cd)
	output[,6] <- dist - dd
	output[,7] <- zr
	return(output)
}

#################### Function name: fi ####################
# Function to calculate the sum of squared residuals (calculated under log-scale)
# GIVEN FIXED source vent location and wind direction.
# The name "fi" is short for "fitting".
# Input:
	# obs, sv, and wind_dir:
	#	inputs for function "sp_par_cal".
	###### Output ######
	# ssr:
	#	a numeric value which is the sum of squared residuals (ssr) from the fitting.
	# Note:
	#	The method uses the semi-empirical model of Yang and Bursik (2016).
	#	Note the line with "***" on the right, which is used for the
	#	semi-empirical model proposed by Gonzalez-Mellado and De la Cruz-Reyna
	# 	(2010). It is not used here.
fi <- function(obs, sv, wind_dir){
	td <- as.data.frame(sp_par_cal(obs, sv, wind_dir))
		# Call the "sp_par_cal" function, and turn the output to data.frame.
	colnames(td) <- c("x","y","dist","dd","cd", "dif","zr")
		# Name the columns.
	td$z = log10(td$zr)
		# Transform the thickness into log-scale.
	## Fitting and calculate the ssr
#       fit.res <- lm(z~dif+log(dist),data=td)		# *** Case 1: power-law model, not used here.
        fit.res <- lm(z~dist+dd,data=td)		# Case 2: exponential model.
      	ssr <- sum(fit.res$residuals^2)			# Calculate the ssr.
	return(ssr)
}

#################### Function name: fi2 ####################
# This function is basically identical to the function "fi".
# The only difference is the output.
# It is called when the vent position is found.
# In addition to "ssr" in function "fi", it also returns the fitted coefficients
# of the semi-empirical model and the associated r-square value.
# With these values, we could have a better understanding on the predicted result.
# Input
	# Identical to the inputs for function "fi"
# Output
	# a vector with five elements including:
	# ssr: sum of squared residuals;
	# fit.res$coefficients: fitted coefficients (three values) of the semi-empirical model given
	# 	fixed vent location and wind direction;
	# summary(fit.res)$r.squared): r-squared value of the fitted result given
	#	fixed vent location and wind direction (a numeric value).
fi2 <- function(obs, sv, wind_dir){
	td <- as.data.frame(sp_par_cal(obs, sv, wind_dir))
	colnames(td) <- c("x","y","dist","dd","cd", "dif","zr")
	td$z = log10(td$zr)
	## Fitting and measure the ssr
#      	fit.res <- lm(z~dif+log(dist),data=td)		# Case 1: power-law model, not used here.
       	fit.res <- lm(z~dist+dd,data=td)		# Case 2: exponential model.
      	ssr <- sum(fit.res$residuals^2)
	return(c(ssr, fit.res$coefficients, summary(fit.res)$r.squared))
}

#################### Function name: cg ####################
# Function to generate points (in the x-y plane) around a given point. This given point
# is the proposed vent location from the previous iteration.
#
# The generated points are proposed such that the following function compares if
# one of them or the proposed vent location from the previous iteration is closer to the true vent location.
# The generated points are located in the four cardinal directions with respect to the proposed vent location
# from the previous iteration.
# The function name is short for "Circle Generator".
# Input:
	# sv:
	#	the proposed vent location from previous iteration.
	#	Its form is identical to "sv" in "sp_par_cal".
	# h:
	#	a numeric value (search radius), indicating how far the four generated points are
	#	from the previous proposed vent location.

# Output
	#	a 4-by-2 matrix containing the coordinates of the four generated points.
cg <- function(sv, h){
	output  = cbind(c(rep(sv[,1],4)+sin(0:3*90*pi/180)*h),
		    c(rep(sv[,2],4)+cos(0:3*90*pi/180)*h))
	return(output)
}

#################### Function name: baf_pre ####################
# This function prepares for the function "baf" below. It is designed
# to avoid local minima in estimating the wind direction.
# The name "baf_pre" is short for "best-fitted angle finder_prepare".
# Input
	# sv and obs:
	#	inputs required for function "sp_par_cal"
	# nlps:
	#	A numeric value indicating the number of iterations
	# 	used to estimate the prevailing wind direction.
	# 	It is not used here, but is necessary for the ongoing functions.
# Output
	# A vector containing two or three values:
	# 	If the first element is 1,
	# 		no local minima occur. The 2nd element is a rough estimate
	#		on the prevailing wind direction.
	#	If the first element is 2,
	# 		local minima are present. The next two elements are rough estimates
	#		on the global and local minima of wind direction. Both of them will be applied to ongoing
	#		functions.
baf_pre <- function(sv, obs, nlps){
	pot_ang <- as.data.frame((1:36)*10)
	pot_ssr <- apply(pot_ang, MARGIN = 1, FUN=fi, obs = obs, sv = sv)
	#lowest_rank = which(rank(pot_ssr)==1)			# Power-law ***
	#next_lowest_rank = which(rank(pot_ssr)==2)		# Power-law ***
	lowest_rank = min(which(rank(pot_ssr)<2))				#exponential
	next_lowest_rank = min(which(rank(pot_ssr)<4 & rank(pot_ssr)>2))	#exponential
	if(abs(lowest_rank - next_lowest_rank)==1 || abs(lowest_rank - next_lowest_rank)==35){
		return(c(1, pot_ang[lowest_rank,1]))
	}else{
		return(c(2, pot_ang[lowest_rank,1], pot_ang[next_lowest_rank, 1]))
	}
}

#################### Function name: baf_processor ####################
# Function to estimate the prevailing wind direction with an assumed and
# fixed vent location. It uses a standard one-dimensional gradient descent method.
# The name "baf_processor" is short for "best-angle finder_processor".
# Input
	# sv and obs: inputs for the function "sp_par_cal".
	# nlps: number of loops for the 1d gradient descent method.
	# 	It is sufficient to set its value to 20.
	# proposed_winddir: the proposed wind direction from the function "baf_pre".
	# This function uses the output from function "baf_pre" as
	# the initial guess on wind direction.
# Output
	# a 1-by-2 matrix containing:
	#	the predicted wind direction;
	#	the corresponding sum of squared residuals.
baf_processor <- function(sv, obs, nlps, proposed_winddir){
	fdm <- matrix(c(-1,0,1,-1,0,1), nrow=2)
	h <- 5
	warns <- 0
	resm <- data.frame(matrix(ncol=2,nrow=2))
	gdnt <- c()
	wind_dir = proposed_winddir
	res <- fi(obs, sv, wind_dir)
	resm[1,1] <- wind_dir
	resm[1,2] <- res[1]
	wind_dir <- wind_dir + h
	res <- fi(obs, sv, wind_dir)
	resm[2,1] <- wind_dir
	resm[2,2] <- res[1]
	if(resm[1,2] >= resm[2,2]){
		lid <- 1
	}else{
		lid <- -1
	}
	r = 1
	# lid=1 -> going forward, wind_direction+ ;
	# lid=-1 -> going backward, wind_direction- ;
	# lid=0 -> going in between, the wind direction is between the last two values;
	# r=0 -> h has just been shrunk;
	# r=1 -> the h has been shrunk for the last two iterations.
	for(i in 3:nlps){
		if(lid == 1){
			wind_dir <- resm[order(resm[,2]),][1,1] + h
			lid <- 1
		}else if(lid == -1){
			wind_dir <- resm[order(resm[,2]),][1,1] - 2 * h
			lid <- -1
		}else if(lid == 0 && r == 1){
			h <- h * 0.4
			wind_dir <-resm[order(resm[,2]),][1,1] + h
			r = 0
		}else if(lid == 0 && r == 0){
			wind_dir <-resm[order(resm[,2]),][1,1] - h
			r = 1
		}

		res <- fi(obs, sv, wind_dir)
		resm[i,1] <- wind_dir
		resm[i,2] <- res[1]

		resm <- resm[order(resm[,1]),]
			if(lid == 1){
				gdnt <-  fdm %*% resm[c(i-2,i-1,i),2]
			}
			if(lid == -1){
				gdnt <- fdm %*% resm[c(1,2,3),2]
			}
			if(lid == 0 ){
				n <- which(resm[,2]==min(resm[,2]))
				gdnt <- fdm %*% resm[c(n-1,n, n+1) ,2]
			}
		if(gdnt[1]<0 && gdnt[2]>=0){lid <- 0}
		if(gdnt[1]>=0 && gdnt[2] >= 0){lid <- -1}
		if(gdnt[1]<0 && gdnt[2] <0){lid <- 1}
		if(gdnt[1] > 0 && gdnt[2] < 0){lid <- -1}

		if(h < 0.01){break}
	}
	temp <- resm[which(resm[,2]==min(resm[,2])),]
	output <- as.matrix(cbind(temp[1], temp[2]), nrow = 1)

	return(output)
}

#################### Function name: baf ####################
# As noted above, local minimum could occur for estimating the prevailing wind direciton.
# Therefore this function is designed to receive outputs from the function "baf_pre".
# It first identifies if local minima occur or not, then applies the proposed initial
# guess(es) to the function "baf_processor", and collects the output.
# If local minima occur, it compares the resultant two sum of squared residuals to
# determine which one represents the true prevailing wind direction (global minimum).
# Input
	# sv and obs:
	#	inputs required for function "sp_par_cal".
	# nlps:
	#	a numerical value required for the function "baf_processor",
	#	as described before.
# Output
	# Optimum output from function "baf_processor".
baf <- function(sv, obs, nlps){
	raw_inference <- baf_pre(sv, obs, nlps)
	if(raw_inference[1] == 1){
		output = baf_processor(sv, obs, nlps, raw_inference[2])
		return(output)
	}else{
		output_1 = baf_processor(sv, obs, nlps, raw_inference[2])
		output_2 = baf_processor(sv, obs, nlps, raw_inference[3])
		if(output_1[2]<output_2[2]){return(output_1)}else{return(output_2)}
	}
}

#################### Function name: compare ####################
# This function COMPAREs if the proposed vent location from the last iteration
# or one of the points generated from function "cg" is closer to the true vent location.
# 	If the proposed vent location from the last iteration is closer to the true vent,
# 	it passes this proposed vent location to the next iteration.
# 	If one or more points generated from function "cg" are closer
# 	to the true vent, it provides a direction (unit vector) which is used to propose
#	the vent location for the next iteration (which will lie in the direction represented
#	by the unit vector).
# Input
	# sv and obs:
	#	inputs required for function "sp_par_cal".
	# h:
	# 	input required for function "cg".
	# nlps:
	#	inputs required for functions "baf_pre", "baf_processor", and "baf".
	# h_ratio:
	#	a numeric value that can be used to further constrain the search radius in function "cg".
	#	From our experiments, it seems that to set this value to 1 is an appropriate option.
# Output
	# 	if:
	#	the proposed vent location from the last iteration is closer to the true vent:
	#		the output is a numeric value 1. We keep this point for the next iteration.
	#	else:
	#		the output is a vector of three elements. The first element is the numeric value 2.
	#		The second and third elements together form a unit vector, and the proposed
	#		vent location for the next iteration will be at that direction with respect to
	#		the current proposed vent location.
	#		The way the unit vector is determined can be thought of as a crude approximation
	#		of the divergence of the cost function.
compare <- function(sv, obs, h, nlps, h_ratio){
	rsm <- (cg(sv, h*h_ratio))
	cr <- baf(sv, obs, nlps)

	rr = t(apply(X = rsm,  MARGIN = 1, FUN = baf, obs = obs, nlps = nlps))
	first_order_grad <- rr[,2]-cr[2]

	if(min(first_order_grad>0)){
		return(1)
	}else{
		first_order_grad[first_order_grad > 0] = 0
		direction = c(1,1,-1,-1)
		grad_y = (-1*direction[c(1,3)])%*%first_order_grad[c(1,3)]
		grad_x = (-1*direction[c(2,4)])%*%first_order_grad[c(2,4)]
		grad_unit = c(grad_x/(grad_x^2+grad_y^2)^0.5, grad_y/(grad_x^2+grad_y^2)^0.5)
		return(c(2,grad_unit))
	}
}
####################End of intermediate functions#################################

#################### Function name: gd_simplified ####################
# This function integrates the above functions, implements ANOTHER gradient descent
# method in the x-y plane, and estimates the source vent location.
# See notations in the file "demo_svl_2.0.R" for the description of this
# function, and how to set values for the input parameters.
gd_simplified <- function(sv, obs, runs, h, r, nlps, h_ratio, numb){

	obs = obs[numb,]
	tcoord <- as.data.frame(matrix(ncol=2))
	dec <- c()
	hl <- c()
	ihd <- h

	ir=1
	jg <- 0

	warns <- 0
	for(i in 1:runs){
			dec <- compare(sv, obs, h, nlps, h_ratio)
			if(dec[1]!= 1 && jg ==0){
					sv <- sv + h*dec[2:3]
					tcoord[i,] <- sv
					hl[i] <- h
					jg <- 0
			}else if(dec[1] != 1 && jg == 1){
					h <- h *(1 - r)
					sv <- sv + h*dec[2:3]
					tcoord[i,] <- sv
					hl[i] <- h
					jg <- 0
			}else if(dec[1] == 1 && h/ihd >= 0.0001){
				h = h * r
				hl[i] <- h
				jg <- 1
			}
		if(h < 0.1){break}
	}
	bd <- baf(sv, obs, nlps)
	rownames(tcoord) <- NULL

	output_sv = tcoord[nrow(tcoord),]
	output_ang = bd[1]

	fitted_results= fi2(obs, as.matrix(sv), output_ang)

	return(as.matrix(cbind(sv, output_ang, h, coef = t(fitted_results[c(2,3,4)]), ssr = fitted_results[1], rsquare = fitted_results[5])))
}
########################################## End of functions #####################################################

# References:
#	Gonzalez-Mellado, A. O., and S. De la Cruz-Reyna. "A simple semi-empirical approach to model thickness of
# ash-deposits for different eruption scenarios." Natural Hazards and Earth System Sciences 10.11 (2010): 2241.
# 	Yang Q, Bursik M. A new interpolation method to model thickness, isopachs, extent, and volume of tephra fall
# deposits. Bulletin of Volcanology, 2016, 78(10): 68.