Main Page   Modules   Namespace List   Class Hierarchy   Alphabetical List   Data Structures   File List   Namespace Members   Data Fields   Globals   Related Pages   Examples  

Shop.cpp

In this example a shop which is selling different goods to customers is simulated. Shop-assistants handle the final payment after a customer has gathered its goods from the shelves in the shop. Each shelf is holding a limited amount of one commodity. If a shelf runs out of its commodity a shop-assistant has to restock the shelf. There is also a shopkeeper, who employs the shop-assistants and orders goods, if the store room of the shop is running out of it. Finally, there is a process generating new customers after a random delay.

//----------------------------------------------------------------------------
//      Copyright (C) 2002, 2003 Humboldt-Universitaet zu Berlin
//
//      This library is free software; you can redistribute it and/or
//      modify it under the terms of the GNU Lesser General Public
//      License as published by the Free Software Foundation; either
//      version 2.1 of the License, or (at your option) any later version.
//
//      This library is distributed in the hope that it will be useful,
//      but WITHOUT ANY WARRANTY; without even the implied warranty of
//      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//      Lesser General Public License for more details.
//
//      You should have received a copy of the GNU Lesser General Public
//      License along with this library; if not, write to the Free Software
//      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//----------------------------------------------------------------------------
//
// Includes
//
// We include the main ODEMx header file odemx.h which in
// turn includes the other header files of ODEMx. All
// header files of ODEMx define a macro when included,
// to prevent repetitive inclusion. That means it doesn't
// hurt to include a header file more than ones, because
// subsequent inclusions are ignored.
#include <odemx/odemx.h>

// Apart from ODEMx we use some C++ standard features in
// this simulation.
#include <iostream>
#include <vector>
#include <string>
#include <list>

//
// Namespaces
//
// All classes of ODEMx are placed in the namespace odemx
// to prevent conflicts. In this simulation it is save to
// use the whole odemx namespace.
using namespace odemx;
using namespace std;

//
// Global parameters
//
// To simplify matters we use global parameters in this example.
// That is however not recommended in general. A better place
// for simulation parameters is the specific simulation class.
//
// GOODS contains the different commodities sold in the shop.
static const char* GOODS[] = {"Apple", "Bananas", "Bread", "Butter", "Candies",
                                                          "Cheese", "Chips", "Chocolate", "Mixed-pickles", "Fish",
                                                          "Jam", "Juice", "Lemon", "Lemonade", "Marmalade",
                                                          "Milk", "Orange", "Pepper", "Pineapples", "Pork",
                                                          "Salami", "Salt", "Sugar", "Tomatoes", "Water"};

// NUMBER_OF_GOODS is used in many for-loops as the end-condition.
static const int NUMBER_OF_GOODS = sizeof(GOODS) / sizeof(const char*);

// INIT_STOCK is the initial amount of each good hold in the
// store room.
static const int INIT_STOCK = 50;

// INIT_TILLS is the initial number of tills available.
static const int INIT_TILLS = 5;

// INIT_PERSONAL is the initial number of shop-assistants
// employed by the shopkeeper.
static const int INIT_PERSONAL = 8;

// SHELF_CAPACITY defines the capacity of a shelf. The capacity
// is measured in items, not in size. That means every shelf
// holds up to SHELF_CAPACITY items of its commodity.
static const int SHELF_CAPACITY = 15;

// ACCEPTED_QUEUE_LENGTH defines the queue length after
// which a shop-assistant will help out at the tills.
static const int ACCEPTED_QUEUE_LENGTH = 5;

// ACCEPTED_SHELF_STOCK defines the number of items in
// a shelf after which the shelf has to be refilled.
static const int ACCEPTED_SHELF_STOCK = 5;

// The following parameters define delays for payment,
// for refilling shelves, for taking goods from a shelf, for moving
// between shelves, and they define the time after which the shopkeeper
// is checking the store room of the shop again.
static const SimTime PAYMENT_DELAY = 5.0;
static const SimTime RESTOCK_DELAY = 15.0;
static const SimTime TAKE_DELAY = 1.0;
static const SimTime MOVE_DELAY = 1.0;
static const SimTime STOCKKEEPER_DELAY = 60.0;

//
// Pre-declarations
//
class Shelf;
class Tills;
class Shopkeeper;
class Assistant;
class Customer;
class CustomerGen;

//
// Classes
//

//
// The class ShopSim is derived from Simulation and provides
// access to some important objects of this simulation. All
// classes which have a pointer to ShopSim can use the get*
// methods to get these objects. In addition we could move
// the global parameters to this class. ShopSim also
// does some initialisations in its constructor and the
// initSimulation() function.
// 
class ShopSim : public Simulation
{
        // Shelves in the shop, one for each commodity
        vector<Shelf*>          shelves;
        // Back-store for refilling shelves
        vector<int>                     stock;
        // Customers pay at one of the tills
        Tills*                          tills;

        Shopkeeper*                     boss;
        CustomerGen*            generator;

        // RNGs
        Randint                         *rng5, *rng10, *rng;
        Negexp*                         delay;

        // Assistant-management
        Condq*                          work;

        // Output
        HtmlReport*                     report;
        HtmlTrace*                      tillTrace;

public:
        // Construction and initialisation
        ShopSim();
        virtual void initSimulation();

        // get*() methods
        vector<Shelf*>& getShelves() {return shelves;}
        vector<int>& getStock() {return stock;}
        Tills* getTills() {return tills;}

        Shopkeeper* getBoss() {return boss;}
        CustomerGen* getCustomerGen() {return generator;}

        Idist* getRNG() {return rng;}
        Idist* getRNG5() {return rng5;}
        Idist* getRNG10() {return rng10;}
        Rdist* getDelay() {return delay;}

        Condq* getWork() {return work;}

        Report* getReport() {return report;}
};

//
// Shelf represents a shelf in our shop. Every shelf
// contains one commodity and provides methods for
// customers to take goods. The shop-assistants refill
// a shelf if its getting out of stock.
// The management of commodity items hold in a shelf
// is realised with the ODEMx standard component Res.
//
class Shelf
{
        // Pointer to ShopSim
        ShopSim* sim;
        
        // The type of commodity presented in this shelf
        int type;
        
        // The amount of goods left in this shelf
        Res commodity;
        
public:
        // Construction
        Shelf(ShopSim* s, int t);

        // Type of commodity presented in this shelf
        int getType() const {return type;}

        // Customers use this method to take goods.
        int takeCommodity(int n);

        // Assistants use this method to restock the shelf. 
        int fillShelf(int n);

        // Assistants use this method to check the stock.
        int checkStock() const {return commodity.getTokenNumber();}
};

//
// After taking their goods customers go to the tills
// to pay. There are several tills available but a till
// has also to be handled by a shop-assistant to be active.
// The payment is managed with the ODEMx standard component
// Waitq which realises the synchronisation between customers
// giving the money and shop-assistants taking it. The Tills
// also run a statistic about the number of assistants using
// the ODEMx class Accum.
//
class Tills
{
        // Pointer to ShopSim
        ShopSim* sim;

        // Customers wait in a Waitq for service.
        Waitq   queue;

        // Tills runs statistics about the assistants.
        Accum* statistic;

        // The number of assistants handling the tills.
        int cashier;

        // The number of tills available.
        int tills;

public:
        // Construction
        Tills(ShopSim* s);

        // Customers use the method pay() to enter the queue (enqueue) at the
        // tills and to pay.
        void pay();

        // Assistants use take to get the next customer.
        Customer* take();

        // The number of shop-assistants who take the payment changes,
        // updated with updatedCashier().
        void updateCashier(int n);

        // get* methods
        int getQueueLength() const {return queue.getWaitingSlaves().size();}
        int getCashier() const {return cashier;}
        int getTills() const {return tills;}
};

//
// The shopkeeper is responsible for the employment of shop-assistants
// and the ordering of goods. The class Shopkeeper is derived from
// Process and defines the behaviour of the shopkeeper by implementing
// the function main().
//
class Shopkeeper : public Process
{
public:
        // Construction
        Shopkeeper(ShopSim* sim);

        // Behaviour
        virtual int main();
};

//
// Shop-assistants represent the work-force driving the shop. They
// refill the shelves or handle a till. The class Assistant defines
// the behaviour of the shop-assistants by implementing main().
// Shop-assistants decide on their own which task they fulfil
// next (handle a till or refill shelves). If neither is required
// they wait for work in a conditional queue provided by ODEMx and
// kept in the ShopSim class (ShopSim::work).
//
class Assistant : public Process
{
public:
        // Construction
        Assistant(ShopSim* sim);

        // Behaviour
        virtual int main();

        // chooseWork and work are used to find the next task for an
        // assistant or to wait for work.
        int chooseWork();
        bool work();
};

//
// This class represents the customers in this simulation. A
// customer has (random) demand which he/she tries to fulfil
// by taking goods from the shelves. Finally, a customer pays
// the taken goods at the tills.
//
class Customer : public Process
{
        // The customers demand.
        vector<int>      demand;

        // The goods in the customers basket.
        vector<int>      basket;

public:
        // Construction
        Customer(ShopSim* sim);

        // Behaviour
        virtual int main();
};

//
// The Customers are randomly created by CustomerGen. By this means
// we achieve an approximation of the customer-flow in our shop.
//
class CustomerGen : public Process
{
public:
        // Construction
        CustomerGen(ShopSim* sim);

        // Behaviour
        virtual int main();
};

//
// Implementations of member functions
//

//
// Construction of a ShopSim object itself and some other major
// objects.
//
ShopSim::ShopSim() :
        Simulation("ShopSim"),
        shelves(NUMBER_OF_GOODS),
        stock(NUMBER_OF_GOODS)
{
        // HtmlReport and HtmlTrace are used to gather the results
        // of a simulation. The data are written in the two files
        // 'ShopSim-Report.html' and 'ShopSim-TillTrace.html'.
        report = new HtmlReport(this, "ShopSim-Report.html");
        tillTrace = new HtmlTrace(this, "ShopSim-TillTrace.html");

        // HtmlTrace fortunately provides a filter to reduce the
        // output. We are just taking logging information from the tills.
        // Without such precautions we would get a 24 Mbytes trace.
        tillTrace->setFilter("none; sl:Tills");

        // To receive the simulation events we have to register our
        // trace to the simulation.
        addConsumer(tillTrace);

        // Create and initialise shelves, one shelf for each commodity.
        for (int i=0; i<NUMBER_OF_GOODS; ++i)
        {
                shelves[i] = new Shelf(this, i);
                stock[i] = INIT_STOCK;
        }

        // Create the tills, the boss, and the customer generator.
        tills = new Tills(this);
        boss = new Shopkeeper(this);
        generator = new CustomerGen(this);

        // Create the random number generators.
        // Randint and Negexp are provided by ODEMx.
        rng = new Randint(this, "RNG", 0, NUMBER_OF_GOODS);
        rng5 = new Randint(this, "RNG", 0, 5);
        rng10 = new Randint(this, "RNG", 0, 10);
        delay = new Negexp(this, "Delay", 0.125);

        // We also want to have a look at the statistics from our RNGs.
        // To get these we have to add them to the HtmlReport object. report
        // will gather information only from registered objects.
        report->addProducer(rng);
        report->addProducer(rng5);
        report->addProducer(rng10);
        report->addProducer(delay);

        // Finally, the Condq work is created and added to the report.
        work = new Condq(this, "Work");
        report->addProducer(work);
}

//
// initSimulation is called before the simulation starts. We
// use this function to activate the boss and the customer
// generator. They in turn create the other processes.
// The initSimulation function is some sort of an entry-point
// of a simulation. It is called before the simulation
// starts and is the best place to activate the first processes
// of a simulation.
// 
void ShopSim::initSimulation()
{
        boss->activate();
        generator->activate();
}

//
// Construction of a Shelf object.
//
Shelf::Shelf(ShopSim* s, int t) :
        sim(s),
        type(t),
        commodity(s, (string("Shelf_") + GOODS[t]).c_str(), 0, SHELF_CAPACITY)
{
        // Shelf is using a Res object to manage the amount of goods
        // available. Res is providing some report about its usage.
        // To add this report to our HtmlReport we register the Res
        // object.
        s->getReport()->addProducer(&commodity);
}

//
// fillShelf is used by the shop-assistants to restock the shelf.
// In this version the function is only a wrapper to the release
// method of the Res object which is actually managing the available
// goods.
//
int Shelf::fillShelf(int n)
{
        commodity.release(n);
        return commodity.getTokenNumber();
}

//
// takeCommodity is used by the customers to get the goods. We
// could just relay the call to commodity.acquire(). But than a
// customer would wait until there are enough items available.
// Instead we signal the shop-assistants the need to refill a
// shelf and give the customer only the items available.
//
int Shelf::takeCommodity(int n)
{
        int avail = commodity.getTokenNumber();

        if (avail > n)
        {
                commodity.acquire(n);
                return n;
        }
        else
        {
                commodity.acquire(avail);
                sim->getWork()->signal();
                return avail;
        }
}

//
// Construction of the Tills object.
//
Tills::Tills(ShopSim* s) :
        sim(s),
        queue(s, "Tills"),
        cashier(0),
        tills(INIT_TILLS)
{
        // Tills is providing some additional statistics about
        // the number of assistants handling the tills.
        statistic = new Accum(sim, "Tills statistics");

        // We add these statistics along with the information from
        // the utilised Waitq object to the report.
        s->getReport()->addProducer(&queue);
        s->getReport()->addProducer(statistic);
}

//
// Payment is done by the functions pay and take. There are
// two objects working together to handle payment. A customer
// giving the money and a shop-assistant taking it. Both have to
// be synchronised. Tills uses the standard ODEMx synchronisation
// object Waitq to do this.
//
void Tills::pay()
{
        // Every time a customer wants to pay, waiting assistants
        // are signalled.
        sim->getWork()->signal();

        // The synchronisation between a customer (who wants
        // to pay) and an assistant is realised with Waitq. Waitq
        // manages not only the synchronisation but also provides
        // statistics. The wait() function used here adds the
        // current process (the one that has just called pay()) to the
        // queue.
        queue.wait();
}

//
// Assistants use take to get the next customer.
//
Customer* Tills::take()
{
        Process* p = 0;

        // The coopt() function of Waitq returns the next Customer 
        // in the queue(sorted according to priority and time of arrival).
        // If there is no customer left the current process
        // (the one that has just called take()) waits.
        p = queue.coopt();

        return (Customer*) p;
}

//
// The number of assistants working at the tills is updated
// with this function. In this function the statistic about
// assistants handling tills is also updated.
//
void Tills::updateCashier(int n)
{
        cashier += n;
        statistic->update(cashier);
}

//
// Construction of Assistant objects.
//
Assistant::Assistant(ShopSim* sim) :
        Process(sim, "Assistant")
{}

//
// Assistant is derived from Process. That means it has to
// provide its own main() function, where it defines the actions
// done by an Assistant.
//
int Assistant::main()
{
        //
        // At first we cache pointers to our simulation
        // and to the tills.
        //
        ShopSim* sim = (ShopSim*)getSimulation();
        Tills* tills = sim->getTills();

        while (true)
        {
                //
                // Wait for something to do:
                // Assistants have to do two different tasks.
                // If required they handle a till to take the 
                // money from the customers. Otherwise they go 
                // through the shop and refill empty shelves.
                // If neither is required they wait for work.
                // ShopSim uses the ODEMx synchronisation object
                // Condq to keep track of waiting Assistants.
                // Condq allows processes to wait for certain
                // conditions defined by a condition call-back
                // function. Assistant's member function work()
                // was designed to define the condition when an
                // Assistant has to act.
                // The first real action of an assistant is to 
                // wait for a task.
                //
                sim->getWork()->wait((Condition)&Assistant::work);

                //
                // Choose your work:
                // The function chooseWork() determines what has
                // to be done. It returns 1 if an assistant is 
                // needed at the tills and 2 if a shelf has to be
                // refilled.
                if (chooseWork()==1)
                {               
                        //
                        // Cashier task
                        //

                        // update number of assistants at the tills
                        tills->updateCashier(1);
                        
                        // serve customers as long as there are some
                        while (tills->getQueueLength()>0)
                        {
                                // get the next customer in queue
                                Customer* c = tills->take();

                                // payment does take some time
                                holdFor(PAYMENT_DELAY);

                                // finally, activate the customer
                                c->activate();
                        }

                        // update number of assistants at the tills
                        tills->updateCashier(-1);
                }
                else
                {
                        //
                        // Restock task
                        //

                        // The assistant goes through all shelves and
                        // refills if necessary and possible.
                        vector<Shelf*>::iterator i;
                        for (i=sim->getShelves().begin(); i!=sim->getShelves().end(); ++i)
                        {
                                // We cache some pointer.
                                Shelf* shelf = *i;
                                vector<int>& stock = sim->getStock();
                                int type = shelf->getType();

                                if (shelf->checkStock() < ACCEPTED_SHELF_STOCK &&
                                        stock[type] > 0)
                                {
                                        // A shelf is filled up to its capacity
                                        // if possible.
                                        int n = SHELF_CAPACITY - shelf->checkStock();
                                        if (stock[type] < n)
                                                n = stock[type];

                                        shelf->fillShelf(n);
                                        stock[type] -= n;

                                        // refilling takes time
                                        holdFor(RESTOCK_DELAY);
                                }
                        }
                }
        }

        // This one will never be reached.
        return 0;
}

//
// chooseWork() decides which task should be done.
//
int Assistant::chooseWork()
{
        ShopSim* sim = (ShopSim*)getSimulation();
        vector<Shelf*>::iterator i;

        // If the queue at the tills has exceeded an acceptable
        // length and we have a free till without an assistant,
// the assistant should work at it.
        if (sim->getTills()->getQueueLength() >= ACCEPTED_QUEUE_LENGTH && 
                sim->getTills()->getCashier() < sim->getTills()->getTills())
                return 1;

        // Otherwise, if there is a shelf out of stock while we have
        // still goods left, the assistant should go on the refill-route.
        for (i=sim->getShelves().begin(); i!=sim->getShelves().end(); ++i)
        {
                if ((*i)->checkStock()<ACCEPTED_SHELF_STOCK &&
                        (sim->getStock())[(*i)->getType()] > 0)
                        return 2;
        }

        // If neither is required the assistant should wait.
        return 0;
}

//
// The condition call-back work() uses the function chooseWork()
// to find something to do.
//
bool Assistant::work()
{
        return chooseWork()!=0;
}

//
// Construction of the Shopkeeper object.
//
Shopkeeper::Shopkeeper(ShopSim* sim) : 
        Process(sim, "Shopkeeper")
{}

//
// Like Assistant Shopkeeper is a Process and has to
// define its behaviour in the main() function.
//
int Shopkeeper::main()
{
        ShopSim* sim = (ShopSim*)getSimulation();
        Tills* tills = sim->getTills();

        //
        // The first task of the shopkeeper is to employ
        // some shop-assistants.
        //
        int i;
        for (i=0; i<INIT_PERSONAL; ++i)
        {
                Assistant* person = new Assistant(sim);
                person->activate();
        }

        //
        // Afterwards the only thing the shopkeeper has to do 
        // is to look after the stock of goods in his shop.
        //
        while (true)
        {
                //
                // Order goods:
                // We check all goods; if something runs out
                // we fill it up again.
                //
                vector<int>& stock = sim->getStock();
                for (i=0; i<NUMBER_OF_GOODS; ++i)
                {
                        if (stock[i]==0) stock[i]=INIT_STOCK;
                }

                // 
                // Wait some time
                //
                holdFor(STOCKKEEPER_DELAY);
        }

        return 0;
}

//
// Construction of the Customer objects.
//
Customer::Customer(ShopSim* sim) :
        Process(sim , "Customer"),
        demand(NUMBER_OF_GOODS),
        basket(NUMBER_OF_GOODS)
{}

//
// The Customer wants a random amount of a random number of
// commodities. The customer tries to take the desired items
// from the shelves, pays at the tills, and finally, leaves the
// simulation.
//
int Customer::main() 
{
        // We cache some pointer.
        ShopSim* sim = (ShopSim*)getSimulation();
        vector<Shelf*>& shelves = sim->getShelves();
        Tills* tills = sim->getTills();
        Idist* rng = sim->getRNG();
        Idist* rng5 = sim->getRNG5();
        Idist* rng10 = sim->getRNG10();

        //
        // Init wish-list
        //
        int i;

        // A customer wants up to 10 different types of goods.
        int numberOfItems = rng10->sample();
        for (i=0; i<numberOfItems; ++i)
        {
                int type = rng->sample();
                int amount = rng5->sample();

                demand[type] = amount;
        }

        // 
        // Get goods from shelves
        //
        for (i=0; i<NUMBER_OF_GOODS; ++i)
        {
                if (demand[i]==0)
                {
                        holdFor(MOVE_DELAY);
                        continue;
                }

                Shelf* shelf = shelves[i];
                basket[i] = shelf->takeCommodity(demand[i]);

                holdFor(TAKE_DELAY + MOVE_DELAY);
        }

        //
        // Pay at a till
        //
        tills->pay();

        return 0;
}

//
// Construction of the CustomerGen object.
//
CustomerGen::CustomerGen(ShopSim* sim) : 
        Process(sim, "CustomerGen")
{}

//
// CustomerGen creates Customers.
//
int CustomerGen::main()
{
        // Cash the ShopSim pointers.
        ShopSim* sim = (ShopSim*)getSimulation();
        Customer* c = 0;
        SimTime delay;

        // CustomerGen adds another statistic to our report.
        // It uses the ODEMx class Histo to fill a histogram
        // about the actual delays. It doesn't matter to the
        // report when another participant is added. The
        // content of a report is defined by all registered
        // report producers at the moment the function
        // generateReport() is called. (In this example we
        // don't care about memory leaks.)
        Histo* histo = new Histo(sim,"CustomerGen Delays", 0.0, 60.0, 10);
        sim->getReport()->addProducer(histo);

        while (true)
        {
                // Create and activate a new Customer.
                c = new Customer(sim);
                c->activate();

                // Wait a random time and update statistic.
                delay = sim->getDelay()->sample();
                histo->update(delay);
                holdFor(delay);
        }

        return 0;
}

//
// Main:
// In the global main() function we finally create an object
// of the type ShopSim, start the trace, run the simulation for
// two simulated days, stop trace and generate the report.
//
int main(int argc, char* argv[])
{
        //
        // Using a local variable to hold the ShopSim object is
        // save because the stack of the global main() function
        // won't be touched during a simulation. The stacks of
        // the Process main() functions however are not available
        // to each other or to the global main(). You can of course
        // use dynamic allocation as seen in CustomerGen::main().
        //
        ShopSim sim;
        
        //
        // Logging of simulation events is not started before
        // startTrace() is called. If you want to break the
        // logging temporarily use pauseTrace() and continueTrace().
        //
        sim.startTrace();

        //
        // The Simulation class provides different functions to
        // compute the simulation. runUntil() starts computation
        // until the given simulation time is reached. We have to
        // use this method because our simulation does not terminate
        // on its own. Which would be the case, if a Process calls
        // exitSimulation() at ShopSim or if there would be no
        // process scheduled for execution.
        //
        sim.runUntil(60 * 24 * 2);

        //
        // The stopTrace() function is terminating the trace. It
        // cannot be started again afterwards!
        //
        sim.stopTrace();

        //
        // The report is generated at this moment. All registered
        // objects must be alive and ready to add their parts to
        // the report.
        //
        sim.getReport()->generateReport();

        return 0;
}


Generated on Mon Aug 11 10:36:06 2003 for ODEMx by doxygen1.3