#
# waiting-sim.R,  7 Jun 26
#

library("colorspace")
library("plyr")


par(bty="l")
par(las=1)
par(pch="+") 
pal_col=rainbow(2)


rand_priority=function(len=1, max_priority)
{
return(trunc(runif(len, 1, max_priority+1)))
}


# Run a project with the priority increasing with time in queue
age_project=function(max_priority, qlen, age_factor=2, num_tasks=1000, mk_plot=0)
{
cur_time=qlen

# Initialise queue
ilist=data.frame(size=rep(1, qlen), priority=rand_priority(qlen, max_priority), birth=1:qlen)

   # Pick a task to be implemented and replace with a random new task
   do_task=function(dummy)
   {
      pick_top=function() # pick task with highest priority
      {
      top=ilist$priority+(cur_time-ilist$birth)/(age_factor*qlen)
      top_list=which(top == max(top))
      return(ilist$birth[sample(top_list, 1)])
      }

   birth=pick_top()

   cur_time<<-cur_time+1
   slot=which(ilist$birth == birth)
   ilist$size[slot]<<-1
   ilist$priority[slot]<<-rand_priority(max_priority=max_priority)
   ilist$birth[slot]<<-cur_time

   return(cur_time-birth) # time in queue
   }

wtime=sapply(1:num_tasks, do_task) # Iterate over num_tasks

x_pow=20
x_bounds=1:x_pow

# Fit a model to the waiting times
wt_cnt=count(wtime)
# Exponential
wt_mod=glm(freq ~ log(x), data=wt_cnt, subset=(x <= x_pow))
# print(summary(wt_mod))

if (mk_plot)
   {
   plot(wt_cnt, log="x", col=pal_col[2])

   pred=predict(wt_mod, newdata=data.frame(x=x_bounds))
   lines(x_bounds, pred, col=pal_col[1])
   }

return(data.frame(pow=coef(wt_mod)[2], max_priority, qlen, age_factor))
}



run_project=function(max_priority, qlen, P, chng_prio=1, updown=0.5, num_tasks=1000, mk_plot=0)
{
cur_time=qlen

# Initialise queue
ilist=data.frame(size=rep(1, qlen), priority=rand_priority(qlen, max_priority), birth=1:qlen)

   # Pick a task to be implemented and replace with a random new task
   do_task=function(dummy)
   {
      pick_top=function() # pick task with highest priority
      {
      top=max(ilist$priority)
      top_list=which(ilist$priority == top)
      # Pick oldest
      return(min(ilist$birth[top_list]))
      }

   rand_i=trunc(runif(1, 1, qlen+1))
   if (chng_prio == 1)
      {
      if (runif(1) < (1-P)) # Make a priority change?
         {
         if (runif(1) < updown) # Change up or down?
            ilist$priority[rand_i]<<-ifelse(ilist$priority[rand_i] < max_priority, (ilist$priority[rand_i]+1), ilist$priority[rand_i])
         else
            ilist$priority[rand_i]<<-ifelse(ilist$priority[rand_i] > 1 , (ilist$priority[rand_i]-1), ilist$priority[rand_i])
         }
      birth=pick_top()
      }
   else
      birth=ifelse(runif(1) < (1-P), ilist$birth[rand_i], pick_top())

   cur_time<<-cur_time+1
   slot=which(ilist$birth == birth)
   ilist$size[slot]<<-1
   ilist$priority[slot]<<-rand_priority(max_priority=max_priority)
   ilist$birth[slot]<<-cur_time

   return(cur_time-birth)
   }

wtime=sapply(1:num_tasks, do_task) # Iterate over num_tasks

x_pow=20
x_bounds=1:x_pow

# Fit a model to the waiting times
wt_cnt=count(wtime)
# Power law
wt_mod=glm(log(freq) ~ log(x), data=wt_cnt, subset=(x <= x_pow) & (x > 1))
# print(summary(wt_mod))

if (mk_plot)
   {
   plot(wt_cnt, log="xy", col=pal_col[2])

   pred=predict(wt_mod, newdata=data.frame(x=x_bounds))
   lines(x_bounds, exp(pred), col=pal_col[1])
   }

return(data.frame(pow=coef(wt_mod)[2], max_priority, qlen, P))
}



plot_P=function(Max_priority, Qlen)
{
mp=subset(qp_comb, (qlen == Qlen) & (max_priority == Max_priority))

lines(mp$P, mp$pow)
}


plot_priority=function(Qlen, p)
{
mp=subset(qp_comb, (qlen == Qlen) & (P == p))

lines(mp$max_priority, mp$pow)
}



# Try out various values
par(mfcol=c(2, 2))
run_project(5, 10, 0.9, 1, 0.2)
run_project(5, 10, 0.9, 1, 0.8)
run_project(5, 10, 0.7, 0)
run_project(5, 10, 0.9, 0)


# Run 100 projects
# proj_dist=replicate(100, run_project(5, 10, 0.9, chng_prio=0, num_tasks=1e3), simplify=FALSE)
# proj_dist=replicate(100, run_project(5, 10, 0.9, chng_prio=0, num_tasks=1e5), simplify=FALSE)

proj_dist=replicate(100, run_project(5, 10, 0.9, chng_prio=0, num_tasks=1e4), simplify=FALSE)
proj_dist=replicate(100, run_project(5, 10, 0.7, chng_prio=0, num_tasks=1e4), simplify=FALSE)
t=do.call(rbind, proj_dist)
plot(sort(t$pow),
	xlab="Index", ylab="Exponent")


# Example in blog post
par(mfcol=c(1, 1))

proj_dist=replicate(100, run_project(5, 10, 0.9, chng_prio=0, num_tasks=1e3), simplify=FALSE)
# proj_dist=replicate(100, run_project(5, 10, 0.7, chng_prio=1, updown=0.7, num_tasks=1e4), simplify=FALSE)
t=do.call(rbind, proj_dist)
plot(sort(t$pow), col="salmon",
	xaxs="i",
	xlim=c(0, nrow(t)),
	xlab="Project index", ylab=expression(paste0("Exponent: ", alpha)))


dev.copy(dev=png, file="pri-rand-5-10.png")
dev.off()


# Age example
age_project(5, 10, age_factor=3, num_tasks=1e3, mk_plot=1)

proj_dist=replicate(100, run_project(5, 10, num_tasks=1e4), simplify=FALSE)
t=do.call(rbind, proj_dist)
plot(sort(t$pow),
	xlab="Index", ylab="Exponent")



# Iterate over combinations of configuration options
# Now know that this is pointless for under 1e5 task projects
priority_range=seq(3, 20)
qlen_range=seq(3, 25)
# P_range=c(0.9, 0.92, 0.94, 0.96, 0.98)
P_range=c(0.7, 0.8, 0.9)
max_param=length(priority_range)*length(qlen_range)*length(P_range)
# Data frame containing all combinations of the three vectors
queue_param=expand.grid(priority_range, qlen_range, P_range)

qp_comb=adply(1:nrow(queue_param), .mar=1, function(X) run_project(queue_param$Var1[X],
									queue_param$Var2[X],
									queue_param$Var3[X]))

plot(0, type="n",
	xlim=range(P_range), ylim=c(-0.5, -3),
	xlab="P", ylab="power")
plot_P(3, 3)


plot(0, type="n",
	xlim=range(priority_range), ylim=c(-0.5, -3),
	xlab="Priority", ylab="power")
plot_priority(3, 0.9)


pow_mod=glm(pow ~ qlen+qlen:P, data=qp_comb)
summary(pow_mod)

mod_co=coef(pow_mod)

# Range of power law exponent for various P
range(mod_co[1]+(mod_co[2]+0.7*mod_co[3])*qlen_range)
range(mod_co[1]+(mod_co[2]+0.9*mod_co[3])*qlen_range)


