Beruflich Dokumente
Kultur Dokumente
Philippe Laroque
$Id: simuBanque.lyx 1665 2009-03-05 16:02:45Z phil $
laroque@u-cergy.fr
mars 2009
Abstract
Ce petit document résume, à travers une étude de cas simple, le principe du passage d’un
modèle UML à un codage C++. L’exemple utilise la STL. Il est donné comme corrigé indicatif
lors de la formation continue “SAGEM” en avril 2009.
1 Rappel du sujet
Il s’agit de simuler le fonctionnement d’une banque contenant un nombre fixé de caissiers, et
recevant des clients qui arrivent de manière pseudo-aléatoire. Lorsqu’un client arrive, si un caissier
est libre il prend en charge le client, sinon le client prend place dans une file d’attente (supposée
commune à tous les caissiers, représentée par exemple par un système de tickets numérotés en
ordre croissant).
Le but de la simulation est de fournir des résultats statistiques sur les différents acteurs de la
simulation. Pour cela, on donne en entrée de la simulation:
• La durée estimée de la simulation : c’est la durée au bout de laquelle la banque n’accepte
plus de nouveaux clients. Bien entendu, ceux qui se trouvent déjà dans la file seront servis...
• Le nombre de caissiers.
• Le temps moyen de service de chaque caissier (on suppose donc qu’ils ne sont pas tous
également performants ...).
• Le temps moyen entre deux arrivées successives de clients.
1
Hypothèses de fonctionnement
Pour réaliser cette simulation, on adopte un certain nombre d’hypothèses simplificatrices :
• Les clients sont honnêtes (ils ne cherchent pas à passer devant ceux qui étaient là avant eux).
• Les clients sont patients (quelle que soit la longueur de la file, ils attendent leur tour et ne
quittent pas la banque avant d’avoir été servis).
• Les clients sont paresseux (si plusieurs caissiers sont libres lors de l’arrivée d’un client,
ce dernier se fera servir par le caissier de plus faible numéro, celui situé le plus près de la
porte d’entrée).
• Les caissiers ne sont jamais fatigués : dès la fin de traitement d’un client, le caissier en
reprend un si la file n’est pas vide.
• On dispose d’un générateur aléatoire permettant de déterminer les arrivées de clients et les
temps de service effectifs des caissiers.
• La simulation repose sur la succession d’événements discrets: on suppose qu’entre deux
événements consécutifs il ne se passe rien de marquant dans le système, donc le temps varie
de manière discrète.
• les attributs ne figurent pas, ils sont à la charge du développeur de chaque classe;
• les passages d’objets se font tous par adresse, même si le modèle UML ne permet pas de le
représenter (même chose en ce qui concerne les objets retournés par une méthode);
• les associations sont traversables par une méthode (accesseur) du même nom que le rôle. Par
exemple, pour accéder à la file d’attente depuis la banque b, on écrit en C++ b->fileAttente();
Ces méthodes ne sont pas représentées dans le modèle de conception ci-dessous (mais doivent
bien sûr être définies!):
2
Poisson DiscreteSimulation Event
0..*
init() DiscreteSimulation() Event(h : double,s : DiscreteSimulation)
next(moy : double = 1) : double add(e : Event) time() : double
run() fire(
time() : double
Client
Client(ha : double) Depart Arrivee
heureArrivee() : double
Depart(h : double,s : DiscreteSimulation,c : Caissier) Arrivee(h : double,s : DiscreteSimulation)
fire( fire(
0..*
FileAttente
Banque Caissier
FileAttente(bq : Banque)
Banque() Caissier(tms : double,bq : Banque)
estVide() : bool
unCaissierLibre() : Caissier N nbClientsServis() : int
longueurMax() : int
nbClientsServis() : int estLibre() : bool
longueurMoyenne() : double
dureePrevue() : double liberer()
tempsMoyenAttente() : double
dureeReelle() : double tauxOccupation() : double
ajouter(c : Client)
tempsMoyenEntreArrivees() : double servir(c : Client)
retirer() : Client
#ifndef __Event_h
#define __Event_h
#include <iostream>
#include <string>
using namespace std;
class DiscreteSimulation;
/*******************************
3
* Event is the abstract root for all event types.
* It only publishes the time at which it is to fired, and the
* fire() method (abstract).
*******************************/
class Event {
protected:
const double _time;
DiscreteSimulation *ds;
public:
Event(double h, DiscreteSimulation *s) : _time(h) { ds = s; }
double time() { return _time; }
virtual void fire() = 0;
virtual string type() = 0;
friend ostream& operator << (ostream& o, Event& e) {
return o << e.type() << "@" << e.time();
}
};
#endif
/****************************************************************************/
/****************************************************************************
*
* $Id: DiscreteSimulation.C 1667 2009-03-10 14:09:08Z phil $
*
****************************************************************************/
static char rcsId[] = "@(#) $Id: DiscreteSimulation.C 1667 2009-03-10 14:09:08Z phil $";
#include "DiscreteSimulation.h"
/***************************************************************************
* Implementation of the DiscreteSimulation class.
* The insertion of an Event
* is extremely simple, since the map used to store the events is
* "naturally" sorted !
* The run() method iteratively removes the first (that is, "chronologically"
* next) event from the map, fires it and deletes it. It stops when the
* map is empty.
***************************************************************************/
void DiscreteSimulation :: add (Event *e) {
events.insert(e);
}
4
Event *e = (*(events.begin()));
events.erase(events.begin());
_time = e->time();
e->fire();
// cout << *e << endl; // uncomment to debug
delete e;
}
}
/***************************************************************************/
L’implémentation
/****************************************************************************
*
* $Id: DiscreteSimulation.C 1667 2009-03-10 14:09:08Z phil $
*
****************************************************************************/
static char rcsId[] = "@(#) $Id: DiscreteSimulation.C 1667 2009-03-10 14:09:08Z phil $";
#include "DiscreteSimulation.h"
/***************************************************************************
* Implementation of the DiscreteSimulation class.
* The insertion of an Event
* is extremely simple, since the map used to store the events is
* "naturally" sorted !
* The run() method iteratively removes the first (that is, "chronologically"
* next) event from the map, fires it and deletes it. It stops when the
* map is empty.
***************************************************************************/
void DiscreteSimulation :: add (Event *e) {
events.insert(e);
}
/***************************************************************************/
5
3.2 La classe Arrivee
L’interface
/****************************************************************************
*
* $Id: Arrivee.C 1664 2009-03-05 14:52:22Z phil $
*
****************************************************************************/
static char rcsId[] = "@(#) $Id: Arrivee.C 1664 2009-03-05 14:52:22Z phil $";
#include <iostream>
#include "Arrivee.h"
#include "Client.h"
#include "Caissier.h"
#include "FileAttente.h"
#include "Poisson.h"
#include "Banque.h"
using namespace std;
/***************************************************************************
* lors d’une arrivee :
* - on cree le client
* - on calcule l’heure de la prochaine arrivee
* - si cette heure est < duree prevue, on cree la prochaine arrivee
* et on l’ajoute a l’echeancier
* - si un caissier est libre, il sert le client
* - sinon, le client fait la queue dans la file d’attente
***************************************************************************/
void Arrivee :: fire() {
// cerr << "Il est " << _time << " : arrivee\n";
Banque * s = (Banque*) ds;
Client * c = new Client(_time,s);
double next = _time + Poisson::next(s->tempsMoyenEntreArrivees());
if (next < s->dureePrevue())
s->add(new Arrivee(next,s));
Caissier * cs = s->unCaissierLibre();
if (cs)
cs->servir(c);
else
s->fileAttente()->ajouter(c);
}
/***************************************************************************/
L’implémentation
/****************************************************************************
*
* $Id: Arrivee.C 1664 2009-03-05 14:52:22Z phil $
*
****************************************************************************/
6
static char rcsId[] = "@(#) $Id: Arrivee.C 1664 2009-03-05 14:52:22Z phil $";
#include <iostream>
#include "Arrivee.h"
#include "Client.h"
#include "Caissier.h"
#include "FileAttente.h"
#include "Poisson.h"
#include "Banque.h"
using namespace std;
/***************************************************************************
* lors d’une arrivee :
* - on cree le client
* - on calcule l’heure de la prochaine arrivee
* - si cette heure est < duree prevue, on cree la prochaine arrivee
* et on l’ajoute a l’echeancier
* - si un caissier est libre, il sert le client
* - sinon, le client fait la queue dans la file d’attente
***************************************************************************/
void Arrivee :: fire() {
// cerr << "Il est " << _time << " : arrivee\n";
Banque * s = (Banque*) ds;
Client * c = new Client(_time,s);
double next = _time + Poisson::next(s->tempsMoyenEntreArrivees());
if (next < s->dureePrevue())
s->add(new Arrivee(next,s));
Caissier * cs = s->unCaissierLibre();
if (cs)
cs->servir(c);
else
s->fileAttente()->ajouter(c);
}
/***************************************************************************/
/****************************************************************************
*
* $Id: Banque.C 1664 2009-03-05 14:52:22Z phil $
*
****************************************************************************/
static char rcsId[] = "@(#) $Id: Banque.C 1664 2009-03-05 14:52:22Z phil $";
#include "Banque.h"
#include "FileAttente.h"
#include "Caissier.h"
#include "Poisson.h"
#include "Arrivee.h"
7
/***************************************************************************
* La banque est responsable de la creation des caissiers et de la file.
* dans cette version, pas de mise sous forme canonique
***************************************************************************/
Banque :: Banque(double dp, int nbC, double *tms, double tma) {
_file = new FileAttente(this);
_caissiers = new Caissier*[nbC];
_nbCaissiers = nbC;
_dureePrevue = dp;
_tempsMoyenEntreArrivees = tma;
add(new Arrivee(Poisson::next(tma),this));
Poisson::init(); // initialisation generateur aleatoire
for (int i=0; i<nbC;i++)
_caissiers[i] = new Caissier(tms[i],this);
}
/***************************************************************************
* retourne le premier caissier disponible, ou 0 si tous pris
* (implemente donc l’hypothese du client paresseux)
***************************************************************************/
Caissier * Banque :: unCaissierLibre() {
for (int i = 0; i < _nbCaissiers; i++)
if (_caissiers[i]->estLibre()) return _caissiers[i];
return 0;
}
/***************************************************************************
* somme des clients servis par les differents caissiers
***************************************************************************/
int Banque :: nbClientsServis() {
int res = 0;
for (int i = 0; i< _nbCaissiers; i++) {
res += _caissiers[i]->nbClientsServis();
}
return res;
}
/***************************************************************************/
L’implémentation
/****************************************************************************
*
* $Id: Banque.C 1664 2009-03-05 14:52:22Z phil $
*
****************************************************************************/
static char rcsId[] = "@(#) $Id: Banque.C 1664 2009-03-05 14:52:22Z phil $";
#include "Banque.h"
#include "FileAttente.h"
#include "Caissier.h"
8
#include "Poisson.h"
#include "Arrivee.h"
/***************************************************************************
* La banque est responsable de la creation des caissiers et de la file.
* dans cette version, pas de mise sous forme canonique
***************************************************************************/
Banque :: Banque(double dp, int nbC, double *tms, double tma) {
_file = new FileAttente(this);
_caissiers = new Caissier*[nbC];
_nbCaissiers = nbC;
_dureePrevue = dp;
_tempsMoyenEntreArrivees = tma;
add(new Arrivee(Poisson::next(tma),this));
Poisson::init(); // initialisation generateur aleatoire
for (int i=0; i<nbC;i++)
_caissiers[i] = new Caissier(tms[i],this);
}
/***************************************************************************
* retourne le premier caissier disponible, ou 0 si tous pris
* (implemente donc l’hypothese du client paresseux)
***************************************************************************/
Caissier * Banque :: unCaissierLibre() {
for (int i = 0; i < _nbCaissiers; i++)
if (_caissiers[i]->estLibre()) return _caissiers[i];
return 0;
}
/***************************************************************************
* somme des clients servis par les differents caissiers
***************************************************************************/
int Banque :: nbClientsServis() {
int res = 0;
for (int i = 0; i< _nbCaissiers; i++) {
res += _caissiers[i]->nbClientsServis();
}
return res;
}
/***************************************************************************/
/****************************************************************************
*
* $Id: Depart.C 1665 2009-03-05 16:02:45Z phil $
*
****************************************************************************/
static char rcsId[] = "@(#) $Id: Depart.C 1665 2009-03-05 16:02:45Z phil $";
9
#include <iostream>
#include "Depart.h"
#include "Banque.h"
#include "Client.h"
#include "Caissier.h"
#include "FileAttente.h"
using namespace std;
/***************************************************************************
* lors de la fin du service d’un caissier,
* - si la file est vide, le caissier attend
* - sinon,
* - le premier client quitte la file
* - le caissier le sert
***************************************************************************/
void Depart :: fire() {
// cerr << "Il est " << _time << " : depart\n";
Banque * s = (Banque*) ds;
if (s->fileAttente()->estVide()) _caissier->liberer();
else {
Client * c = s->fileAttente()->retirer();
_caissier->servir(c);
delete c;
}
}
/***************************************************************************/
L’implémentation
/****************************************************************************
*
* $Id: Depart.C 1665 2009-03-05 16:02:45Z phil $
*
****************************************************************************/
static char rcsId[] = "@(#) $Id: Depart.C 1665 2009-03-05 16:02:45Z phil $";
#include <iostream>
#include "Depart.h"
#include "Banque.h"
#include "Client.h"
#include "Caissier.h"
#include "FileAttente.h"
using namespace std;
/***************************************************************************
* lors de la fin du service d’un caissier,
* - si la file est vide, le caissier attend
* - sinon,
* - le premier client quitte la file
* - le caissier le sert
10
***************************************************************************/
void Depart :: fire() {
// cerr << "Il est " << _time << " : depart\n";
Banque * s = (Banque*) ds;
if (s->fileAttente()->estVide()) _caissier->liberer();
else {
Client * c = s->fileAttente()->retirer();
_caissier->servir(c);
delete c;
}
}
/***************************************************************************/
/****************************************************************************
*
* $Id: FileAttente.C 1664 2009-03-05 14:52:22Z phil $
*
****************************************************************************/
static char rcsId[] = "@(#) $Id: FileAttente.C 1664 2009-03-05 14:52:22Z phil $";
#include "FileAttente.h"
#include "Banque.h"
#include "Client.h"
#include "Caissier.h"
/***************************************************************************
* constructeur:
* mise a zero des parametres internes, memorisation du lien retour
* vers la banque
***************************************************************************/
FileAttente :: FileAttente(Banque* b) {
_banque = b;
_lMax = 0;
_surface = _lMoy = _sommeTempsAttente = _precedent = 0.0;
}
/***************************************************************************
* valeur moyenne : l’integrale temporelle de la longueur sur la duree reelle
***************************************************************************/
double FileAttente :: longueurMoyenne() {
return _surface / _banque->dureeReelle();
}
/***************************************************************************
* l’ajout physique du client dans la file s’accompagne:
11
* - d’une MAJ de la longueur max le cas echeant
* - de l’ajout d’une portion de surface a la valeur de l’integrale
***************************************************************************/
void FileAttente :: ajouter(Client* c) {
int s = _clients.size();
double h = _banque->time();
_surface += s * (h - _precedent); // valeur du rectangle a ajouter
_precedent = h; // memorisation du dernier evt file
_clients.push_back(c);
if (s >= _lMax) _lMax++;
}
/***************************************************************************
* le retrait physique du client de tete s’accompagne:
* - de l’ajout d’une portion de surface a la valeur de l’integrale
***************************************************************************/
Client * FileAttente :: retirer() {
int s = _clients.size();
double h = _banque->time();
Client * res = _clients.front();
_surface += s * (h - _precedent); // valeur du rectangle a ajouter
_sommeTempsAttente += (h - res->heureArrivee());
_precedent = h; // memorisation du dernier evt file
_clients.pop_front(); // N.B. : retourne void !!
return res;
}
/***************************************************************************
***************************************************************************/
double FileAttente :: tempsMoyenAttente() {
return _sommeTempsAttente / _banque->nbClientsServis();
}
/***************************************************************************/
L’implémentation
/****************************************************************************
*
* $Id: FileAttente.C 1664 2009-03-05 14:52:22Z phil $
*
****************************************************************************/
static char rcsId[] = "@(#) $Id: FileAttente.C 1664 2009-03-05 14:52:22Z phil $";
#include "FileAttente.h"
#include "Banque.h"
#include "Client.h"
#include "Caissier.h"
/***************************************************************************
* constructeur:
12
* mise a zero des parametres internes, memorisation du lien retour
* vers la banque
***************************************************************************/
FileAttente :: FileAttente(Banque* b) {
_banque = b;
_lMax = 0;
_surface = _lMoy = _sommeTempsAttente = _precedent = 0.0;
}
/***************************************************************************
* valeur moyenne : l’integrale temporelle de la longueur sur la duree reelle
***************************************************************************/
double FileAttente :: longueurMoyenne() {
return _surface / _banque->dureeReelle();
}
/***************************************************************************
* l’ajout physique du client dans la file s’accompagne:
* - d’une MAJ de la longueur max le cas echeant
* - de l’ajout d’une portion de surface a la valeur de l’integrale
***************************************************************************/
void FileAttente :: ajouter(Client* c) {
int s = _clients.size();
double h = _banque->time();
_surface += s * (h - _precedent); // valeur du rectangle a ajouter
_precedent = h; // memorisation du dernier evt file
_clients.push_back(c);
if (s >= _lMax) _lMax++;
}
/***************************************************************************
* le retrait physique du client de tete s’accompagne:
* - de l’ajout d’une portion de surface a la valeur de l’integrale
***************************************************************************/
Client * FileAttente :: retirer() {
int s = _clients.size();
double h = _banque->time();
Client * res = _clients.front();
_surface += s * (h - _precedent); // valeur du rectangle a ajouter
_sommeTempsAttente += (h - res->heureArrivee());
_precedent = h; // memorisation du dernier evt file
_clients.pop_front(); // N.B. : retourne void !!
return res;
}
/***************************************************************************
***************************************************************************/
double FileAttente :: tempsMoyenAttente() {
return _sommeTempsAttente / _banque->nbClientsServis();
}
/***************************************************************************/
13
3.6 La classe Poisson
/****************************************************************************
*
* $Id: Poisson.h 975 2007-01-10 11:09:59Z phil $
*
****************************************************************************/
#ifndef __Poisson_h
#define __Poisson_h
/*******************************
* Generateur aleatoire de nombre reels positifs suivant une loi de Poisson.
* la loyenne est fixee par le parametre de la methode de classe next().
* par defaut, la moyenne est 1.0
*******************************/
#include <math.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
class Poisson {
public:
// permet d’avoir des resultats differents a chaque fois
// ou de forcer une meme serie aleatoire (param. seed)
static void init(int seed = 0) {
srandom(seed ? seed : getpid());
}
static double next(double moy = 1.0) {
return -log(((double)random()/RAND_MAX))*moy;
}
};
#endif
/****************************************************************************/
static char rcsId[] = "@(#) $Id: tBanque.C 1664 2009-03-05 14:52:22Z phil $";
#include <iostream>
#include <stdio.h>
#include "Banque.h"
#include "Caissier.h"
#include "FileAttente.h"
14
#include <string.h>
#include <stdlib.h>
using namespace std;
/***************************************************************************
* les valeurs par defaut de la simulation :
* - 2 caissiers (temps de service 1 et 1)
* - duree de 10
* - les clients arrivent tous les 0.6
*
* Ces valeurs peuvent etre modifiees sur la ligne de commande :
* $ banque [ -dp duree ] [[ -nc nbCaissiers ] -ts temps1...tempsN ] [ -ta tempsArrivees ]
* avec
* - -dp pour modifier la duree prevue
* - -nc pour modifier le nombre de caissiers (il faut alors donner les temps
* de service de tous les caissiers)
* - -ts pour modifier les temps moyens de service des caissiers
* - -ta pour modifier le temps moyen entre deux arrivees successives
***************************************************************************/
15
// affichage des valeurs des parametres de la simulation
cout << "duree : " << duree
<< "\nnbc : " << nbCaissiers
<< "\nta : " << tempsArrivees
<< "\ntps : ";
for (int i = 0; i < nbCaissiers ; i++)
cout << temps[i] << ’ ’;
cout << endl;
//////////////////////////////////////////////////////////////////////
/***************************************************************************/
3.8 Le makefile
Seul le début est intéressant, les dépendances générées représentent 10 pages et ne sont pas repro-
duites:
.C.o :
c++ -c $<
.o:
c++ -o $@ $<
ALL = banque
all : $(ALL)
clean :
touch clean simuBanque
16
rm -f clean *.o *.aux *.log *.dvi *.toc *.tex *.tmp *~ 2> /dev/null
rm -rf $(ALL) simuBanque 2>/dev/null
banque : $(OBJS)
c++ -o $@ $(OBJS) && clear && ./banque
############################################################
# gestion des dependances C++
############################################################
depend:
sed ’/^#DEP/q’ < Makefile > mk.tmp
c++ -M *.C >> mk.tmp
mv mk.tmp Makefile
17
Contents
1 Rappel du sujet 1
18