Question 1 : Comparaison de performances de S1 et de S2

Code de la simulation pour les serveurs S1 et S2 :

Cette fonction est pensée de manière à ce que la dataframe renvoyée soit du même format que celle renvoyée par la fonction de simulation de S3. Cette dataframe contient toutes les informations nécessaires à l’analyse de la simulation.

library("ggplot2")
library("dplyr")
#fonction de simulation d'une M/M/1 :
simulBase = function(N=100, arrival_rate=.2, processing_rate=1,cons_rate=1,cons_idle=1, strat="base", debug=FALSE) {
  # Dates d'arrivées (calculées à partir d'inter-arrivées
  Arrival = cumsum(rexp(n=N,rate=arrival_rate));
  
  ConsoParTaches = rep(N,x=NA);
  
  # Temps de traitement des tâches (générés)
  Service = rexp(n=N,rate=processing_rate);
  
  # Temps de traitement restant pour les tâches en liste d'attente.
  Remaining = rep(N,x=NA);
  
  # NA au début, passe à la date courante t lorsque Remaining passe à 0.
  Completion = rep(NA,times=N);
  
  t = 0            # Date courante
  
  NextArrival = 1  # incrémentée jusqu'à dépasser le nombre de tâches total.
  CurrentTask = NA  # Client actuellement servi
  
  while(T) {
    if(debug) print(t)
    if(debug) print(Arrival);
    if(debug) print(Service);
    if(debug) print(Remaining);
    
    #Calcul prochain evenement :
    dtA = NA; # temps jusqu'à la prochaine arrivée
    dtC = NA; # temps jusqu'à la prochaine terminaison (Completion)
    if(length(Arrival[Arrival>t])>0) { # "Si il existe des valeurs supérieur à t dans Arrival" (s'il reste des tâches)
        dtA = head(Arrival[Arrival>t],n=1)-t  # "Alors on prend la prmeière et on lui retire t, c'est donc le temps                                                      avant qu'elle arrive"
    }
    if(!is.na(CurrentTask)) { # "Si la tache actuelle n'est pas nulle"
        dtC = Remaining[CurrentTask]; # "Alors dtC = le temps restant pour cette tache"
    }
    if(is.na(dtA) & is.na(dtC)) { # Si les deux sont nuls, alors c'est qu'on a fini, on sort de la boucle
        break;
    } 
    dt = min(dtA,dtC,na.rm=T)  # prochain evenement : min des deux (on compte pas les NA)
    
    
    #On peut donc avancer dans l'algo jusqu'au prochain evenement, on avance d'une "étape"
    #On va donc mettre à jour tout ce qu'il faut.
    #La date :
    t = t + dt;
    
    #Les arrivées (si l'evenement actuel est une arrivée, et qu'il en reste)
    if(NextArrival<=N && (Arrival[NextArrival] <= t)) {
      Remaining[NextArrival] = Service[NextArrival];
      NextArrival = NextArrival+1
    }
    
    #Le remaining (On met a jour les temps restants et completion si l'evenement est une tache terminée)
    if(!is.na(CurrentTask)) {
      Remaining[CurrentTask] = Remaining[CurrentTask] - dt;
      if(Remaining[CurrentTask] <= 0) {
        Completion[CurrentTask] = t
        Remaining[CurrentTask] = NA
        CurrentTask = NA
      }
    }
    
    #On va réélire la prochaine CurrentTask
    #On crée une liste intermédiaire qui contient tous les temps restants non nuls de Remaining.
    WaitingList=(1:N)[!is.na(Remaining)]
    if(is.na(CurrentTask)) {
      if(length(WaitingList)>0) {
        CurrentTask = head(WaitingList,n=1);
        ConsoParTaches[CurrentTask] = Remaining[CurrentTask] * cons_rate;
      }
    }
  }
  if(debug) print(Completion)
  
  #On retourne les données :
  return(data.frame(
    Strat = strat,
    Arrival_rate=arrival_rate,
    Processing_rate=processing_rate,
    ConsTotal = sum(ConsoParTaches) + ((max(Completion) - sum(Service))*cons_idle),
    Arrival = Arrival,
    Completion = Completion
    ))
  
}

1) Temps de réponse moyen des 2 serveurs S1 et S2 :

Voici le code permettant de simuler les deux serveurs S1 et S2, qui se simulent de la même manière avec juste des valeurs qui changent en entrée.
Remarque : je set la seed deux fois au même taux, ainsi, le début de ces deux simulation est théoriquement identique, permettant de pouvoir comparer plus facilement les courbes.

set.seed(1444);

df = data.frame()
#Simulation de S1
for (r in seq(from=.1, to=0.9, by=.1)) {
    df = rbind(df,simulBase(N=10000, arrival_rate=r, processing_rate=1, cons_rate=1, cons_idle=0.5, "S1", debug=FALSE));
}
set.seed(1444);
#Simulation de S2
for (r in seq(from=.1, to=1.9, by=.1)) {
    df = rbind(df,simulBase(N=10000, arrival_rate=r, processing_rate=2, cons_rate=4, cons_idle=0.25, "S2", debug=FALSE));
}


#Calcul du temps de réponse moyen et de la variation
dfRep = df %>% group_by(Strat,Arrival_rate, Processing_rate) %>%
    summarize(Response = mean(Completion - Arrival), 
              Response_se = sd(Completion - Arrival)/sqrt(n()))

#On trace les deux courbes
ggplot(dfRep, aes(x=Arrival_rate, y=Response, colour=Strat)) + geom_line() +
    geom_errorbar(aes(ymin = Response - 2*Response_se,
                      ymax = Response + 2*Response_se))

Je fais varier le taux d’arrivée lambda vers la valeur du processing rate pour chaque serveur (donc 0.9 pour un taux de 1, etc). En effet, il est très difficile d’effectuer des simulations lorsque que l’on approche de la capacité max du serveur, la variabilité devient très élevée, les écarts types n’ont d’ailleurs qu’assez peu de sens quand la charge est trop élevée : on peut constater qu’ils ont l’air faux quand lambda approche du processing rate.
A part cela, on peut constater que le résultats ressemble exactement à ce que l’on peut s’attendre : Le temps de réponse de chaque serveur est assez bas, jusqu’a ce que le taux d’arrivée approche trop le temps de service. De plus, on constate logiquement que le serveur 2 étant deux fois plus rapide, lorsque que le taux d’arrivée est bas, il répond en moyenne deux fois plus rapidement.

2) Consommation moyenne des 2 serveurs S1 et S2 :

Voici le code me permettant de calculer la consommation moyenne de ces deux serveurs. La majorité des informations nécessaires sont déjà présentes dans la dataframe, il suffit de les remettre en ordre dans une nouvelle dataframe pour ne garder que l’utile, et les tracer.

#On calcul la conso des deux serveurs
dfCons = df %>% group_by(Strat,Arrival_rate, Processing_rate) %>%
    summarize(TimeTotal = max(Completion),
              Conso = head(ConsTotal,n=1))

#Voici la conso réelle pour répondre à N=5000 requêtes
ggplot(dfCons, aes(x=Arrival_rate, y=Conso, colour=Strat)) + geom_line()

Ce graphe n’est cependant pas très parlant car non normalisé, on peut cependant déjà apercevoir qu’elles se croisent.

#Voici la conso divisée par le temps pour un graphe plus parlant
ggplot(dfCons, aes(x=Arrival_rate, y=Conso/TimeTotal, colour=Strat)) + geom_line()

Sur le graphe normalisé avec la conso divisée par le temps, le résultat est beaucoup plus parlant. On constate que le taux d’arrivée qui permet aux deux serveur d’avoir la même consommation est situé entre 0.1 et 0.2 environ.

3) Valeur de lambda :

On sait que la valeur de lambda qui permet aux deux serveurs d’avoir une consommation similaire est située environ entre 0.1 et 0.2. On peut donc resimuler dans cet intervalle pour y voir plus clair, et ainsi se rapprocher petit à petit de la vrai valeur de lambda. Je me suis arrêté à la simulation de 0.16 à 0.19 avec un pas de 0.005 :

set.seed(1444);

dfTemp = data.frame()
#Simulation de S1
for (r in seq(from=.16, to=.19, by=.01)) {
    dfTemp = rbind(dfTemp,simulBase(N=10000, arrival_rate=r, processing_rate=1, cons_rate=1, cons_idle=0.5, "S1", debug=FALSE));
}
set.seed(1444);
#Simulation de S2
for (r in seq(from=.16, to=.19, by=.01)) {
    dfTemp = rbind(dfTemp,simulBase(N=10000, arrival_rate=r, processing_rate=2, cons_rate=4, cons_idle=0.25, "S2", debug=FALSE));
}

#On calcul la conso des deux serveurs
dfConsTemp = dfTemp %>% group_by(Strat,Arrival_rate, Processing_rate) %>%
    summarize(TimeTotal = max(Completion),
              Conso = head(ConsTotal,n=1))

ggplot(dfConsTemp, aes(x=Arrival_rate, y=Conso/TimeTotal, colour=Strat)) + geom_line()

On constate donc que lambda vaut environ 0.182 (à vu d’oeil).
Remarque : Si on augmente un peu le pas (0.01 par exemple), On constate que la courbe devient un peu plus lisse, mais le résultat reste identique, lambda tourne autour de 0.182 à vu d’oeil sur le graphe.
On a donc à priori notre réponse: le serveur S2 devient plus gourmand en energie à partir d’un taux d’arrivée de 0.182 environ.

Question 2 : Etude du serveur S3

Code de la simulation pour S3 :

Cette fonction permet de simuler le serveur S3. Elle combine donc les deux serveurs précédents, et renvoie dans la dataframe les informations nécessaires à l’analyse de cette simulation.
Voici le fonctionnement de la simulation :
Lorsque qu’une tâche arrive au serveur, si les deux serveurs S1 et S2 sont disponibles, elle ira toujours au même serveur. Cependant, si l’un des deux est occupé, elle sera forcément traitée par l’autre. On constate donc immédiatement que le choix du serveur qui sera “prioritaire” va beaucoup influencer les résultats.

#fonction de simulation du serveur S3 :
simulS3pS1 = function(N=100, arrival_rate=.2, processing_rateS1=1, processing_rateS2=2, cons_rateS1=1, cons_rateS2=4,
                   cons_idleS1=0.5, cons_idleS2=0.25, strat="S3", debug=FALSE) {
  # Dates d'arrivées (calculées à partir d'inter-arrivées
  Arrival = cumsum(rexp(n=N,rate=arrival_rate));
  
  # Temps de traitement des tâches (générés)
  ServiceS1 = rexp(n=N,rate=processing_rateS1);
  
  # Temps de traitement des tâches (générés)
  ServiceS2 = rexp(n=N,rate=processing_rateS2);
  
  #Conso pour chaque tâches effectuée
  ConsoParTaches = rep(N,x=NA)
  
  # Temps de traitement restant pour les tâches en liste d'attente.
  Remaining = rep(N,x=NA);
  
  # NA au début, passe à la date courante t lorsque Remaining passe à 0.
  Completion = rep(NA,times=N);
  
  t = 0            # Date courante
  
  NextArrival = 1  # incrémentée jusqu'à dépasser le nombre de tâches total.
  CurrentTaskS1 = NA  # Client actuellement servi par S1
  CurrentTaskS2 = NA  # Client actuellement servi par S2
  
  while(T) {
    if(debug) print(t)
    if(debug) print(Arrival);
    if(debug) print(Service);
    if(debug) print(Remaining);
    
    #Calcul prochain evenement :
    dtA = NA; # temps jusqu'à la prochaine arrivée
    dtCS1 = NA; # temps jusqu'à la prochaine terminaison (Completion) de S1
    dtCS2 = NA; # temps jusqu'à la prochaine terminaison (Completion) de S2

    if(length(Arrival[Arrival>t])>0) { # "Si il existe des valeurs supérieur à t dans Arrival" (s'il reste des tâches)
        dtA = head(Arrival[Arrival>t],n=1)-t  # "Alors on prend la prmeière et on lui retire t, c'est donc le temps                                                      avant qu'elle arrive"
    }
    if(!is.na(CurrentTaskS1)) { # "Si la tache actuelle n'est pas nulle"
        dtCS1 = Remaining[CurrentTaskS1]; # "Alors dtC = le temps restant pour cette tache traitée par S1"
    }
    if(!is.na(CurrentTaskS2)) { # "Si la tache actuelle n'est pas nulle"
        dtCS2 = Remaining[CurrentTaskS2]; # "Alors dtC = le temps restant pour cette tache traitée par S2"
    }
    if(is.na(dtA) & is.na(dtCS1) & is.na(dtCS2)) { # Si les deux sont nuls, alors c'est qu'on a fini, on sort de la boucle
        break;
    } 
    dt = min(dtA,dtCS1,dtCS2,na.rm=T)  # prochain evenement : min des trois (on compte pas les NA)
    
    #On peut donc avancer dans l'algo jusqu'au prochain evenement, on avance d'une "étape"
    #On va donc mettre à jour tout ce qu'il faut.
    #La date :
    t = t + dt;
    
    #Les arrivées (si l'evenement actuel est une arrivée, et qu'il en reste)
    if(NextArrival<=N && (Arrival[NextArrival] <= t)) {
      Remaining[NextArrival] = ServiceS1[NextArrival];
      NextArrival = NextArrival+1
    }
    
    #Le remaining (On met a jour les temps restants et completion si l'evenement est une tache terminée)
    #Si S1 était en train de travailler
    if(!is.na(CurrentTaskS1)) {
      Remaining[CurrentTaskS1] = Remaining[CurrentTaskS1] - dt;
      if(Remaining[CurrentTaskS1] <= 0) {
        Completion[CurrentTaskS1] = t
        Remaining[CurrentTaskS1] = NA
        CurrentTaskS1 = NA
      }
    }
    
    #Si S2 était en train de travailler
    if(!is.na(CurrentTaskS2)) {
      Remaining[CurrentTaskS2] = Remaining[CurrentTaskS2] - dt;
      if(Remaining[CurrentTaskS2] <= 0) {
        Completion[CurrentTaskS2] = t
        Remaining[CurrentTaskS2] = NA
        CurrentTaskS2 = NA
      }
    }
    
    #On réélis les taches pour les deux serveurs si ils ne font rien actuellement
    #On crée une liste intermédiaire qui contient tous les temps restants non nuls de Remaining.
    WaitingList=(1:N)[!is.na(Remaining)]
    
    if(is.na(CurrentTaskS1)) {
      if(length(WaitingList)>0) {
        if((!is.na(CurrentTaskS2)) && (head(WaitingList,n=1) == CurrentTaskS2)) {
          if(length(WaitingList)>1) {
            CurrentTaskS1 = head(WaitingList[-1],n=1);
            ConsoParTaches[CurrentTaskS1] = Remaining[CurrentTaskS1] * cons_rateS1;
            ServiceS2[CurrentTaskS1] = 0;
          }
        }else {
          CurrentTaskS1 = head(WaitingList,n=1);
          ConsoParTaches[CurrentTaskS1] = Remaining[CurrentTaskS1] * cons_rateS1;
          ServiceS2[CurrentTaskS1] = 0;
        }
      }
    }
    
    if(is.na(CurrentTaskS2)) {
      if(length(WaitingList)>0) {
        if((!is.na(CurrentTaskS1)) && (head(WaitingList,n=1) == CurrentTaskS1)) {
          if(length(WaitingList)>1) {
            CurrentTaskS2 = head(WaitingList[-1],n=1);
            Remaining[CurrentTaskS2] = ServiceS2[CurrentTaskS2];
            ConsoParTaches[CurrentTaskS2] = Remaining[CurrentTaskS2] * cons_rateS2;
            ServiceS1[CurrentTaskS2] = 0;
          }
        }else {
          CurrentTaskS2 = head(WaitingList,n=1);
          Remaining[CurrentTaskS2] = ServiceS2[CurrentTaskS2];
          ConsoParTaches[CurrentTaskS2] = Remaining[CurrentTaskS2] * cons_rateS2;
          ServiceS1[CurrentTaskS2] = 0;
        }
      }
    }
    
    
    
  }
  if(debug) print(Completion)
  
  #On retourne les données :
  return(data.frame(
    Strat = strat,
    Arrival_rate=arrival_rate,
    Processing_rate=processing_rateS1 + processing_rateS2,
    ConsTotal = sum(ConsoParTaches) + ((max(Completion) - sum(ServiceS1))*cons_idleS1) +
        ((max(Completion) - sum(ServiceS2))*cons_idleS2),
    Arrival = Arrival,
    Completion = Completion
    ))
  
}

Le code de simulS3pS2 est identique, avec la seule différence étant que S2 est prioritaire par rapport à S1. Inutile donc de montrer le code pour ne pas rendre le DM trop volumineux.

1) Performances de S3 par rapport aux serveurs précédents :

Comme expliqué précédemment, il y a deux façon de simuler le serveur S3 : on peut placer S1 ou S2 comme serveur “prioritaire”, c’est à dire qui prendra forcément les requêtes quand les deux serveurs sont disponibles.
J’ai décidé de tester les deux versions, donc nommées S3pS1 pour S3 priorité S1 et S3pS2. Le code de la simulation est très similaire, on borne lambda de 0.1 à 2.9 car S3 a comme capacité max 3 (S1+S2).

set.seed(1444);

dfS3 = df

#Simulation de S3pS1
for (r in seq(from=.1, to=2.9, by=.1)) {
    dfS3 = rbind(dfS3,simulS3pS1(N=10000, arrival_rate=r, processing_rateS1=1, processing_rateS2=2, cons_rateS1=1, cons_rateS2=4, cons_idleS1=0.5, cons_idleS2=0.25,  "S3pS1", debug=FALSE));
}

#Simulation de S3pS2
for (r in seq(from=.1, to=2.9, by=.1)) {
    dfS3 = rbind(dfS3,simulS3pS2(N=10000, arrival_rate=r, processing_rateS1=1, processing_rateS2=2, cons_rateS1=1, cons_rateS2=4, cons_idleS1=0.5, cons_idleS2=0.25,  "S3pS2", debug=FALSE));
}

#Calcul du temps de réponse moyen et de la variation
dfRep = dfS3 %>% group_by(Strat,Arrival_rate, Processing_rate) %>%
    summarize(Response = mean(Completion - Arrival), 
              Response_se = sd(Completion - Arrival)/sqrt(n()))

#On trace les deux courbes
ggplot(dfRep, aes(x=Arrival_rate, y=Response, colour=Strat)) + geom_line() +
    geom_errorbar(aes(ymin = Response - 2*Response_se,
                      ymax = Response + 2*Response_se))

Voici comment se compare le serveur S3 aux autres serveurs. On constate que logiquement, le temps de réponse augmente quand lambda approche de 3 (car le serveur S3 à une capacité de service de 1+2). De plus, pour S3pS1, la courbe se rapproche plutot de celle de S1 lorsque le taux d’arrivée est bas, tandis que pour S3pS2, elle est quasiment égale à S2. Ces résultats sont alors jusque la très prévisible. On peut d’ailleurs remarquer un point intéressant : le temps de réponse moyen de S3pS1 diminue un peu au début lorsque le taux d’arrivée augmente, c’est parce que on va de plus en plus avoir besoin du serveur S2 qui traite les tâches 2 fois plus vite que le serveur S1, diminuant ainsi la moyenne du temps de réponse pour ces taux d’arrivée la.
Lorsque lambda approche de 3, soit la capacité max de S3, les résultats sont inconsistant de par la difficultée de simuler un serveur près de sa capacité max.

#On calcul la conso de tous les serveurs
dfCons = dfS3 %>% group_by(Strat,Arrival_rate, Processing_rate) %>%
    summarize(TimeTotal = max(Completion),
              Conso = head(ConsTotal,n=1))

#Voici la conso divisée par le temps pour un graphe parlant
ggplot(dfCons, aes(x=Arrival_rate, y=Conso/TimeTotal, colour=Strat)) + geom_line()

Voici directement le graphe normalisé de la consommation de ces quatres serveurs. On constate que la consommation de S3 est la plus élevée peu importe la priorité choisie, ce qui est logique car on a deux serveurs qui tournent, soit en IDLE soit en consommation max, mais rapidement S3pS1 devient plus intéressant que S2 (pour lambda qui vaut entre 0.5 et 0.7 à vue d’oeil). S3 restera d’ailleurs par la suite toujours plus intéressant que S2 en terme de consommation. S3pS2 devient lui aussi plus intéressant que S2 à partir de lambda égal à 1.25 environ à vue d’oeil.

Encore une fois, c’était assez prévisible que la version de S3 avec S1 prioritaire serait plus intéressante en terme de consommation, mais S3pS2 est plus intéressant en terme de temps de réponse moyen. Cependant, étant donné que lorsque lambda augmente les deux serveurs vont finir par marcher à 100%, S3pS1 er S3pS2 vont finir par converger vers les mêmes valeurs : on peut déjà commencer à l’observer sur ce graphe.

2) Valeur de lambda :

On reprend le même processus que pour les serveurs S1 et S2 : on va approximer la valeur de lambda qui permet aux courbes de S2 et S3pS1 de se croiser en ciblant la simulation dans l’intervalle que l’on a observé :

set.seed(1444);

dfTemp = data.frame()
#Simulation de S2
for (r in seq(from=.55, to=.6, by=.01)) {
    dfTemp = rbind(dfTemp,simulBase(N=10000, arrival_rate=r, processing_rate=2, cons_rate=4, cons_idle=0.25, "S2", debug=FALSE));
}

set.seed(1444);
#Simulation de S3
for (r in seq(from=.55, to=.6, by=.01)) {
    dfTemp = rbind(dfTemp,simulS3pS1(N=10000, arrival_rate=r, processing_rateS1=1, processing_rateS2=2, cons_rateS1=1, cons_rateS2=4, cons_idleS1=0.5, cons_idleS2=0.25,  "S3pS1", debug=FALSE));
}

dfConsTemp = data.frame()
#On calcul la conso des deux serveurs
dfConsTemp = dfTemp %>% group_by(Strat,Arrival_rate, Processing_rate) %>%
    summarize(TimeTotal = max(Completion),
              Conso = head(ConsTotal,n=1))

ggplot(dfConsTemp, aes(x=Arrival_rate, y=Conso/TimeTotal, colour=Strat)) + geom_line()

L’approximation de lambda permettant de croiser les courbes a été plutôt difficile, car en choisissant un pas plutôt faible, les courbes se croisent beaucoup de fois dans un intervalle assez large. Pour “lisser” les résultats, j’ai choisi de prendre un pas plus élevé pour essayer de trouver la valeur moyenne de l’intersection. On peut donc approximer (avec un taux d’incertitude élevé tout de même) lambda à environ 0.57 pour que S3pS1 devienne plus intéressant que S2. (obtenir la précision à 0.01 près est assez difficile ici).

De plus, pour que S3pS2 devienne plus intéressant que S2, la valeur de lambda est d’environ 1.24

Donc à partir du taux d’arrivée lambda de 0.57, il est plus intéressant d’utiliser S3pS1 que S2, et à partir de lambda valant 1.24, S3pS2 devient plus intéressant que S2 aussi.

Question Subsidiaire : Influence de l’hypothèse Markovienne

On simule les 4 serveurs en utilisant cette fois les temps deterministes 1/mu1 et 1/mu2 (donc 1 et O.5). Je vous épargne le code des fonctions, qui est le même avec juste l'initialisation du tableau Service qui ressemble désormais à : Service = rep(processing_rate,N);

set.seed(1444);

df = data.frame()
#Simulation de S1
for (r in seq(from=.1, to=0.9, by=.1)) {
    df = rbind(df,simulBaseDet(N=10000, arrival_rate=r, processing_rate=1, cons_rate=1, cons_idle=0.5, "S1", debug=FALSE));
}
set.seed(1444);
#Simulation de S2
for (r in seq(from=.1, to=1.9, by=.1)) {
    df = rbind(df,simulBaseDet(N=10000, arrival_rate=r, processing_rate=0.5, cons_rate=4, cons_idle=0.25, "S2", debug=FALSE));
}
set.seed(1444);
#Simulation de S3pS1
for (r in seq(from=.1, to=2.9, by=.1)) {
    df = rbind(df,simulS3pS1Det(N=10000, arrival_rate=r, processing_rateS1=1, processing_rateS2=0.5, cons_rateS1=1, cons_rateS2=4, cons_idleS1=0.5, cons_idleS2=0.25,  "S3pS1", debug=FALSE));
}
set.seed(1444);
#Simulation de S3pS2
for (r in seq(from=.1, to=2.9, by=.1)) {
    df = rbind(df,simulS3pS2Det(N=10000, arrival_rate=r, processing_rateS1=1, processing_rateS2=0.5, cons_rateS1=1, cons_rateS2=4, cons_idleS1=0.5, cons_idleS2=0.25,  "S3pS2", debug=FALSE));
}


#Calcul du temps de réponse moyen et de la variation
dfRep = df %>% group_by(Strat,Arrival_rate, Processing_rate) %>%
    summarize(Response = mean(Completion - Arrival), 
              Response_se = sd(Completion - Arrival)/sqrt(n()))

#On trace les deux courbes
ggplot(dfRep, aes(x=Arrival_rate, y=Response, colour=Strat)) + geom_line() +
    geom_errorbar(aes(ymin = Response - 2*Response_se,
                      ymax = Response + 2*Response_se))

On peut constater que les courbes des serveurs S1 et S2 sont similaires que nos résultats précédents. La courbe de S3pS1 à l’air tout de même plus ‘stable’ avec le temps de service déterministe ici, qu’avec le temps calculé par loi exponentielle utilisé précédemment. On observe un peu moins de ‘vagues’ globalement, les courbes ont donc l’air plus propres.

#On calcul la conso de tous les serveurs
dfCons = df %>% group_by(Strat,Arrival_rate, Processing_rate) %>%
    summarize(TimeTotal = max(Completion),
              Conso = head(ConsTotal,n=1))

#Voici la conso divisée par le temps pour un graphe parlant
ggplot(dfCons, aes(x=Arrival_rate, y=Conso/TimeTotal, colour=Strat)) + geom_line()

Pour ce qui est des courbes de consommations moyenne, les résultats sont encore une fois très similaire à nos résultats précédents (avec fonction exponentielle), mais ces courbes sont tout de même plus lisses et donc légèrement plus facile à lire.

set.seed(1444);

dfTemp = data.frame()
#Simulation de S1
for (r in seq(from=.16, to=.19, by=.01)) {
    dfTemp = rbind(dfTemp,simulBaseDet(N=10000, arrival_rate=r, processing_rate=1, cons_rate=1, cons_idle=0.5, "S1", debug=FALSE));
}
set.seed(1444);
#Simulation de S2
for (r in seq(from=.16, to=.19, by=.01)) {
    dfTemp = rbind(dfTemp,simulBaseDet(N=10000, arrival_rate=r, processing_rate=0.5, cons_rate=4, cons_idle=0.25, "S2", debug=FALSE));
}

#On calcul la conso des deux serveurs
dfConsTemp = dfTemp %>% group_by(Strat,Arrival_rate, Processing_rate) %>%
    summarize(TimeTotal = max(Completion),
              Conso = head(ConsTotal,n=1))

ggplot(dfConsTemp, aes(x=Arrival_rate, y=Conso/TimeTotal, colour=Strat)) + geom_line()

Pour l’intersection entre S1 et S2 en terme de consommation, on trouve un lambda d’environ 0.181, soit presque exactement ce que nous avions trouvé en utilisant les fonction exponentielles pour simuler le temps de service (0.182).

set.seed(1444);

dfTemp = data.frame()
#Simulation de S2
for (r in seq(from=.53, to=.56, by=.01)) {
    dfTemp = rbind(dfTemp,simulBaseDet(N=10000, arrival_rate=r, processing_rate=0.5, cons_rate=4, cons_idle=0.25, "S2", debug=FALSE));
}

set.seed(1444);
#Simulation de S3
for (r in seq(from=.53, to=.56, by=.01)) {
    dfTemp = rbind(dfTemp,simulS3pS1Det(N=10000, arrival_rate=r, processing_rateS1=1, processing_rateS2=0.5, cons_rateS1=1, cons_rateS2=4, cons_idleS1=0.5, cons_idleS2=0.25,  "S3pS1", debug=FALSE));
}

dfConsTemp = data.frame()
#On calcul la conso des deux serveurs
dfConsTemp = dfTemp %>% group_by(Strat,Arrival_rate, Processing_rate) %>%
    summarize(TimeTotal = max(Completion),
              Conso = head(ConsTotal,n=1))

ggplot(dfConsTemp, aes(x=Arrival_rate, y=Conso/TimeTotal, colour=Strat)) + geom_line()

On constate que entre S2 et S3p1, l’intersection des courbes de consommation s’effectue avec un lambda très légèrement plus faible que lors de nos précédentes simulations : entre 0.54 et 0.55 environ (ici) contre 0.57 avec les fonctions exponentielles.
Cependant, cette différence est très faible, donc quasi négligeable aussi.

Conclusion de la question subsidiaire :

Les résultats semblent très similaires avec des temps de service déterministes mais avec des courbes légèrement plus ‘lissées’ dans l’ensemble, les résultats doivent aussi être bien plus constant car pas de variabilité lié à la seed.
On ne note en tout cas pas de changement majeur dans la direction des courbes et leurs différentes intersections.

Damien Wykland