SSim C++ API documentation (v. 1.7.4)

bs.cc

This extensive example implements a simulation of a simple supply-chain system with a manufacturer, a retailer, and a number of buyers.

This uses reactive processes.

Thanks to Matt Rutherford for creating this example, and to Cyrus Hall for updating it.

00001 //
00002 // bs.cc
00003 //
00004 // this example uses reactive processes to implement a contrived
00005 // commerce scenario where there are buyers, retailers and
00006 // manufacturers that are interacting with each other.
00007 //
00008 #include <cstdlib>
00009 #include <iostream>
00010 #include <map>
00011 #include <queue>
00012 #include <set>
00013 #include <siena/ssim.h>
00014 #include <sstream>
00015 #include <string>
00016 
00017 using namespace ssim;
00018 using namespace std;
00019 
00020 int get_rand( int a, int b )
00021 {
00022   return( a + (int)(((double)rand()/(RAND_MAX+1.0))*(double)(b-a+1)) );
00023 }
00024 
00025 //
00026 // events (messages) sent between processes
00027 //
00028 class Order : public Event
00029 {
00030 public:
00031   Order(const string& item, int quantity, int deliverToId)
00032     : m_item(item), m_orderId(rand()), m_quantity(quantity),
00033       m_deliverToId(deliverToId) {}
00034   
00035   Order(const Order& ord)
00036     : m_item(ord.m_item), m_orderId(ord.m_orderId),
00037       m_quantity(ord.m_quantity), m_deliverToId(ord.m_deliverToId) {}
00038 
00039   virtual ~Order() {}
00040 
00041   const string& getItem() const
00042   {
00043     return m_item;
00044   }
00045 
00046   int getOrderId() const
00047   {
00048     return m_orderId;
00049   }
00050   
00051   int getQuantity() const
00052   {
00053     return m_quantity;
00054   }
00055     
00056   ProcessId getDeliverToId() const
00057   {
00058     return m_deliverToId;
00059   }
00060 
00061   string str() const
00062   {
00063     ostringstream oss;
00064     oss << "[Order: item=" << m_item << ",orderId="
00065         << m_orderId << ",quantity=" << m_quantity << ",deliverToId="
00066         << m_deliverToId << "]";
00067     return oss.str();
00068   }
00069 
00070 private:
00071   string m_item;
00072   int m_orderId;
00073   int m_quantity;
00074   ProcessId m_deliverToId;
00075 };
00076           
00077 class Delivery : public Event
00078 {
00079 public:
00080   Delivery(const string& item, int orderId, int quantity)
00081     : m_item(item), m_orderId(orderId), m_quantity(quantity) {}
00082 
00083   virtual ~Delivery() {}
00084 
00085   const string& getItem() const
00086   {
00087     return m_item;
00088   }
00089 
00090   int getOrderId() const
00091   {
00092     return m_orderId;
00093   }
00094 
00095   int getQuantity() const
00096   {
00097     return m_quantity;
00098   }
00099 
00100   string str() const
00101   {
00102     ostringstream oss;
00103     oss << "[Delivery: item=" << m_item << ",orderId="
00104         << m_orderId << ",quantity=" << m_quantity << "]";
00105     return oss.str();
00106   }
00107     
00108 private:
00109   string m_item;
00110   int m_orderId;
00111   int m_quantity;
00112 };
00113 
00114 class Manufacturer : public Process
00115 {
00116 public:
00117   Manufacturer() : m_id(NULL_PROCESSID) {}
00118 
00119   virtual ~Manufacturer() {}
00120 
00121   virtual void process_event(const Event *e)
00122   {
00123     const Order* ord;
00124     
00125     if ((ord = dynamic_cast<const Order *>(e)) != 0)
00126       {
00127         //
00128         // if we have received an Order event
00129         //
00130         int delay = m_delayMap[ord->getItem()];
00131         
00132         cout << "Manufacturer(" << m_id << ") received: " << ord->str() << endl;
00133         Delivery* del = new Delivery(ord->getItem(), ord->getOrderId(),
00134                                      ord->getQuantity());
00135         // add a random extra delay
00136         Sim::signal_event(ord->getDeliverToId(), del, delay + get_rand(0, 10));
00137       }
00138     else if (dynamic_cast<const Delivery *>(e) != 0)
00139       {
00140         cerr << "Manufacturer(" << m_id << ") received a delivery event!" << endl;
00141       }
00142     else
00143       {
00144         cerr << "Manufacturer(" << m_id << ") received unknown event" << endl;
00145       }
00146   }
00147   
00148   void addItem(const string& item, int delay)
00149   {
00150     cout << "Manufacturer(" << m_id << ") -> " << item << " takes "
00151          << delay << " days to make" << endl;
00152     m_delayMap[item] = delay;
00153   }
00154     
00155   void setId(ProcessId id)
00156   {
00157     m_id = id;
00158   }
00159     
00160   ProcessId getId() const
00161   {
00162     return m_id;
00163   }
00164   
00165 private:
00166   ProcessId m_id;
00167   map<string,int> m_delayMap;
00168 };
00169 
00170 struct item_count
00171 {
00172   int stock;
00173   int held;
00174   int ordered;
00175   int claimed;
00176 };
00177 
00178 class Retailer : public Process
00179 {
00180 public:
00181   Retailer(ProcessId manufac_id) : m_id(NULL_PROCESSID), m_mid(manufac_id) {}
00182 
00183   virtual ~Retailer() {}
00184 
00185   virtual void process_event(const Event *e)
00186   {
00187     const Delivery * d;
00188     const Order * o;
00189     if ((d = dynamic_cast<const Delivery *>(e)))
00190       {
00191         handleDelivery(d);
00192       }
00193     else if ((o = dynamic_cast<const Order *>(e)))
00194       {
00195         handleOrder(o);
00196       }
00197     else
00198       {
00199         cerr << "Retailer(" << m_id << ") received unknown event." << endl;
00200       }
00201   }
00202   
00203   void addItem(const string& item, int quantity )
00204   {
00205     cout << "Retailer(" << m_id << ") -> has " << quantity << " " << item
00206          << "(s)" << endl;
00207     item_count ic;
00208     ic.stock = quantity;
00209     ic.held = 0;
00210     ic.ordered = 0;
00211     ic.claimed = 0;
00212     m_stockMap[item] = ic;
00213   }
00214 
00215   ProcessId getId() const
00216   {
00217     return m_id;
00218   }
00219     
00220   void setId(ProcessId id)
00221   {
00222     m_id = id;
00223   }
00224   
00225 private:
00226   void handleDelivery(const Delivery* del)
00227   {
00228     item_count ic = m_stockMap[del->getItem()];
00229     int q_delivered = del->getQuantity();
00230 
00231     cout << "Retailer(" << m_id << ") received: " << del->str() << endl;
00232     /*
00233       cout << "Retailer(" << m_id << ") s=" << ic.stock << " h="
00234       << ic.held << " c=" << ic.claimed << " o=" << ic.ordered
00235       << endl;
00236     */
00237     
00238     // a delivery of X widgets arrived, we need to combine with
00239     // the widgets that are being held and start sending deliveries
00240     // out to the buyer.
00241     // !!!  Maybe we should implement the deliveries to buyers as
00242     //      a timeout?  If nothing else, this would demonstrate that
00243     //      functionality  --  CPH
00244 
00245     ic.ordered -= q_delivered;
00246 
00247     queue<Order*>& oqRef = m_pendingOrders[del->getItem()];
00248     queue<Order*> newQ;
00249     while(!oqRef.empty())
00250       {
00251         Order *ord = oqRef.front();
00252         oqRef.pop();
00253 
00254         int q_needed = ord->getQuantity();
00255         if((ic.stock + ic.held + q_delivered) >= q_needed)
00256           {
00257             // we are going to fulfill this order one way or the other...
00258             Delivery *del = new Delivery(ord->getItem(), ord->getOrderId(),
00259                                          ord->getQuantity());
00260             Sim::signal_event(ord->getDeliverToId(), del, 1);
00261             delete ord;
00262             
00263             // Just have to account for it, first from held
00264             q_needed -= ic.held;
00265             if(q_needed < 0)
00266               {
00267                 ic.held = -1 * q_needed;
00268                 q_needed = 0;
00269               }
00270             else
00271               {
00272                 ic.held = 0;
00273               }
00274             
00275             if(q_needed != 0)
00276               {
00277                 // second from stock
00278                 q_needed -= ic.stock;
00279                 if(q_needed < 0)
00280                   {
00281                     ic.stock = -1 * q_needed;
00282                     q_needed = 0;
00283                   }
00284                 else
00285                   {
00286                     ic.stock = 0;
00287                   }
00288                 
00289                 if(q_needed != 0)
00290                   {
00291                     // third from delivery
00292                     if(q_delivered < q_needed)
00293                       {
00294                         cerr << "LOGIC ERROR: q_needed=" << q_needed << endl;
00295                         break;
00296                       }
00297                     
00298                     q_delivered -= q_needed;
00299                     ic.claimed -= q_needed;
00300                   }
00301               }
00302           }
00303         else
00304           { // we can't fulfill the order yet
00305             newQ.push(ord);
00306           }
00307       }
00308     m_pendingOrders[del->getItem()] = newQ;
00309     
00310     // if q_delivered is still positive, it goes into stock
00311     ic.stock += q_delivered;
00312 
00313     /*
00314       cout << "Retailer(" << m_id << ") s=" << ic.stock << " h=" << ic.held
00315       << " c=" << ic.claimed << " o=" << ic.ordered << endl;
00316     */
00317     m_stockMap[del->getItem()] = ic;
00318   }
00319 
00320   void handleOrder(const Order* ord)
00321   {
00322     /*
00323      * an order was received by this retailer, it needs to 
00324      * check if it can fulfill the order out of stock. If
00325      * not, it needs to order the difference, and put
00326      * the order in a holding area until it gets delivery.
00327      */
00328     int q_needed = ord->getQuantity();
00329     item_count ic = m_stockMap[ord->getItem()];
00330     
00331     cout << "Retailer(" << m_id << ") received: " << ord->str() << endl;
00332     /*
00333       cout << "Retailer(" << m_id << ") s=" << ic.stock << " h="
00334       << ic.held << " c=" << ic.claimed << " o=" << ic.ordered
00335       << endl;
00336     */
00337     if(ic.stock >= q_needed)
00338       {
00339         Delivery *del = new Delivery(ord->getItem(), ord->getOrderId(), q_needed);
00340         Sim::signal_event(ord->getDeliverToId(), del, 1);
00341         ic.stock -= q_needed;
00342       }
00343     else
00344       {
00345         // move the ones in stock to held
00346         q_needed -= ic.stock;
00347         ic.held += ic.stock;
00348         ic.stock = 0;
00349         
00350         // need to wait for a delivery, have to copy
00351         // order because scheduler may delete order after this 
00352         // method returns.
00353         m_pendingOrders[ord->getItem()].push(new Order(*ord));
00354         ic.claimed += q_needed;
00355         // if there aren't already enough on order, generate an order 
00356         if((ic.ordered - ic.claimed) < 0)
00357           {
00358             int q = ic.claimed - ic.ordered;
00359             Order *ord2 = new Order(ord->getItem(), q, m_id);
00360             Sim::signal_event(m_mid, ord2, 1);
00361             ic.ordered += q;
00362           }
00363       }
00364     // store item_count back into stock map to reflect any changes
00365     m_stockMap[ord->getItem()] = ic;
00366   }
00367 
00368   ProcessId m_id;
00369   ProcessId m_mid;
00370   map<string,item_count> m_stockMap;
00371   map<string,queue<Order*> > m_pendingOrders;
00372 };
00373 
00374 class Buyer : public Process
00375 {
00376 public:
00377   Buyer() : m_id(NULL_PROCESSID), m_rid(NULL_PROCESSID) {}
00378 
00379   virtual ~Buyer() {}
00380 
00381   void addItem(const string& item, int quantity)
00382   {
00383     m_shoppingMap[item] = quantity;
00384   }
00385   
00386   virtual void process_event(const Event *e)
00387   {
00388     const Delivery* del;
00389     int sendDelay;
00390     
00391     if ((del = dynamic_cast<const Delivery*>(e)))
00392       {
00393         sendDelay = m_delayMap[del->getItem()];
00394         
00395         cout << "Buyer(" << m_id << ") order for " << del->getQuantity()
00396              << " " << del->getItem() << " took "
00397              << (Sim::clock()-sendDelay) << " days." << endl;
00398         m_shoppingMap[del->getItem()] -= del->getQuantity();
00399       }
00400     else if ((dynamic_cast<const Order *>(e)))
00401       {
00402         cerr << "Buyer(" << m_id << ") received an order event!" << endl;
00403       }
00404     else
00405       {
00406         cerr << "Buyer(" << m_id << ") received unknown event." << endl;
00407       }
00408   }
00409   
00410   virtual void init()
00411   {
00412     map<string,int>::const_iterator i;
00413     
00414     for(i = m_shoppingMap.begin(); i != m_shoppingMap.end(); ++i)
00415       {
00416         Order *ord = new Order( (*i).first, (*i).second, m_id );
00417         m_delayMap[(*i).first] = get_rand( 0, 20 );
00418         Sim::signal_event(m_rid,  ord, m_delayMap[(*i).first]);
00419       }
00420   }
00421   
00422   ProcessId getId() const
00423   {
00424     return m_id;
00425   }
00426   
00427   bool isSatisfied() const
00428   {
00429     map<string,int>::const_iterator i;
00430     bool ret = true;
00431     for(i = m_shoppingMap.begin(); i != m_shoppingMap.end(); ++i)
00432       {
00433         if( (*i).second != 0 )
00434           {
00435             cerr << "Buyer(" << m_id << ") still needs " << (*i).second << " of " << (*i).first << endl;
00436             ret = false;
00437           }
00438       }
00439     return ret;
00440    }
00441   
00442   void setId( ProcessId id )
00443   {
00444     m_id = id;
00445   }
00446   
00447   void setRetailerId( ProcessId id )
00448   {
00449     m_rid = id;
00450   }
00451   
00452 private:
00453   ProcessId m_id;
00454   ProcessId m_rid;
00455   map<string,int> m_shoppingMap;
00456   map<string,int> m_delayMap;
00457 };
00458 
00459 #define BUYER_COUNT 10
00460 
00461 class ErrorHandler : public SimErrorHandler
00462 {
00463 public:
00464   unsigned busy_count, term_count;
00465 
00466   ErrorHandler() : busy_count(0), term_count(0) {}
00467 
00468   void clear() throw ()
00469   {
00470     cout << "ErrorHandler::clear() called" << endl;
00471   }
00472 
00473   void handle_busy(ProcessId pid, const Event * e) throw()
00474   {
00475     busy_count++;
00476   }
00477 
00478   void handle_terminated(ProcessId pid, const Event * e) throw()
00479   {
00480     term_count++;
00481   }
00482 };
00483 
00484 // main function creates a list of the items being delt with by the
00485 // supply-chain, instantiates a single manufacturer and retailer and a
00486 // number of buyers and ties them together by passing the process ids,
00487 // and finally runs the simulation.
00488 int main(int argc, char** argv)
00489 {
00490   srand(1);
00491   
00492   set<string> items;
00493   items.insert("HardDrive");
00494   items.insert("Keyboard");
00495   items.insert("Monitor");
00496   items.insert("Mouse");
00497   items.insert("Printer");
00498   items.insert("Scanner");
00499   
00500   Manufacturer m;
00501   m.setId(Sim::create_process(&m));
00502   
00503   Retailer r(m.getId());
00504   r.setId(Sim::create_process(&r));
00505   
00506   Buyer b[BUYER_COUNT];
00507   
00508   set<string>::const_iterator i;
00509   for(i = items.begin(); i != items.end(); ++i)
00510     {
00511     // name, production delay
00512     m.addItem((*i), get_rand(1, 30));
00513         
00514     // name, quantity in stock
00515     r.addItem((*i), get_rand(1, 100));
00516                   
00517     for(int j = 0; j < BUYER_COUNT; ++j) {
00518       if(i == items.begin()) {
00519         b[j].setId(Sim::create_process(&b[j]));
00520         b[j].setRetailerId(r.getId());
00521       }
00522       // name, number needed
00523       b[j].addItem((*i), get_rand(1, 50));
00524     }
00525   }
00526 
00527   ErrorHandler eh;
00528   Sim::set_error_handler( &eh );
00529   Sim::run_simulation();
00530 
00531   if( eh.busy_count )
00532     {
00533       cout << "WARNING: there were " << eh.busy_count << " dropped events." << endl;
00534     }
00535   if( eh.term_count )
00536     {
00537       cout << "WARNING: there were " << eh.term_count << " events sent to terminated processed." << endl;
00538     }
00539   bool failed = false;
00540   // check that all buyers are satisfied
00541   for( int j = 0; j < BUYER_COUNT; ++j )
00542     {
00543       if( ! b[j].isSatisfied() )
00544         {
00545           failed = true;
00546         }
00547     }
00548   return failed;
00549 }
00550