Saat kita mengantri di kasir supermarket, call center menahan panggilan, atau server web menangani request bersamaan, sistem di belakang layar adalah queue. Untuk memprediksi waktu tunggu rata-rata, panjang antrean puncak, dan utilisasi server, kita tidak bisa mengandalkan estimasi intuitif — kita perlu simulasi. Discrete event simulation adalah paradigma di mana kita melompat dari satu event ke event berikutnya (bukan increment waktu kecil-kecil), membuatnya sangat efisien. Pada Chapter ini kita akan menyimulasikan M/M/1 queue — model matematika klasik dengan kedatangan Poisson dan layanan eksponensial. Implementasinya memperlihatkan bagaimana priority queue (event queue) dan queue biasa (FIFO customer) saling melengkapi.
Discrete Event Simulation: Konsep #
Pada simulasi waktu diskrit, sistem berevolusi melalui event — titik waktu ketika sesuatu terjadi (kedatangan customer, selesainya layanan). Karena tidak ada perubahan antar event, kita melompat langsung dari satu event ke event berikutnya. Priority queue berisi event diurutkan berdasarkan waktu — event paling awal diproses lebih dulu.
| Komponen | Struktur Data | Peran |
|---|---|---|
| Event queue | priority_queue<Event, vector<Event>, greater<Event>> | Min-heap berdasarkan waktu |
| Customer queue | queue<int> FIFO | Customer menunggu giliran |
| RNG arrival | exponential_distribution<double>(1/lambda) | Poisson process |
| RNG service | exponential_distribution<double>(1/mu) | Service eksponensial |
| Statistics | Counter skalar | Total arrival, completed, dll |
M/M/1 Queue: Aturan dan Mekanisme #
Aturan #
Notasi M/M/1 (Kendall notation):
- M pertama: Markovian (Poisson/exponential) arrival process dengan rate
λ. - M kedua: Markovian service time dengan rate
μ. - 1: satu server.
Utilisasi sistem: ρ = λ / μ. Bila ρ ≥ 1, sistem tidak stabil — antrian tumbuh tanpa batas. Bila ρ < 1, sistem stabil dengan metrik teoretis:
- E[N] (panjang antrean rata-rata) =
ρ / (1 - ρ) - E[T] (waktu di sistem rata-rata) =
1 / (μ - λ) - E[W] (waktu tunggu rata-rata) =
ρ / (μ - λ)
Mekanisme di Balik Layar #
Saat arrival terjadi:
- Bila server idle → customer langsung dilayani, jadwalkan departure setelah service time.
- Bila server sibuk → customer masuk FIFO queue, menunggu giliran.
Saat departure terjadi:
- Hapus customer dari sistem.
- Bila queue tidak kosong → pop customer berikutnya, jadwalkan departure setelah service time.
- Bila queue kosong → set server menjadi idle.
Priority queue memastikan event diproses dalam urutan kronologis. Setiap event dapat menjadwalkan event baru (arrival berikutnya, departure) — paradigma event-driven.
Kompleksitas #
| Operasi | Time | Space |
|---|---|---|
| Event push | O(log E) | O(1) per event |
| Event pop | O(log E) | O(1) |
| Customer queue push/pop | O(1) | O(1) |
| Total simulation | O(E log E), E = jumlah event | O(E) untuk event queue + O(N) untuk customer queue |
Kode: Simulasi M/M/1 #
#include <iostream>
#include <queue>
#include <vector>
#include <random>
#include <iomanip>
using namespace std;
struct Event {
double time;
int type; // 0 = arrival, 1 = departure
int customer_id;
bool operator>(const Event& o) const { return time > o.time; }
};
class MM1Simulation {
public:
MM1Simulation(double lambda, double mu, double sim_time)
: lambda_(lambda), mu_(mu), sim_time_(sim_time),
rng_(42), arrival_dist_(1.0/lambda), service_dist_(1.0/mu) {}
void run() {
// Event pertama: arrival customer 1
double t = arrival_dist_(rng_);
event_queue_.push({t, 0, 1});
next_customer_ = 2;
cout << "=== Simulasi M/M/1 Queue ===\n";
cout << "lambda (arrival rate) = " << lambda_ << " cust/min\n";
cout << "mu (service rate) = " << mu_ << " cust/min\n";
cout << "rho (utilization) = " << lambda_/mu_ << "\n";
cout << "Sim time = " << sim_time_ << " min\n\n";
cout << left << setw(8) << "Time"
<< setw(12) << "Event"
<< setw(12) << "Customer"
<< setw(15) << "Queue Size" << "\n";
cout << "-------|------------|-----------|---------------\n";
while (!event_queue_.empty()) {
Event e = event_queue_.top(); event_queue_.pop();
if (e.time > sim_time_) break;
if (e.type == 0) { // arrival
total_customers_++;
cout << left << setw(8) << fixed << setprecision(2) << e.time
<< setw(12) << "arrival"
<< setw(12) << e.customer_id
<< setw(15) << queue_.size() << "\n";
if (server_busy_) {
queue_.push(e.customer_id);
} else {
// Langsung dilayani
server_busy_ = true;
double service_t = service_dist_(rng_);
event_queue_.push({e.time + service_t, 1, e.customer_id});
current_service_start_ = e.time;
}
// Jadwalkan arrival berikutnya
double next_arr = e.time + arrival_dist_(rng_);
event_queue_.push({next_arr, 0, next_customer_++});
} else { // departure
completed_customers_++;
total_service_time_ += (e.time - current_service_start_);
cout << left << setw(8) << fixed << setprecision(2) << e.time
<< setw(12) << "departure"
<< setw(12) << e.customer_id
<< setw(15) << queue_.size() << "\n";
if (!queue_.empty()) {
int next_cust = queue_.front(); queue_.pop();
double service_t = service_dist_(rng_);
event_queue_.push({e.time + service_t, 1, next_cust});
current_service_start_ = e.time;
} else {
server_busy_ = false;
}
}
}
report();
}
private:
double lambda_, mu_, sim_time_;
mt19937 rng_;
exponential_distribution<double> arrival_dist_, service_dist_;
priority_queue<Event, vector<Event>, greater<Event>> event_queue_;
queue<int> queue_; // FIFO: customer menunggu di antrian
bool server_busy_ = false;
int next_customer_ = 1;
int total_customers_ = 0;
int completed_customers_ = 0;
double total_service_time_ = 0;
double current_service_start_ = 0;
void report() {
cout << "\n=== Laporan Simulasi ===\n";
cout << "Total arrival : " << total_customers_ << " customer\n";
cout << "Total completed : " << completed_customers_ << " customer\n";
cout << "Sisa antrian : " << queue_.size() << " customer\n";
cout << "Total service : " << fixed << setprecision(2)
<< total_service_time_ << " min\n";
cout << "Avg service time: " << fixed << setprecision(2)
<< (completed_customers_ ? total_service_time_/completed_customers_ : 0)
<< " min\n";
}
};
int main() {
// lambda=0.8, mu=1.0 (rho=0.8), sim 20 min
MM1Simulation sim(0.8, 1.0, 20.0);
sim.run();
return 0;
}Output yang Dihasilkan:
=== Simulasi M/M/1 Queue ===
lambda (arrival rate) = 0.8 cust/min
mu (service rate) = 1 cust/min
rho (utilization) = 0.8
Sim time = 20 min
Time Event Customer Queue Size
-------|------------|-----------|---------------
1.27 arrival 1 0
1.48 departure 1 0
2.48 arrival 2 0
2.96 arrival 3 0
3.04 arrival 4 1
3.39 departure 2 2
3.53 arrival 5 1
3.66 arrival 6 2
3.80 departure 3 3
3.86 departure 4 2
4.50 arrival 7 1
5.14 departure 5 2
5.14 departure 6 1
6.73 arrival 8 0
7.50 arrival 9 1
8.25 arrival 10 2
8.26 arrival 11 3
8.28 arrival 12 4
8.87 arrival 13 5
9.28 arrival 14 6
9.32 arrival 15 7
9.99 departure 7 8
10.26 departure 8 7
10.35 departure 9 6
11.32 departure 10 5
11.80 departure 11 4
12.23 arrival 16 3
12.74 arrival 17 4
14.31 arrival 18 5
15.22 arrival 19 6
15.70 arrival 20 7
15.71 arrival 21 8
15.89 departure 12 9
16.71 departure 13 8
17.20 departure 14 7
17.22 departure 15 6
17.48 departure 16 5
17.76 departure 17 4
17.99 arrival 22 3
18.74 arrival 23 4
18.91 departure 18 5
19.10 departure 19 4
19.59 departure 20 3
19.79 departure 21 2
=== Laporan Simulasi ===
Total arrival : 23 customer
Total completed : 21 customer
Sisa antrian : 1 customer
Total service : 17.51 min
Avg service time: 0.83 minBedah Kode Baris-per-Baris #
struct Event { double time; int type; int customer_id; bool operator>(const Event& o) const { return time > o.time; } };: tiap event memiliki waktu terjadinya, tipe (arrival/departure), dan customer yang terlibat. Operator>mengubah max-heap default menjadi min-heap — event dengan waktu terkecil keluar lebih dulu.priority_queue<Event, vector<Event>, greater<Event>> event_queue_;: event queue berbasis min-heap.greater<Event>memilih comparator yang mengembalikantruesaata > b, sehinggatop()adalah event dengan time minimum.exponential_distribution<double> arrival_dist_(1.0/lambda);: distribusi eksponensial untuk arrival. Parameter rateλditerjemahkan ke mean1/λ. Saat dipanggilarrival_dist_(rng_), menghasilkan random variate dengan distribusi Poisson process inter-arrival.mt19937 rng_(42);: Mersenne Twister dengan seed deterministik (42) — menghasilkan output reproducible. Penting untuk debugging simulasi.if (server_busy_) queue_.push(e.customer_id);: bila server sibuk, customer masuk FIFO queue. OperasiO(1).double service_t = service_dist_(rng_); event_queue_.push({e.time + service_t, 1, e.customer_id});: jadwalkan departure untuk customer yang dilayani. Service time acak dari distribusi eksponensial; event departure ditempatkan di masa depan.double next_arr = e.time + arrival_dist_(rng_); event_queue_.push({next_arr, 0, next_customer_++});: jadwalkan arrival berikutnya. Poisson process: inter-arrival time eksponensial, sehingga setiap arrival men-trigger penjadwalan arrival berikutnya.if (!queue_.empty()) { int next_cust = queue_.front(); queue_.pop(); ... }: setelah departure, ambil customer berikutnya dari FIFO. Bila queue kosong, server idle.if (e.time > sim_time_) break;: stop simulasi pada batas waktu. Event yang tersisa di queue diabaikan.
Validasi terhadap Teori #
Untuk λ=0.8, μ=1.0 (ρ=0.8):
- E[N] teoretis =
ρ/(1-ρ) = 0.8/0.2 = 4customer - E[T] teoretis =
1/(μ-λ) = 1/0.2 = 5min - E[W] teoretis =
ρ/(μ-λ) = 0.8/0.2 = 4min
Pada simulasi kami, total service time 17.51 min untuk 21 customer → avg service ~0.83 min (mendekati 1/μ=1.0 dengan toleransi statistik). Untuk mendapatkan konvergensi yang lebih dekat ke teori, jalankan simulasi lebih lama (mis. 1000 min) dan rata-ratakan beberapa run dengan seed berbeda.
Varian Sistem Antrean #
| Notasi | Arrival | Service | Server | Contoh |
|---|---|---|---|---|
| M/M/1 | Poisson | Exponential | 1 | Single teller bank |
| M/M/c | Poisson | Exponential | c | Call center |
| M/D/1 | Poisson | Deterministic | 1 | Conveyor belt |
| M/G/1 | Poisson | General | 1 | Web server (mixed loads) |
| G/G/1 | General | General | 1 | Worst-case bound |
| M/M/∞ | Poisson | Exponential | ∞ | Self-service |
Aplikasi Simulasi Antrean #
| Domain | Apa yang Disimulasikan | Manfaat |
|---|---|---|
| Call center | Jumlah agen optimal | Mengurangi waktu tunggu |
| Server web | Kapasitas untuk peak load | Capacity planning |
| Manufaktur | Throughput lini produksi | Identifikasi bottleneck |
| Transportasi | Antrian di traffic light | Optimasi timing sinyal |
| Healthcare | Antrian pasien IGD | Alokasi staf |
| Theme park | Antrean wahana | Design fast-pass system |
Ringkasan #
- Discrete event simulation melompat antar event, bukan increment waktu kecil — sangat efisien.
- M/M/1 queue: arrival Poisson, service exponential, satu server.
- Priority queue berisi event (min-heap by time); FIFO queue berisi customer.
ρ = λ/μharus < 1 untuk sistem stabil.- Random number generator dengan seed deterministik → output reproducible.
- Varian M/M/c, M/G/1, dan lain-lain dapat disimulasikan dengan struktur yang sama.