Panduan lengkap TUBES 1: simulasi bus rental mobil (Soal 2.38)

Panduan ini menjelaskan seluruh tugas dari awal sampai akhir dalam satu berkas: tugas ini untuk apa, soalnya apa, kenapa dijawab dengan simulasi, kenapa model dan kodenya dibuat seperti ini, dan bagaimana mempersiapkan pengumpulan dan demo. Kode yang dijelaskan adalah src/bus-sim.c di repo tubes1-simlib-bus (606 baris sebelum statistik, 683 baris sekarang, satuan waktu menit). Seluruh isi bus-sim.c dan Makefile ada di sini, jadi tidak perlu membuka berkas lain.

Untuk setiap bagian, panduan ini menjawab empat pertanyaan:

  1. Untuk apa / kenapa ada: kalimat mana di soal yang dijawabnya.
  2. Kenapa dibutuhkan: apa yang rusak kalau bagian itu tidak ada.
  3. Cara kerjanya: langkah demi langkah.
  4. Kenapa kodenya begini: pilihan desain yang diambil dan alternatif yang tidak dipilih.
BagianIsi
Atugas ini untuk apa, dan kenapa dijawab dengan simulasi
Bsoal 2.38 dalam bahasa biasa, dan keputusan tafsir yang kita ambil
Cdari kalimat soal ke bagian kode
Dglosarium kata teknis, termasuk penjelasan lengkap stream dan stream 6
0gambaran besar file bus-sim.c
0Aistilah SIMLIB, simbol, dan rumus yang dipakai
1urutan baca
2seluruh isi bus-sim.c beserta penjelasan dan alasan desainnya
3menelusuri hasil make trace
4mesin status bus
5perbandingan dengan versi satu berkas kita (bus_sim.c)
6statistik (a) sampai (f): hasil nyata dan pemeriksaan kewajaran
7apa yang boleh dan tidak boleh diklaim
8soal latihan
9cara menjalankan dari nol
Jpengumpulan dan persiapan demo
Kpertanyaan yang mungkin ditanyakan dosen, beserta jawabannya

Bagian A: tugas ini untuk apa

TUBES I Pemodelan dan Simulasi. Kita diminta membuat simulasi untuk soal 2.38 di buku Law & Kelton, memakai C dan SIMLIB, lalu mengumpulkan tiga hal dalam satu PDF dan mendemokannya di akhir waktu UTS:

Yang dikumpulkanIsi
Deskripsi masalahversi soal menurut program kita, satu halaman (kelompok boleh menafsirkan soal berbeda, asalkan ditulis)
Source codeharus sama dengan yang didemokan
Outputharus sama dengan yang didemokan

Tujuan belajarnya: memodelkan sistem antrian yang tidak sederhana sebagai simulasi event-driven, mengumpulkan statistik darinya, dan memeriksa bahwa hasilnya masuk akal.

Kenapa dijawab dengan simulasi, bukan rumus

Sistem ini punya banyak hal sekaligus: tiga antrian di tempat berbeda, satu pelayan yang berpindah-pindah (bus), kapasitas 20, minimal 5 menit berhenti di tiap lokasi, serta waktu naik dan turun yang acak per orang. Rumus teori antrian seperti M/M/1 hanya berlaku untuk pelayan yang diam di satu tempat dan aturan yang lebih sederhana. Untuk sistem seperti ini tidak ada rumus tertutup. Jalan keluarnya: menjalankan sistem itu di komputer dan mencatat apa yang terjadi, lalu menghitung rata-rata dari catatan itu. Itulah simulasi.

Apa itu simulasi event-driven (satu paragraf)

Simulasi tidak berdetak per detik. Ia hanya memproses kejadian (event) satu per satu menurut waktunya: orang datang, bus tiba, selesai naik, selesai turun, dan seterusnya. Jam simulasi melompat dari satu kejadian ke kejadian berikutnya. Karena itu 80 jam waktu simulasi (4800 menit) selesai dalam sekejap di komputer. SIMLIB menyediakan mesin dasarnya (daftar kejadian, antrian, bilangan acak, pencatat statistik); kita menulis aturan kejadian-kejadiannya.

Apa yang dihasilkan

Enam kelompok angka di akhir 80 jam: (a) jumlah orang di antrian, (b) lama menunggu, (c) jumlah orang di bus, (d) lama bus berhenti, (e) lama satu putaran, (f) lama orang berada di sistem. Angka-angka ini adalah perkiraan dari satu kali jalan, bukan nilai pasti sistem (lihat Bagian 7).


Bagian B: soal 2.38 dalam bahasa biasa

Ceritanya

Sebuah rental mobil punya tiga lokasi: terminal udara 1, terminal udara 2, dan tempat rental mobil (lokasi 3). Orang datang ke tiap lokasi secara acak (14, 10, dan 24 orang per jam) lalu antre menunggu bus. Ada satu bus berkapasitas 20 orang yang berputar terus: 3 → 1 → 2 → 3. Orang dari terminal selalu ingin ke rental mobil. Orang dari rental mobil ingin ke terminal 1 (peluang 0,583) atau terminal 2 (peluang 0,417).

Rute (Gambar 2.72)

Angka pada gambar adalah panjang ruas jalan dalam mil. Bus berputar berlawanan arah jarum jam (panah bus di sisi bawah menunjuk ke kanan).

            3
   TL ─────────────── TR
   │                  │
  0.5                 1
   │                  │
  (1) Air terminal 1  │
   │                 (3) Car rental  ← bus mulai di sini
   1                  │
   │                  1
  (2) Air terminal 2  │
   │                  │
  0.5                 │
   │       3     Bus →│
   BL ─────────────── BR

Karena bus hanya berhenti di (1), (2), (3), jarak antar-halte dijumlahkan dari ruas yang dilewati:

Dari → keRuas yang dilewatiJarakWaktu tempuh (30 mph)
3 → 1naik 1, lewat atas 3, turun 0,54,5 mil9 menit
1 → 2langsung 11 mil2 menit
2 → 3turun 0,5, lewat bawah 3, naik 14,5 mil9 menit

Satu putaran = 10 mil = 20 menit jalan, belum termasuk waktu berhenti.

Aturan di tiap halte

UrutanAturanWaktu
1orang yang tujuannya halte ini turun dulu, urutan FIFOUniform(16, 24) detik per orang
2lalu orang di antrian naik sampai kapasitas 20Uniform(15, 25) detik per orang
3bus berhenti minimal 5 menit; setelah 5 menit, bila tidak ada yang sedang naik atau turun, bus langsung berangkat

Yang diminta

Simulasi 80 jam dan enam statistik (a) sampai (f) di atas, dengan penugasan bilangan acak: stream 1 sampai 3 untuk antar-kedatangan di lokasi 1 sampai 3, stream 4 untuk waktu turun, stream 5 untuk waktu naik, stream 6 untuk tujuan penumpang yang tiba di rental mobil. (Stream = “dadu” bernomor untuk bilangan acak; penjelasan lengkap di Bagian D.)

Hal yang soal tidak jelaskan, dan keputusan kita

Soal sengaja tidak lengkap di beberapa hal. Setiap kelompok boleh menafsirkan, asal menuliskannya. Keputusan kita, dengan alasan dan alternatifnya:

Hal yang tidak jelasKeputusan kitaAlasanAlternatif
Jarak antar-halte4,5 / 1 / 4,5 mil, dijumlahkan dari ruas di gambargambar hanya memberi panjang ruas; bus hanya berhenti di tiga lokasikelompok lain bisa membaca gambar berbeda
Bus pada t = 0sudah meninggalkan rental mobil, langsung menuju lokasi 1soal: bus “leaves immediately”; antrian masih kosong di t = 0memodelkan satu pemberhentian 5 menit di awal (tidak dipilih karena soal menyuruh langsung berangkat)
Kapan tujuan penumpang rental ditentukansaat ia tiba di antrianstream 6 dinamai “destination of an arrival”, dan dengan begitu hanya dipakai sekali per orangsaat naik bus (hasil acak bisa berbeda)
Orang yang datang saat bus masih berhenti di lokasinyalangsung naik bila bus idle dan masih ada kursibus masih di tempat dan ada kursi, jadi wajar ikut dimuat”snapshot”: hanya yang sudah antre saat bus tiba
Maksud “5 menit lalu berangkat”berangkat tepat saat 5 menit lewat dan tidak ada proses naik/turun; bila proses masih berjalan di menit ke-5, ditunggu selesaimengikuti kalimat soal apa adanyamemotong proses di menit ke-5 (tidak masuk akal secara fisik)
Kapasitasmenghitung semua orang di bus, termasuk yang hanya lewatkursi tetap terpakaihanya menghitung yang turun di halte berikutnya
Delay antriandihitung sampai orang diambil dari antrianmenunggu berakhir saat pelayanan dimulaisampai selesai naik
Lama di sistemdari tiba sampai selesai turunitu saat orang benar-benar keluar dari sistemsampai mulai turun
Jumlah di bus (c)orang keluar dari bus di awal bongkarjumlah langsung berkurang saat orang mulai turundi akhir bongkar (angka rata-rata sedikit lebih tinggi)
Yang belum selesai di menit 4800tidak dihitung di (b), (e), (f)tidak ada nilai akhir untuk merekamemperpanjang simulasi sampai semua selesai

Semua keputusan ini harus ditulis di deskripsi satu halaman; itulah gunanya halaman itu.


Bagian C: dari kalimat soal ke bagian kode

Kalimat soalBagian kodeOutput
orang datang di lokasi i dengan antar-kedatangan eksponensial, laju 14, 10, 24 per jamlocation_set (laju menjadi menit per orang), event_arrival, schedule_next_arrivaln di (b) mendekati 14 × 80, 10 × 80, 24 × 80
tiap lokasi punya antrian FIFO tanpa bataslist 1-3, passenger_file, passenger_remove_first(a), (b)
satu bus, kapasitas 20, 30 mphBUS_CAPACITY, BUS_SPEED_MPH, bus_is_full, location_set(c), (e)
bus mulai di lokasi 3 dan langsung berangkat berlawanan arah jarum jambus_init, init_model, next_stoptrace: bus tiba di lokasi 1 pada menit 9
orang dari terminal ke rental mobil; dari rental ke terminal 1 (0,583) atau 2 (0,417)passenger_create dengan stream 6(f) per lokasi asal
turun dulu, FIFO, U(16,24) detiktry_start_unload, event_end_unload, stream 4(f)
lalu naik sampai kapasitas, U(15,25) detiktry_start_load, event_end_load, stream 5(b), (c)
minimal 5 menit; bila tidak ada proses setelah 5 menit, langsung berangkatMIN_STOP_TIME, event_min_stop_reached, try_depart, bus_depart(d) minimum 5,000
simulasi 80 jamSIM_LENGTH = 4800 menit, event akhir simulasijudul laporan
statistik (a) sampai (f)hook stats_* dan stats_reporttabel hasil

Bagian D: glosarium kata teknis

Daftar istilah yang muncul di soal, di kode, dan di laporan. Dikelompokkan menurut tema. Kalau ada kata yang masih membingungkan dan belum ada di sini, tambahkan di sini.

D1. Stream dan bilangan acak (penjelasan lengkap)

Stream = satu “dadu” bernomor. SIMLIB punya satu pembangkit bilangan acak (lcgrand) yang dapat menjalankan banyak urutan bilangan acak yang saling terpisah. Tiap urutan diberi nomor, dan nomor itulah yang disebut stream (stream 1, stream 2, dan seterusnya sampai 100). Setiap kali kita meminta angka dari sebuah stream, kita mendapat bilangan acak antara 0 dan 1 (misalnya 0,42 atau 0,90), dan angka berikutnya dari stream yang sama selalu berbeda.

Tiga sifat penting:

SifatArtinya
Tiap stream punya titik awal sendiridisebut seed; sudah diset di simlib.c untuk semua stream
Urutannya tetapkarena angka berikutnya ditentukan oleh angka sebelumnya, menjalankan program dua kali memberi hasil yang sama persis (hasil bisa diulang)
Antar-stream terpisahangka yang diambil dari stream 4 tidak memengaruhi angka di stream 5

Kenapa dipisah? Misalkan semua kebutuhan acak memakai satu urutan. Kalau kita mengubah satu hal (misalnya cara memilih waktu naik), jumlah angka yang terambil berubah, dan semua angka acak sesudahnya bergeser: antar-kedatangan, tujuan penumpang, semuanya ikut berubah. Dengan stream terpisah, setiap sumber ketidakpastian punya dadu sendiri, sehingga perubahan di satu tempat tidak mengubah hasil acak di tempat lain. Itu juga membuat perbandingan antar versi program adil. Karena itu soal menetapkan nomor stream untuk tiap kebutuhan acak:

StreamDipakai untukDalam kode
1, 2, 3waktu antar-kedatangan di lokasi 1, 2, 3STREAM_INTERARRIVAL(loc)
4waktu menurunkan satu orangSTREAM_UNLOAD
5waktu menaikkan satu orangSTREAM_LOAD
6tujuan orang yang baru tiba di rental mobilSTREAM_DESTINATION

Stream 6 secara khusus. Orang yang tiba di rental mobil harus memilih tujuan: terminal 1 (peluang 0,583) atau terminal 2 (peluang 0,417). Pemilihan itu “dilempar” dengan dadu khusus, yaitu stream 6. Caranya: setiap orang yang tiba di rental mengambil satu angka u dari stream 6.

Angka u dari stream 6Tujuan
u kurang dari 0,583 (misalnya 0,42)terminal 1
u 0,583 atau lebih (misalnya 0,90)terminal 2

Di kode ini ada di passenger_create: lcgrand(STREAM_DESTINATION) < PROB_RENTAL_TO_T1. Orang dari terminal tidak memakai stream 6, karena tujuannya pasti rental mobil. Jadi jumlah angka yang diambil dari stream 6 sama dengan jumlah orang yang tiba di rental (sekitar 24 × 80 = 1920 dalam satu kali jalan). Stream 6 hanya menentukan tujuan; waktu datang orang rental memakai stream 3.

D2. Simulasi dan waktu

IstilahArtinya
Simulasimenjalankan model sistem di komputer untuk melihat perilakunya, dan mencatat statistiknya
Event (kejadian)sesuatu yang mengubah keadaan sistem pada satu saat: orang datang, bus tiba, selesai naik, selesai turun, batas 5 menit tercapai, simulasi berakhir
Event-drivenjam simulasi hanya melompat ke kejadian berikutnya, bukan berdetak per detik
Jam simulasi (sim_time)waktu di dalam simulasi (menit); bukan waktu komputer
Daftar event (event list)daftar kejadian yang sudah dijadwalkan, terurut menurut waktunya
Jadwalkan (event_schedule)menaruh satu kejadian baru ke daftar event untuk terjadi di waktu tertentu
Replikasisatu kali menjalankan simulasi penuh; kita hanya punya satu replikasi (seed bawaan)
Seedtitik awal urutan bilangan acak (lihat D1)
Tracemencetak jalannya event satu per satu untuk memeriksa kronologi

D3. Antrian dan sistem bus

IstilahArtinya
Antrian (queue)orang yang menunggu bus di satu lokasi
FIFOfirst in, first out: yang datang lebih dulu dilayani lebih dulu
Kapasitasjumlah maksimum orang di bus (20); semua orang di bus dihitung
Halte / stop / lokasitempat bus berhenti: terminal 1, terminal 2, rental mobil
Putaran (loop)dari bus berangkat dari rental mobil sampai berangkat lagi dari sana
Lama berhenti (dwell, stop time)dari bus tiba di suatu halte sampai bus berangkat dari halte itu
Delaylama seseorang menunggu di antrian, sampai diambil untuk naik
Lama di sistemdari seseorang tiba sampai selesai turun di tujuannya
Status busMOVING (di jalan), IDLE (berhenti, tidak ada proses), UNLOADING (ada yang sedang turun), LOADING (ada yang sedang naik)
Idleberhenti dan tidak sedang naik atau turun
Mphmil per jam, satuan kecepatan bus (30)

D4. Distribusi (cara angka acak dibentuk)

IstilahArtinya
Eksponensialbentuk sebaran waktu antar-kedatangan; banyak jeda pendek, sedikit jeda panjang; dibentuk dengan expon(rata-rata, stream)
Poissonkedatangan acak independen dengan laju tetap; waktu antar-kedatangannya eksponensial
Waktu antar-kedatangan (interarrival)jeda antara dua orang yang datang berurutan di satu lokasi
Uniform(a, b)semua nilai antara a dan b sama mungkin; dipakai untuk waktu naik (15 sampai 25 detik) dan turun (16 sampai 24 detik)
Lajubanyak kejadian per satuan waktu (14 orang per jam); kebalikannya adalah rata-rata antar-kedatangan (60 ÷ 14 menit)

D5. Kata-kata SIMLIB

IstilahArtinya
SIMLIBpustaka C dari buku Law & Kelton: daftar event, list, bilangan acak, pencatat statistik
Listantrian bernomor di SIMLIB; list 1-3 = antrian di lokasi, list 4-6 = penumpang di bus menurut tujuan
list_file / list_removememasukkan ke ujung list / mengambil dari depan list
list_size[n]jumlah isi list nomor n sekarang
transfer[]”amplop” data sementara untuk list; kolom 1 dan 2 ditimpa SIMLIB tiap event_schedule dan timing
Atributsatu kolom data di transfer[]
maxatrjumlah kolom transfer[] yang dipakai (minimal 4)
timing()mengambil kejadian terdekat dan memajukan jam simulasi
sampstmencatat rata-rata per kejadian (delay, lama berhenti, dan seterusnya)
timestmencatat rata-rata terhadap waktu untuk level yang berubah (jumlah di bus)
filestlaporan otomatis panjang list dari waktu ke waktu
Hookfungsi kecil stats_* yang dipanggil model pada saat tertentu untuk menitipkan angka ke pencatat
Sentinel (1e30)angka besar yang dipakai SIMLIB bila belum ada data; tidak boleh dicetak apa adanya

D6. Statistik dan pemeriksaan

IstilahArtinya
Rata-rata per kejadianjumlah semua nilai dibagi banyaknya kejadian (delay: sampst)
Rata-rata terhadap wakturata-rata level yang dibobot lamanya tiap level bertahan (jumlah di bus: timest)
Maks / Minnilai terbesar / terkecil yang pernah tercatat
nbanyaknya observasi
Hukum Littlerata-rata jumlah dalam sistem = laju datang × rata-rata lama di sistem; dipakai untuk memeriksa kewajaran
Verifikasimemeriksa bahwa program sesuai model dan hasilnya wajar
Validasimemeriksa bahwa model cocok dengan sistem nyata
Kewajaran (sanity check)pemeriksaan sederhana, misalnya maks di bus tidak mungkin lebih dari 20

D7. Kata-kata di kode C

IstilahArtinya
#definememberi nama pada sebuah angka atau potongan teks (misalnya BUS_CAPACITY untuk 20)
Makro berparameter#define yang menerima nilai, misalnya LIST_QUEUE(loc); bukan fungsi, hanya penggantian teks
structsatu paket beberapa data (misalnya Passenger: waktu tiba, asal, tujuan)
enumdaftar nilai bernama (misalnya status bus BUS_MOVING, BUS_IDLE, …)
Prototipedaftar nama fungsi di bagian atas file supaya urutan penulisan fungsi bebas
Flagvariabel 0 atau 1 sebagai penanda (misalnya min_stop_reached)
Globalvariabel yang dapat dipakai semua fungsi (misalnya bus, locations)

Bagian 0: gambaran besar

Soal 2.38: tiga lokasi (terminal 1, terminal 2, rental mobil) dilayani satu bus yang berputar 3 → 1 → 2 → 3, dengan jarak antar-halte 4,5 / 1 / 4,5 mil (tafsiran atas Gambar 2.72).

Seluruh model ada di satu file yang dibagi menjadi bagian-bagian berurutan, seperti jobshop.c di buku:

 bus-sim.c
 ├─ parameter soal, penomoran SIMLIB, tipe data, variabel global     (baris 1-157)
 ├─ main(): loop event                                                 (baris 158-224)
 ├─ init_model(), report()                                             (baris 226-255)
 ├─ entitas: location, passenger, bus                                  (baris 257-378)
 ├─ event: arrival, loading, unloading, bus arrival / 5 menit / berangkat   (baris 380-525)
 ├─ statistik (a)-(f): hook dan laporan                                (baris 527-643)
 └─ trace (opsional)                                                   (baris 645-683)

Aturan desain dari rancangan temanmu: bagian statistik tidak mengubah keadaan model, hanya menerima angka dari hook. Karena itu mengisi statistik hanya menyentuh fungsi stats_*, bukan model.

Siapa pemilik apa:

BagianPemilikStatus
model di src/bus-sim.c (selain stats_*), Makefile, docs/DESIGN.mdtemanmuselesai, bisa di-compile dan jalan
fungsi stats_* di src/bus-sim.ckamu (diisi bersama asisten)sudah diisi, baru di salinan lokal, belum di-commit
lib/simlib/buku Law & Keltonjangan diklaim ditulis sendiri

Bagian 0A: istilah SIMLIB, simbol, dan rumus

Semua yang dipakai di kode ada di sini. Tidak perlu membuka catatan lain.

Cara kerja SIMLIB dalam lima kalimat

  1. Simulasi berjalan dari kejadian ke kejadian (event-scheduling). Waktu simulasi sim_time melompat ke waktu kejadian berikutnya, bukan berdetak per detik. Karena itu 80 jam simulasi selesai dalam sekejap.
  2. Ada daftar kejadian yang terurut menurut waktu. event_schedule(waktu, tipe) menambah kejadian ke daftar itu; timing() mengambil yang paling awal dan memajukan sim_time.
  3. Ada list bernomor (antrian). list_file(LAST, n) memasukkan satu data ke ujung list n; list_remove(FIRST, n) mengambil dari depan list n (jadi FIFO). list_size[n] adalah isinya sekarang.
  4. Data yang dimasukkan atau diambil dari list dibawa lewat larik transfer[]. Kolom 1 dan 2 dipakai SIMLIB sendiri untuk waktu dan tipe event, dan ditimpa setiap event_schedule() atau timing(). Karena itu data penting harus segera disalin keluar dari transfer[].
  5. SIMLIB punya pencatat statistik: sampst (rata-rata per kejadian), timest dan filest (rata-rata terhadap waktu).

Tabel istilah

Yang kamu lihatArtinya
sim_timejam simulasi sekarang (variabel global SIMLIB)
event_schedule(waktu, tipe)catat: nanti jalankan kejadian tipe pada waktu
timing()ambil kejadian terdekat, majukan sim_time, isi next_event_type
transfer[n]amplop data sementara; kolom 1 dan 2 direbut otomatis oleh SIMLIB
list_file(FIRST/LAST, n)masukkan isi transfer[] ke depan/ujung list n
list_remove(FIRST/LAST, n)ambil dari depan/ujung list n, isinya kembali ke transfer[]
list_size[n]jumlah elemen di list n sekarang
maxatrjumlah kolom transfer[] yang dipakai (SIMLIB mewajibkan minimal 4)
expon(mean, stream)angka acak eksponensial dengan rata-rata mean dari stream tertentu
uniform(a, b, stream)angka acak seragam antara a dan b
lcgrand(stream)angka acak mentah antara 0 dan 1 dari stream tertentu
stream”dadu” bilangan acak bernomor (penjelasan lengkap di Bagian D). Soal menetapkan: 1-3 antar-kedatangan lokasi 1-3, 4 bongkar, 5 muat, 6 tujuan penumpang dari rental
sampst(nilai, var)var positif: catat satu observasi. var negatif: minta laporan (hasil di transfer[1..4] = rata-rata, jumlah, maks, min)
timest(nilai, var)seperti sampst tetapi untuk level yang berubah sepanjang waktu. var negatif: laporan di transfer[1..3] = rata-rata terhadap waktu, maks, min
filest(list)laporan otomatis panjang list dari waktu ke waktu (transfer[1..3] seperti timest)

Simbol dan rumus yang muncul

SimbolDibacaArtinya
D ke-idelay (lama menunggu) orang ke-i
jumlah observasi
sigmajumlahkan semuanya
, , d, q, u “topi”hasil perkiraan dari simulasi, bukan nilai pasti
lambdalaju kedatangan (orang per jam)
mulaju layanan
rho, intensitas lalu lintas

Tiga rumus, dengan angka kecil:

  • Rata-rata delay: . Rata-rata biasa. Tiga orang menunggu 0, 2, dan 4 jam: jam.
  • Rata-rata panjang antrian: , dengan lama waktu antrian berisi tepat orang dan lama total. Dibobot lamanya waktu. Dalam 6 jam antrian kosong 3 jam, berisi 1 orang 2 jam, berisi 2 orang 1 jam: .
  • Utilisasi: = persentase waktu server sibuk. Sibuk 4 dari 6 jam: .

Rumus angka acak eksponensial (sudah ada di expon): , dengan angka acak 0 sampai 1 dan rata-rata yang diinginkan. Contoh: , memberi .

Hukum Little untuk mengecek kewajaran: rata-rata jumlah dalam sistem = laju datang × rata-rata lama di sistem, yaitu . Dipakai di Bagian 6.

Dua jenis rata-rata (penting untuk soal ini):

JenisContohFungsi SIMLIB
per kejadiandelay, lama di sistem, lama bus berhenti, lama putaransampst
terhadap waktupanjang antrian, jumlah penumpang di busfilest, timest

Validasi vs verifikasi: validasi = apakah model cocok dengan sistem nyata; verifikasi = apakah program sesuai model dan hasilnya wajar.



Bagian 1: urutan baca

Urutannya dari data → benda → kejadian → perakitan → pengukuran, bukan dari baris pertama. Kalau dibaca dari main duluan, kamu bertemu nama-nama yang belum kamu kenal.

LangkahBagian di bus-sim.cBarisInti yang harus kamu tangkap
1parameter soal1-35semua angka dari soal, satuan menit
2penomoran SIMLIB36-80event, list, atribut, stream, nomor statistik
3tipe data, variabel global, prototipe81-157bentuk Location, Passenger, Bus
4passenger280-325satu orang, dan cara dia disimpan di list SIMLIB
5location257-279tiga lokasi, rute, konversi ke menit
6bus326-379status bus dan penumpang di dalamnya
7arrival380-407orang datang
8unloading439-471orang turun
9loading408-438orang naik
10bus tiba, 5 menit, berangkat472-526siklus bus
11main, init_model, report158-256loop yang menggerakkan semuanya
12statistik527-644pencatat statistik (a) sampai (f)
13trace645-683alat melihat jalannya simulasi
14Makefilecara compile dan menjalankan

Setelah itu ada penelusuran hasil nyata (Bagian 3), mesin status bus (Bagian 4), perbandingan dengan bus_sim.c (Bagian 5), pengerjaan statistik (Bagian 6), batas klaim (Bagian 7), latihan (Bagian 8), dan cara menjalankan (Bagian 9).


Bagian 2: kode dan penjelasannya

Setiap blok kode diberi nomor baris di src/bus-sim.c. Seluruh 683 baris ada di sini, hanya disusun menurut urutan baca.

Langkah 1: parameter soal (baris 1 sampai 35)

src/bus-sim.c baris 1-35

/* Car-rental bus simulation, Law & Kelton Problem 2.38. All times in minutes. */
 
#include "simlib.h"
 
/* Problem parameters. */
 
#define MINUTES_PER_HOUR      60.0
 
#ifndef SIM_LENGTH
#define SIM_LENGTH            (80.0 * MINUTES_PER_HOUR)   /* 80 hours */
#endif
 
#define BUS_CAPACITY          20
#define BUS_SPEED_MPH         30.0
#define MIN_STOP_TIME         5.0                         /* 5 minutes */
#define UNLOAD_TIME_MIN       (16.0 / 60.0)               /* 16 seconds */
#define UNLOAD_TIME_MAX       (24.0 / 60.0)               /* 24 seconds */
#define LOAD_TIME_MIN         (15.0 / 60.0)               /* 15 seconds */
#define LOAD_TIME_MAX         (25.0 / 60.0)               /* 25 seconds */
 
#define ARRIVAL_RATE_T1       14.0                        /* people per hour */
#define ARRIVAL_RATE_T2       10.0
#define ARRIVAL_RATE_RENTAL   24.0
#define PROB_RENTAL_TO_T1     0.583                       /* else terminal 2 */
 
#define DIST_RENTAL_TO_T1     4.5                         /* miles */
#define DIST_T1_TO_T2         1.0
#define DIST_T2_TO_RENTAL     4.5
 
#ifndef ENABLE_TRACE
#define ENABLE_TRACE          0                           /* 1 = print each event */
#endif
 
#define OUTPUT_FILE           "bus-sim.out"
 
  • Kenapa ada: soal memberi banyak angka (kapasitas 20, 30 mph, 5 menit, U(16,24) detik, laju 14/10/24 per jam, peluang 0,583, jarak, 80 jam). Semuanya dikumpulkan di bagian atas file.
  • Kenapa dibutuhkan: kalau angka tersebar di banyak tempat, satu salah ubah membuat hasil keliru tanpa ada yang sadar. Di satu tempat, angka bisa dicocokkan langsung dengan soal.
  • Cara kerja: hanya #define. Satuan waktu menit: SIM_LENGTH = 80 jam × 60 = 4800 menit; MIN_STOP_TIME = 5,0; waktu bongkar dan muat ditulis 16.0 / 60.0 menit (16 detik ÷ 60 detik per menit). Laju kedatangan tetap per jam dan kecepatan tetap mph; keduanya dikonversi sekali di location_set (Langkah 5). SIM_LENGTH dan ENABLE_TRACE dibungkus #ifndef supaya bisa diganti dari baris compile (-DSIM_LENGTH=60), dipakai make trace.
  • Kenapa kodenya begini: angka soal dikumpulkan di bagian atas supaya satu perubahan cukup di satu tempat dan mudah dicocokkan dengan soal. Satuannya menit karena data soal bercampur (jam, detik, mil per jam); kalau ditulis dalam jam, 5 menit menjadi 0,0833 dan hasilnya sulit dibaca. Memakai jam juga benar, asalkan semua konstanta ikut satu satuan. Konversi sengaja dilakukan sekali (di #define dan di location_set) supaya tidak ada angka bersatuan campur di tengah kode.
  • Uji diri: kenapa ditulis 16.0 / 60.0, bukan 16 / 60? (Di C, pembagian dua bilangan bulat dibulatkan ke bawah, jadi 16 / 60 bernilai 0 dan waktu bongkar muat jadi nol.)

Langkah 2: penomoran SIMLIB (baris 36 sampai 80)

src/bus-sim.c baris 36-80

/* Locations. */
 
#define LOC_TERMINAL_1        1
#define LOC_TERMINAL_2        2
#define LOC_CAR_RENTAL        3
#define NUM_LOCATIONS         3
 
/* Event types. Arrival event number = location number. */
 
#define EVENT_ARRIVAL_T1         1
#define EVENT_ARRIVAL_T2         2
#define EVENT_ARRIVAL_RENTAL     3
#define EVENT_BUS_ARRIVAL        4
#define EVENT_END_UNLOAD         5
#define EVENT_END_LOAD           6
#define EVENT_MIN_STOP_REACHED   7
#define EVENT_END_SIMULATION     8
 
/* Lists: queue at each location (1-3), passengers on the bus by destination (4-6). */
 
#define LIST_QUEUE(loc)       (loc)
#define LIST_BUS(dest)        (NUM_LOCATIONS + (dest))
 
/* Passenger attributes in transfer[]. */
 
#define ATTR_ARRIVAL_TIME     1
#define ATTR_ORIGIN           2
#define ATTR_DESTINATION      3
#define NUM_ATTRIBUTES        4
 
/* Random-number streams. */
 
#define STREAM_INTERARRIVAL(loc)  (loc)
#define STREAM_UNLOAD             4
#define STREAM_LOAD               5
#define STREAM_DESTINATION        6
 
/* Statistic variables for sampst/timest (used by the stats functions). */
 
#define SAMPST_QUEUE_DELAY(loc)     (loc)
#define SAMPST_STOP_TIME(loc)       (NUM_LOCATIONS + (loc))
#define SAMPST_LOOP_TIME            7
#define SAMPST_TIME_IN_SYSTEM(loc)  (7 + (loc))
#define TIMEST_NUM_ON_BUS           1
 
  • Kenapa ada: SIMLIB hanya mengenal nomor (nomor event, list, stream, variabel statistik). Bagian ini memberi nama pada nomor-nomor itu.
  • Kenapa dibutuhkan: tanpa nama, kode penuh angka mentah seperti list_file(LAST, 4) yang tidak terbaca dan mudah tertukar.
  • Cara kerja:
    • Event: kedatangan di lokasi 1/2/3 memakai tipe 1/2/3 (nomor event = nomor lokasi), lalu 4 bus tiba, 5 selesai turun, 6 selesai naik, 7 batas 5 menit tercapai, 8 akhir simulasi.
    • List: LIST_QUEUE(loc) = list 1-3 (antrian), LIST_BUS(dest) = list 4-6 (penumpang di bus, dikelompokkan menurut tujuan). Sama dengan bus_sim.c kita.
    • Atribut penumpang di transfer[]: 1 = waktu tiba, 2 = lokasi asal, 3 = tujuan. NUM_ATTRIBUTES = 4 karena SIMLIB mewajibkan maxatr minimal 4.
    • Stream 1-3 antar-kedatangan, 4 bongkar, 5 muat, 6 tujuan penumpang rental, sesuai penugasan soal.
    • Nomor variabel statistik sudah dipesan: SAMPST_QUEUE_DELAY(loc) = 1-3, SAMPST_STOP_TIME(loc) = 4-6, SAMPST_LOOP_TIME = 7, SAMPST_TIME_IN_SYSTEM(loc) = 8-10, TIMEST_NUM_ON_BUS = 1.
  • Kenapa kodenya begini: nomor event kedatangan sama dengan nomor lokasi, sehingga event_schedule(waktu, location) dan switch bisa memakai nomor lokasi langsung. List bus dibagi menurut tujuan supaya orang yang harus turun langsung ketahuan dari list_size; alternatifnya satu list bus lalu memeriksa tujuan tiap orang, yang lebih rumit karena list_remove hanya bisa mengambil dari depan atau ujung. Nomor stream mengikuti soal supaya hasil bisa dibandingkan dengan program lain yang memakai stream sama.
  • Uji diri: kenapa list bus dikelompokkan menurut tujuan? (Supaya saat bus tiba di suatu lokasi, orang yang harus turun langsung ketahuan: tinggal ambil dari list tujuan itu.)

Langkah 3: tipe data, variabel global, prototipe (baris 81 sampai 157)

src/bus-sim.c baris 81-112

/* Entity types. */
 
typedef struct {
    const char *name;
    double      mean_interarrival;    /* minutes */
    int         next_stop;
    double      travel_time_to_next;  /* minutes */
} Location;
 
typedef struct {
    double arrival_time;
    int    origin;
    int    destination;
} Passenger;
 
typedef enum { BUS_MOVING, BUS_IDLE, BUS_UNLOADING, BUS_LOADING } BusStatus;
 
typedef struct {
    int       location;              /* while moving: the next stop */
    BusStatus status;
    int       min_stop_reached;      /* 1 once 5 minutes have passed */
    double    arrival_time;          /* arrival at the current stop */
    double    last_rental_departure;
    Passenger current;               /* passenger being loaded or unloaded */
} Bus;
 
/* Declare non-simlib global variables. */
 
Location locations[NUM_LOCATIONS + 1];
Bus      bus;
FILE    *outfile;
 

src/bus-sim.c baris 113-157

/* Declare non-simlib functions. */
 
void        init_model(void);
void        report(FILE *out);
 
void        location_init(void);
void        location_set(int id, const char *name, double arrival_rate,
                         int next_stop, double distance_to_next);
Passenger   passenger_create(int origin);
void        passenger_file(const Passenger *p, int list);
Passenger   passenger_remove_first(int list);
void        bus_init(void);
int         bus_passenger_count(void);
int         bus_is_full(void);
int         bus_has_passenger_for(int destination);
void        bus_board(const Passenger *p);
Passenger   bus_alight(int destination);
 
void        schedule_next_arrival(int location);
void        event_arrival(int location);
int         try_start_load(void);
void        event_end_load(void);
int         try_start_unload(void);
void        event_end_unload(void);
void        event_bus_arrival(void);
void        event_min_stop_reached(void);
void        try_depart(void);
void        bus_depart(void);
 
void        stats_init(void);
void        stats_queue_delay(int location, double delay);
void        stats_bus_count_changed(int passengers_on_bus);
void        stats_bus_stop(int location, double stop_time);
void        stats_bus_loop(double loop_time);
void        stats_time_in_system(int origin, double time);
void        stats_report_line(FILE *out, const char *label, double avg,
                              double max, double min, int max_is_count);
void        stats_report_sampst(FILE *out, const char *label, int variable,
                                int show_min);
void        stats_report(FILE *out);
 
const char *event_name(int type);
const char *bus_status_name(BusStatus status);
void        trace_event(void);
 
  • Kenapa ada: model punya tiga benda: lokasi, penumpang, bus. Masing-masing diberi struct supaya datanya satu paket.
  • Kenapa dibutuhkan: statistik delay dan lama di sistem butuh tahu kapan tiap orang tiba dan dari mana; aturan bus butuh status dan posisi bus. Struct membuat itu terbaca jelas.
  • Cara kerja:
    • Location: nama, rata-rata antar-kedatangan (menit), lokasi berikutnya, waktu tempuh ke lokasi berikutnya (menit).
    • Passenger: waktu tiba, asal, tujuan.
    • Bus: location (stop sekarang; saat bergerak, stop tujuan), status (BUS_MOVING, BUS_IDLE, BUS_UNLOADING, BUS_LOADING), min_stop_reached (1 setelah 5 menit lewat), arrival_time di stop sekarang, last_rental_departure (untuk statistik putaran), dan current (penumpang yang sedang “di pintu” bus).
    • Variabel global locations, bus, outfile. Blok prototipe hanya daftar fungsi supaya urutan penulisan fungsi bebas, mirip jobshop.c.
  • Kenapa kodenya begini: struct membuat data satu benda menjadi satu paket dan nama field menjelaskan dirinya. Status bus dibuat enum berisi empat nilai, bukan beberapa flag boolean, karena bus selalu berada tepat dalam satu keadaan; flag terpisah bisa saling bertentangan (misalnya “memuat” dan “bergerak” sekaligus). Bus.current ada karena orang yang sedang naik atau turun sudah keluar dari list tetapi belum masuk list tujuan, jadi harus dititipkan di suatu tempat selama waktu layanannya.
  • Uji diri: kenapa ada status BUS_IDLE terpisah dari BUS_MOVING? (IDLE = bus berhenti di stop tapi tidak ada proses; di keadaan ini penumpang yang baru datang boleh langsung naik dan bus boleh berangkat begitu 5 menit lewat.)

Langkah 4: passenger (baris 280 sampai 325)

src/bus-sim.c baris 280-325

/* Entity: passenger. Only these functions use transfer[] for passengers. */
 
Passenger passenger_create(int origin)
{
    Passenger p;
 
    p.arrival_time = sim_time;
    p.origin       = origin;
 
    /* Terminal arrivals go to the car rental; car-rental arrivals go to
       terminal 1 (prob. 0.583) or terminal 2, decided with stream 6. */
 
    if (origin == LOC_CAR_RENTAL) {
        if (lcgrand(STREAM_DESTINATION) < PROB_RENTAL_TO_T1)
            p.destination = LOC_TERMINAL_1;
        else
            p.destination = LOC_TERMINAL_2;
    } else {
        p.destination = LOC_CAR_RENTAL;
    }
 
    return p;
}
 
void passenger_file(const Passenger *p, int list)
{
    transfer[ATTR_ARRIVAL_TIME] = p->arrival_time;
    transfer[ATTR_ORIGIN]       = (double) p->origin;
    transfer[ATTR_DESTINATION]  = (double) p->destination;
    list_file(LAST, list);
}
 
Passenger passenger_remove_first(int list)
{
    Passenger p;
 
    /* Copy right away: event_schedule() and timing() overwrite transfer[]. */
 
    list_remove(FIRST, list);
    p.arrival_time = transfer[ATTR_ARRIVAL_TIME];
    p.origin       = (int) transfer[ATTR_ORIGIN];
    p.destination  = (int) transfer[ATTR_DESTINATION];
 
    return p;
}
 
  • Kenapa ada: soal punya orang yang datang, antre, naik, turun, dan tiap orang punya tujuan sendiri.
  • Kenapa dibutuhkan: statistik delay dan lama di sistem butuh waktu tiba dan asal tiap orang, yang harus ikut terbawa dari antrian ke bus sampai keluar.
  • Cara kerja:
    1. passenger_create(origin): arrival_time = sim_time. Dari terminal tujuannya pasti rental mobil. Dari rental mobil, diundi sekali dengan stream 6: angka acak < 0,583 berarti terminal 1, selain itu terminal 2.
    2. passenger_file menyalin tiga field ke transfer[1..3], lalu list_file(LAST, list) menaruh orang di ujung list.
    3. passenger_remove_first mengambil dari depan list (list_remove(FIRST, ...)), lalu langsung menyalinnya ke struct.
  • Kenapa disalin segera (poin penting): event_schedule() dan timing() menimpa transfer[1] dan transfer[2]. Data yang dibiarkan di transfer[] bisa rusak. Ini bug yang sama dengan yang kita alami di bus_sim.c (di sana dihindari dengan memindah data ke transfer[3..5]).
  • Kenapa kodenya begini: data disalin ke struct agar tidak tertimpa SIMLIB. Menaruh data di transfer[3..5] seperti di bus_sim.c juga bekerja, tetapi setiap pemakai harus mengingat aturannya; dengan fungsi khusus, aturan itu hanya ada di tiga fungsi ini. Tujuan diundi saat orang datang agar stream 6 dipakai tepat sekali per orang rental dan urutan bilangan acak tidak bergantung pada kapan orang naik. lcgrand(stream) < 0,583 dipakai, bukan random_integer, karena hanya ada dua pilihan.
  • Uji diri: kenapa tujuan orang rental diundi saat dia datang, bukan saat naik bus? (Stream 6 di soal menyebut “destination of an arrival at the car rental”, dan stream hanya dipakai sekali per orang.)

Langkah 5: location (baris 257 sampai 279)

src/bus-sim.c baris 257-279

/* Entity: location. The route 3 -> 1 -> 2 -> 3 is defined here only. */
 
void location_init(void)
{
    location_set(LOC_TERMINAL_1, "Air terminal 1", ARRIVAL_RATE_T1,
                 LOC_TERMINAL_2, DIST_T1_TO_T2);
    location_set(LOC_TERMINAL_2, "Air terminal 2", ARRIVAL_RATE_T2,
                 LOC_CAR_RENTAL, DIST_T2_TO_RENTAL);
    location_set(LOC_CAR_RENTAL, "Car rental", ARRIVAL_RATE_RENTAL,
                 LOC_TERMINAL_1, DIST_RENTAL_TO_T1);
}
 
void location_set(int id, const char *name, double arrival_rate,
                  int next_stop, double distance_to_next)
{
    /* Rate is per hour and speed is miles per hour; convert both to minutes. */
 
    locations[id].name                = name;
    locations[id].mean_interarrival   = MINUTES_PER_HOUR / arrival_rate;
    locations[id].next_stop           = next_stop;
    locations[id].travel_time_to_next = distance_to_next / BUS_SPEED_MPH * MINUTES_PER_HOUR;
}
 
  • Kenapa ada: soal punya tiga lokasi dengan laju berbeda dan rute melingkar.
  • Kenapa dibutuhkan: rute 3 → 1 → 2 → 3 dan jaraknya dipakai di banyak tempat (kapan bus tiba, siapa lokasi berikutnya). Kalau rute ditulis di beberapa tempat, bisa tidak konsisten.
  • Cara kerja: location_init mengisi tabel tiga lokasi lewat location_set. Di sini terjadi konversi satuan: mean_interarrival = 60 / laju (laju per jam menjadi menit per orang) dan travel_time_to_next = jarak / kecepatan × 60 (jam menjadi menit). Rute hanya didefinisikan di sini.
idnamarata-rata antar-kedatangannext_stopwaktu tempuh ke berikutnya
1Air terminal 160/14 = 4,29 menit21,0/30 × 60 = 2 menit
2Air terminal 260/10 = 6 menit34,5/30 × 60 = 9 menit
3Car rental60/24 = 2,5 menit14,5/30 × 60 = 9 menit
  • Kenapa kodenya begini: tabel lokasi membuat rute dan laju ada di satu tempat, sehingga mengubahnya cukup di location_init. Konversi satuan dilakukan di location_set karena di situlah laju per jam dan jarak per mph bertemu satuan program. Indeks 0 tidak dipakai supaya nomor lokasi sama dengan indeks larik (1, 2, 3), sesuai kebiasaan SIMLIB yang juga memakai nomor list dan variabel mulai dari 1.
  • Uji diri: kalau rute diubah jadi 3 → 2 → 1 → 3, bagian mana yang diubah? (Hanya location_init.)

Langkah 6: bus (baris 326 sampai 379)

src/bus-sim.c baris 326-379

/* Entity: bus. Passengers on board are in lists 4-6, one per destination. */
 
void bus_init(void)
{
    /* At t = 0 the bus has just left the car rental, heading to terminal 1. */
 
    bus.location              = locations[LOC_CAR_RENTAL].next_stop;
    bus.status                = BUS_MOVING;
    bus.min_stop_reached      = 0;
    bus.arrival_time          = 0.0;
    bus.last_rental_departure = 0.0;
    bus.current.arrival_time  = 0.0;
    bus.current.origin        = 0;
    bus.current.destination   = 0;
}
 
int bus_passenger_count(void)
{
    int dest;
    int count;
 
    count = 0;
    for (dest = 1; dest <= NUM_LOCATIONS; ++dest)
        count += list_size[LIST_BUS(dest)];
    return count;
}
 
int bus_is_full(void)
{
    return bus_passenger_count() >= BUS_CAPACITY;
}
 
int bus_has_passenger_for(int destination)
{
    return list_size[LIST_BUS(destination)] > 0;
}
 
/* Called at the end of loading. */
void bus_board(const Passenger *p)
{
    passenger_file(p, LIST_BUS(p->destination));
    stats_bus_count_changed(bus_passenger_count());
}
 
/* Called at the start of unloading; FIFO among passengers for this stop. */
Passenger bus_alight(int destination)
{
    Passenger p;
 
    p = passenger_remove_first(LIST_BUS(destination));
    stats_bus_count_changed(bus_passenger_count());
    return p;
}
 
  • Kenapa ada: soal punya satu bus dengan kapasitas, posisi, dan keadaan (sedang jalan, bongkar muat, menunggu).
  • Kenapa dibutuhkan: semua aturan soal (turun dulu baru naik, minimal 5 menit, berangkat bila tidak ada proses) bergantung pada keadaan bus saat ini.
  • Cara kerja:
    • bus_init: di t = 0 bus sudah meninggalkan rental mobil, jadi diset BUS_MOVING menuju lokasi 1.
    • bus_passenger_count menjumlahkan isi list 4, 5, 6; bus_is_full membandingkannya dengan 20. Semua yang ada di bus dihitung, termasuk yang hanya lewat.
    • bus_board memasukkan penumpang ke list tujuannya; bus_alight mengeluarkan penumpang pertama (FIFO) dari list tujuan lokasi sekarang. Keduanya memanggil hook stats_bus_count_changed setiap jumlah di bus berubah.
  • Kenapa kodenya begini: jumlah di bus dihitung dengan menjumlahkan tiga list, bukan variabel penghitung terpisah, supaya tidak mungkin tidak sinkron dengan isi list yang sebenarnya. Hook stats_bus_count_changed dipanggil di bus_board dan bus_alight karena hanya di dua tempat itu jumlahnya berubah.
  • Uji diri: kenapa bus.location saat bergerak berisi stop tujuan? (Supaya event_bus_arrival langsung tahu di mana bus akan tiba tanpa menyimpan variabel lain.)

Langkah 7: arrival (baris 380 sampai 407)

src/bus-sim.c baris 380-407

/* Event: passenger arrival. */
 
void schedule_next_arrival(int location)
{
    event_schedule(sim_time + expon(locations[location].mean_interarrival,
                                    STREAM_INTERARRIVAL(location)),
                   location);
}
 
void event_arrival(int location)
{
    Passenger p;
 
    /* Schedule the next arrival at this location. */
 
    schedule_next_arrival(location);
 
    /* The new passenger joins the queue. */
 
    p = passenger_create(location);
    passenger_file(&p, LIST_QUEUE(location));
 
    /* The bus is still waiting at this stop, so the passenger can board. */
 
    if (bus.status == BUS_IDLE && bus.location == location)
        try_start_load();
}
 
  • Kenapa ada: soal: orang datang di tiap lokasi dengan waktu antar-kedatangan eksponensial yang independen.
  • Kenapa dibutuhkan: tanpa kedatangan tidak ada orang di sistem.
  • Cara kerja (event_arrival):
    1. Jadwalkan kedatangan berikutnya di lokasi yang sama (sim_time + expon(rata-rata, stream lokasi itu)), supaya aliran kedatangan tidak putus.
    2. Buat orang baru dan masukkan ke antrian lokasi itu.
    3. Jika bus sedang BUS_IDLE di lokasi ini, panggil try_start_load() agar orang itu langsung naik. Jika bus sedang memuat, orang itu menunggu dan diambil setelah muatan berjalan selesai.
  • Kenapa kodenya begini: kedatangan berikutnya dijadwalkan lebih dulu supaya aliran kedatangan tidak bergantung pada apa yang terjadi pada orang yang sekarang. Orang yang datang saat bus idle langsung dimuat karena bus masih ada di tempat dan ada kursi. Alternatifnya “snapshot” (hanya yang sudah antre saat bus tiba), yang lebih sederhana tetapi membuat bus mengabaikan orang yang berdiri di depannya selama menunggu 5 menit. Ini tafsiran kelompok, bukan keharusan soal.
  • Uji diri: apa yang terjadi bila orang datang saat bus BUS_UNLOADING? (Ia hanya masuk antrian. Setelah semua turun, event_end_unload memanggil try_start_load, dan ia ikut naik bila bus belum penuh.)

Langkah 8: unloading (baris 439 sampai 471)

src/bus-sim.c baris 439-471

/* Event: unloading, one person at a time. */
 
int try_start_unload(void)
{
    if (!bus_has_passenger_for(bus.location))
        return 0;
 
    bus.current = bus_alight(bus.location);
 
    bus.status = BUS_UNLOADING;
    event_schedule(sim_time + uniform(UNLOAD_TIME_MIN, UNLOAD_TIME_MAX, STREAM_UNLOAD),
                   EVENT_END_UNLOAD);
    return 1;
}
 
void event_end_unload(void)
{
    /* The passenger leaves the system here. */
 
    stats_time_in_system(bus.current.origin, sim_time - bus.current.arrival_time);
 
    /* Unload the next person; when nobody is left, start loading. */
 
    if (try_start_unload())
        return;
 
    if (try_start_load())
        return;
 
    bus.status = BUS_IDLE;
    try_depart();
}
 
  • Kenapa ada: soal: orang diturunkan lebih dulu, FIFO, Uniform(16, 24) detik per orang.
  • Kenapa dibutuhkan: tanpa ini penumpang tidak pernah sampai tujuan, sehingga statistik lama di sistem (f) tidak pernah tercatat.
  • Cara kerja:
    • try_start_unload: jika tidak ada penumpang bertujuan stop ini, kembalikan 0. Jika ada, ambil yang pertama dari bus (bus_alight), set BUS_UNLOADING, jadwalkan EVENT_END_UNLOAD setelah waktu acak U(16, 24) detik, kembalikan 1.
    • event_end_unload: orang itu keluar dari sistem, jadi catat sim_time - arrival_time lewat stats_time_in_system(asal, waktu). Lalu: masih ada yang turun? lanjut. Kalau tidak, mulai muat? lanjut. Kalau keduanya tidak, bus menjadi BUS_IDLE dan dicek apakah boleh berangkat (try_depart).
  • Pola “try then return”: if (try_start_unload()) return; berarti “kalau satu proses berhasil dimulai, selesai untuk sekarang; event berikutnya melanjutkan.” Pola sama dipakai di loading.
  • Kenapa kodenya begini: satu orang per event karena waktu turun tiap orang acak (U(16,24) detik), dan simulasi harus menunggu waktu itu sebelum orang berikutnya. Orang keluar dari list di awal bongkar supaya jumlah di bus langsung berkurang; alternatifnya di akhir bongkar (seperti bus_sim.c), yang hanya mengubah rata-rata (c). Urutan “turun lalu naik” terjamin karena try_start_load baru dipanggil setelah tak ada lagi yang turun.
  • Uji diri: kenapa penumpang keluar dari list bus di awal bongkar, bukan di akhir? (Jumlah di bus langsung berkurang saat orang mulai turun. Ini soal definisi, dan memengaruhi nilai statistik (c).)

Langkah 9: loading (baris 408 sampai 438)

src/bus-sim.c baris 408-438

/* Event: loading, one person at a time until the queue is empty or the bus is full. */
 
int try_start_load(void)
{
    if (list_size[LIST_QUEUE(bus.location)] == 0 || bus_is_full())
        return 0;
 
    /* Take the first person from the queue; the delay in queue ends here. */
 
    bus.current = passenger_remove_first(LIST_QUEUE(bus.location));
    stats_queue_delay(bus.location, sim_time - bus.current.arrival_time);
 
    bus.status = BUS_LOADING;
    event_schedule(sim_time + uniform(LOAD_TIME_MIN, LOAD_TIME_MAX, STREAM_LOAD),
                   EVENT_END_LOAD);
    return 1;
}
 
void event_end_load(void)
{
    bus_board(&bus.current);
 
    /* Load the next person if possible, otherwise the bus becomes idle. */
 
    if (try_start_load())
        return;
 
    bus.status = BUS_IDLE;
    try_depart();
}
 
  • Kenapa ada: soal: orang dinaikkan sampai kapasitas, Uniform(15, 25) detik per orang.
  • Kenapa dibutuhkan: tanpa ini tidak ada orang yang berpindah dari antrian ke bus, dan statistik (b), (c), (f) tidak bermakna.
  • Cara kerja:
    • try_start_load: kalau antrian stop ini kosong atau bus penuh, kembalikan 0. Kalau tidak, ambil orang pertama dari antrian, catat delay-nya (sim_time - arrival_time) lewat stats_queue_delay, set BUS_LOADING, jadwalkan EVENT_END_LOAD setelah U(15, 25) detik, kembalikan 1.
    • event_end_load: orang itu naik (bus_board). Kalau masih bisa memuat berikutnya, mulai lagi (muatan “mengular” satu per satu). Kalau tidak, bus menjadi BUS_IDLE dan dicek try_depart.
  • Catatan: delay antrian dihitung sampai orang diambil dari antrian, bukan sampai selesai naik. Sama dengan keputusan di bus_sim.c kita.
  • Kenapa kodenya begini: delay dicatat saat orang diambil dari antrian, bukan saat selesai naik, karena menunggu berakhir ketika pelayanan dimulai (pola yang sama dengan jobshop.c). Rantai try_start_load → event_end_load → try_start_load membuat proses “mengular” tanpa loop while, karena dalam event-scheduling waktu hanya bergerak lewat event.
  • Uji diri: apa yang menghentikan rantai muat? (Antrian kosong atau bus penuh; try_start_load mengembalikan 0.)

Langkah 10: bus tiba, batas 5 menit, berangkat (baris 472 sampai 526)

src/bus-sim.c baris 472-526

/* Event: bus arrival, 5-minute rule and departure. */
 
void event_bus_arrival(void)
{
    bus.status           = BUS_IDLE;
    bus.arrival_time     = sim_time;
    bus.min_stop_reached = 0;
 
    /* The bus stays at least 5 minutes. */
 
    event_schedule(sim_time + MIN_STOP_TIME, EVENT_MIN_STOP_REACHED);
 
    /* Unload first, then load. */
 
    if (!try_start_unload())
        try_start_load();
}
 
void event_min_stop_reached(void)
{
    bus.min_stop_reached = 1;
    try_depart();
}
 
/* Leave once 5 minutes have passed and no loading/unloading is in process.
   Called when the 5 minutes run out and whenever the bus becomes idle. */
void try_depart(void)
{
    if (bus.min_stop_reached && bus.status == BUS_IDLE)
        bus_depart();
}
 
void bus_depart(void)
{
    int here;
 
    here = bus.location;
 
    stats_bus_stop(here, sim_time - bus.arrival_time);
 
    /* A loop runs from one car-rental departure to the next. */
 
    if (here == LOC_CAR_RENTAL) {
        stats_bus_loop(sim_time - bus.last_rental_departure);
        bus.last_rental_departure = sim_time;
    }
 
    /* Drive to the next stop. */
 
    event_schedule(sim_time + locations[here].travel_time_to_next, EVENT_BUS_ARRIVAL);
    bus.location         = locations[here].next_stop;
    bus.status           = BUS_MOVING;
    bus.min_stop_reached = 0;
}
 
  • Kenapa ada: soal: bus berhenti minimal 5 menit di tiap lokasi, dan bila setelah 5 menit tidak ada proses naik/turun, bus langsung berangkat.
  • Kenapa dibutuhkan: ini pengatur siklus bus. Tanpanya bus tidak berpindah lokasi dan statistik (d) dan (e) kosong.
  • Cara kerja:
    • event_bus_arrival: status = BUS_IDLE, catat arrival_time, min_stop_reached = 0. Jadwalkan EVENT_MIN_STOP_REACHED tepat 5 menit kemudian. Lalu “turun dulu, kalau tidak ada yang turun baru naik”.
    • event_min_stop_reached: min_stop_reached = 1, lalu try_depart().
    • try_depart: berangkat hanya jika dua syarat terpenuhi bersamaan: 5 menit sudah lewat dan bus BUS_IDLE.
    • bus_depart: catat lama berhenti (stats_bus_stop); kalau dari rental mobil, catat lama putaran (stats_bus_loop) dan simpan waktu berangkatnya; jadwalkan EVENT_BUS_ARRIVAL di lokasi berikutnya; ubah location, status = BUS_MOVING.
  • Keberangkatan bukan event. try_depart dipanggil setiap kali bus menjadi idle dan saat 5 menit habis, jadi bus berangkat tepat saat syarat terpenuhi.
  • Bedanya dengan bus_sim.c kita: kita menghitung ulang sim_time - dwell_start lalu membandingkannya dengan 5 menit, sehingga kena bug pembulatan desimal (sampai perlu TIME_EPS). Di sini 5 menit ditandai oleh event yang mengisi flag min_stop_reached, jadi tidak ada perbandingan angka desimal di batasnya dan bug itu tidak bisa muncul.
  • Kenapa kodenya begini: batas 5 menit ditandai oleh event MIN_STOP_REACHED yang mengisi flag, bukan dengan membandingkan sim_time - arrival_time >= 5. Perbandingan desimal rawan salah di batas karena pembulatan, dan itu persis bug yang kita alami di bus_sim.c. Keberangkatan bukan event karena soal menyuruh bus berangkat “segera”; jadi cukup dicek setiap kali bus menjadi idle dan saat 5 menit habis (try_depart). Kedua syarat dicek bersamaan agar bus tidak pernah berangkat di tengah muat atau turun.
  • Uji diri: apa yang terjadi bila 5 menit habis saat bus masih memuat? (try_depart melihat status BUS_LOADING, tidak berangkat. Setelah muatan selesai dan bus jadi BUS_IDLE, try_depart dipanggil lagi dan bus berangkat.)

Langkah 11: main, init_model, report (baris 158 sampai 256)

src/bus-sim.c baris 158-225

int main(void)
{
    /* Open output file. */
 
    outfile = fopen(OUTPUT_FILE, "w");
    if (outfile == NULL)
        fprintf(stderr, "Cannot open %s\n", OUTPUT_FILE);
 
    /* Initialize simlib. */
 
    init_simlib();
 
    /* Set maxatr = max(maximum number of attributes per record, 4). */
 
    maxatr = NUM_ATTRIBUTES;
 
    /* Initialize the model. */
 
    init_model();
 
    /* Run the simulation until the end-simulation event occurs. */
 
    do {
 
        /* Determine the next event. */
 
        timing();
        trace_event();
 
        /* Invoke the appropriate event function. */
 
        switch (next_event_type) {
        case EVENT_ARRIVAL_T1:
        case EVENT_ARRIVAL_T2:
        case EVENT_ARRIVAL_RENTAL:
            event_arrival(next_event_type);
            break;
        case EVENT_BUS_ARRIVAL:
            event_bus_arrival();
            break;
        case EVENT_END_UNLOAD:
            event_end_unload();
            break;
        case EVENT_END_LOAD:
            event_end_load();
            break;
        case EVENT_MIN_STOP_REACHED:
            event_min_stop_reached();
            break;
        case EVENT_END_SIMULATION:
            report(stdout);
            if (outfile != NULL)
                report(outfile);
            break;
        default:
            fprintf(stderr, "Unknown event type %d at time %f\n",
                    next_event_type, sim_time);
            return 1;
        }
 
    } while (next_event_type != EVENT_END_SIMULATION);
 
    if (outfile != NULL)
        fclose(outfile);
 
    return 0;
}
 

src/bus-sim.c baris 226-256

void init_model(void)
{
    int loc;
 
    location_init();
    bus_init();
    stats_init();
 
    /* Schedule the first arrival at each location. */
 
    for (loc = 1; loc <= NUM_LOCATIONS; ++loc)
        schedule_next_arrival(loc);
 
    /* The bus leaves the car rental at t = 0 without stopping, so its first
       event is the arrival at terminal 1. */
 
    event_schedule(locations[LOC_CAR_RENTAL].travel_time_to_next, EVENT_BUS_ARRIVAL);
 
    /* Schedule the end of the simulation. */
 
    event_schedule(SIM_LENGTH, EVENT_END_SIMULATION);
}
 
void report(FILE *out)
{
    fprintf(out, "Simulation Results\n");
    fprintf(out, "------------------\n");
    fprintf(out, "Run length: %.0f minutes\n", SIM_LENGTH);
    stats_report(out);
}
 
  • Kenapa ada: merakit semua bagian menjadi satu program yang bisa dijalankan.
  • Kenapa dibutuhkan: tanpa loop utama, fungsi-fungsi event ada tetapi tidak pernah dipanggil.
  • Cara kerja:
    1. Buka file output, init_simlib(), lalu maxatr = NUM_ATTRIBUTES (urutan sesuai buku).
    2. init_model: siapkan lokasi, bus, dan statistik; jadwalkan kedatangan pertama di tiga lokasi; jadwalkan bus tiba di lokasi 1 setelah waktu tempuh dari rental mobil (bus dianggap sudah berangkat di t = 0); jadwalkan akhir simulasi di SIM_LENGTH (4800 menit).
    3. Loop: timing() mengambil event terdekat dan memajukan sim_time; trace_event (opsional) mencetak keadaan; switch memanggil fungsi event yang sesuai; berhenti setelah event akhir simulasi.
    4. Pada event akhir, report dipanggil dua kali: ke layar dan ke file. report mencetak judul lalu memanggil stats_report.
  • Kenapa kodenya begini: kerangka init_simlib, jadwalkan event awal, lalu loop timing() dengan switch adalah pola baku SIMLIB (lihat jobshop.c). Bus tidak diberi event “tiba di lokasi 3” di t = 0; ia langsung dijadwalkan tiba di lokasi 1 karena soal bilang bus berangkat segera dan antrian masih kosong. report dipanggil dari event akhir simulasi supaya rata-rata terhadap waktu dihitung tepat sampai menit 4800.
  • Uji diri: kenapa kedatangan berikutnya dijadwalkan dari dalam event_arrival, bukan semuanya di awal? (Waktu antar-kedatangan acak, jumlah kedatangan dalam 80 jam tidak diketahui di awal, dan satu kedatangan menentukan kapan yang berikutnya.)

Langkah 12: statistik (baris 527 sampai 644)

src/bus-sim.c baris 527-644

/* Statistics (a)-(f). The hooks pass each value to SIMLIB; stats_report()
   reads the results at the end of the run. All times are in minutes. */
 
void stats_init(void)
{
    /* The bus starts empty. */
 
    timest(0.0, TIMEST_NUM_ON_BUS);
}
 
/* (b) delay in queue */
void stats_queue_delay(int location, double delay)
{
    sampst(delay, SAMPST_QUEUE_DELAY(location));
}
 
/* (c) number on the bus */
void stats_bus_count_changed(int passengers_on_bus)
{
    timest((double) passengers_on_bus, TIMEST_NUM_ON_BUS);
}
 
/* (d) time the bus is stopped */
void stats_bus_stop(int location, double stop_time)
{
    sampst(stop_time, SAMPST_STOP_TIME(location));
}
 
/* (e) loop time */
void stats_bus_loop(double loop_time)
{
    sampst(loop_time, SAMPST_LOOP_TIME);
}
 
/* (f) time in system by arrival location */
void stats_time_in_system(int origin, double time)
{
    sampst(time, SAMPST_TIME_IN_SYSTEM(origin));
}
 
/* One table row. A negative value is printed as "-" (not asked or no data).
   max_is_count = 1 prints the maximum as a whole number (people). */
void stats_report_line(FILE *out, const char *label, double avg, double max,
                       double min, int max_is_count)
{
    char avg_text[32], max_text[32], min_text[32];
 
    if (avg < 0.0) sprintf(avg_text, "-"); else sprintf(avg_text, "%.3f", avg);
    if (max < 0.0) sprintf(max_text, "-");
    else sprintf(max_text, max_is_count ? "%.0f" : "%.3f", max);
    if (min < 0.0) sprintf(min_text, "-"); else sprintf(min_text, "%.3f", min);
 
    fprintf(out, "%-42s | %9s | %9s | %9s\n", label, avg_text, max_text, min_text);
}
 
/* A row from a sampst variable; show_min = 0 prints "-" for the minimum. */
void stats_report_sampst(FILE *out, const char *label, int variable, int show_min)
{
    double count, avg, max, min;
 
    /* sampst(0.0, -variable) reports; copy transfer[] before the next call. */
 
    sampst(0.0, -variable);
    avg   = transfer[1];
    count = transfer[2];
    max   = transfer[3];
    min   = transfer[4];
 
    if (count == 0.0)
        stats_report_line(out, label, -1.0, -1.0, -1.0, 0);
    else
        stats_report_line(out, label, avg, max, show_min ? min : -1.0, 0);
}
 
/* Prints (a)-(f) as one table. */
void stats_report(FILE *out)
{
    int    loc;
    char   label[64];
    double avg, max;
 
    fprintf(out, "Locations: 1 = %s, 2 = %s, 3 = %s\n",
            locations[1].name, locations[2].name, locations[3].name);
    fprintf(out, "All times are in minutes\n\n");
    fprintf(out, "%-42s | %9s | %9s | %9s\n", "", "Average", "Max", "Min");
 
    for (loc = 1; loc <= NUM_LOCATIONS; ++loc) {
        sprintf(label, "%sNumber in queue at location %d", loc == 1 ? "[a] " : "    ", loc);
        filest(LIST_QUEUE(loc));
        avg = transfer[1];
        max = transfer[2];
        stats_report_line(out, label, avg, max, -1.0, 1);
    }
 
    for (loc = 1; loc <= NUM_LOCATIONS; ++loc) {
        sprintf(label, "%sDelay in queue at location %d", loc == 1 ? "[b] " : "    ", loc);
        stats_report_sampst(out, label, SAMPST_QUEUE_DELAY(loc), 0);
    }
 
    timest(0.0, -TIMEST_NUM_ON_BUS);
    avg = transfer[1];
    max = transfer[2];
    stats_report_line(out, "[c] Number on the bus", avg, max, -1.0, 1);
 
    for (loc = 1; loc <= NUM_LOCATIONS; ++loc) {
        sprintf(label, "%sBus stop time at location %d", loc == 1 ? "[d] " : "    ", loc);
        stats_report_sampst(out, label, SAMPST_STOP_TIME(loc), 1);
    }
 
    stats_report_sampst(out, "[e] Bus loop time", SAMPST_LOOP_TIME, 1);
 
    for (loc = 1; loc <= NUM_LOCATIONS; ++loc) {
        sprintf(label, "%sTime in system, arrived at location %d",
                loc == 1 ? "[f] " : "    ", loc);
        stats_report_sampst(out, label, SAMPST_TIME_IN_SYSTEM(loc), 1);
    }
}
 
  • Kenapa ada: soal meminta enam kelompok statistik (a) sampai (f) di akhir simulasi: rata-rata dan maksimum jumlah di antrian, delay antrian, jumlah di bus, lama bus berhenti, lama putaran, dan lama orang di sistem.
  • Kenapa dibutuhkan: tanpa ini program jalan tetapi tidak menghasilkan jawaban apa pun. Seluruh tujuan tugas ini adalah angka-angka tersebut.
  • Alurnya tiga tahap. (1) Model memanggil hook pada saat yang benar. (2) Hook menitipkan angka ke SIMLIB. (3) Di akhir, stats_report membaca kembali dan mencetaknya. Statistik tidak mengubah keadaan model.

Peta hook:

SoalHookIsi hookDipanggil oleh
(a)tidak adaSIMLIB melacak list 1-3 sendiri
(b)stats_queue_delay(loc, d)sampst(d, SAMPST_QUEUE_DELAY(loc))try_start_load, saat orang diambil dari antrian
(c)stats_bus_count_changed(n)timest(n, TIMEST_NUM_ON_BUS)bus_board dan bus_alight, tiap jumlah di bus berubah
(d)stats_bus_stop(loc, t)sampst(t, SAMPST_STOP_TIME(loc))bus_depart
(e)stats_bus_loop(t)sampst(t, SAMPST_LOOP_TIME)bus_depart, hanya saat berangkat dari rental
(f)stats_time_in_system(o, t)sampst(t, SAMPST_TIME_IN_SYSTEM(o))event_end_unload, saat orang selesai turun

Bagian-bagiannya:

  • stats_init: timest(0.0, TIMEST_NUM_ON_BUS) menetapkan level jumlah-di-bus mulai dari 0 pada t = 0. Rata-rata dan maksimum (c) tetap benar tanpa baris ini, tetapi nilai minimumnya akan salah (terisi 1, nilai pertama yang tercatat). Minimum (c) tidak dicetak, jadi ini soal kerapian.
  • Lima hook: masing-masing satu baris panggilan SIMLIB. stats_bus_count_changed memakai timest, bukan sampst, karena yang dicatat adalah level yang bertahan sekian lama.
  • stats_report_line: mencetak satu baris tabel: label, rata-rata, maks, dan min, dipisah tanda |. Nilai negatif dicetak sebagai tanda - (dipakai untuk kolom yang tidak diminta soal, atau bila belum ada data). max_is_count = 1 mencetak maks sebagai bilangan bulat (untuk jumlah orang). Semua angka dalam menit.
  • stats_report_sampst: menyiapkan satu baris dari variabel sampst. sampst(0.0, -variable) (nilai negatif berarti minta laporan), lalu hasilnya segera disalin dari transfer[1..4] (rata-rata, jumlah, maks, min). Jika jumlahnya 0, SIMLIB mengisi maks dan min dengan angka sentinel 1e30, jadi baris dicetak dengan tanda -. Parameter show_min bernilai 0 untuk (b) karena soal hanya meminta rata-rata dan maksimum delay.
  • stats_report: mencetak seluruh tabel (a) sampai (f). (a) memakai filest(LIST_QUEUE(loc)); (c) memakai timest(0.0, -TIMEST_NUM_ON_BUS); sisanya memakai stats_report_sampst. Dipanggil dua kali oleh main (ke layar dan ke file); aman karena membaca laporan tidak mengubah angka.
  • Satuan dan bentuk: semua angka sudah menit, jadi tidak ada konversi saat mencetak. Laporannya satu tabel; soalnya tidak ditulis ulang, hanya label baris yang singkat.
  • Kenapa (a) tanpa hook, (c) dengan hook: list antrian adalah list SIMLIB sehingga panjangnya dilacak otomatis. Jumlah di bus adalah gabungan tiga list (4 sampai 6), bukan satu list, jadi model memberi tahu lewat hook tiap jumlahnya berubah.
  • Kenapa kodenya begini: statistik dipisah dari model lewat hook agar model tidak perlu tahu cara pencatatan; mengubah statistik tidak berisiko merusak logika bus. sampst dipakai untuk nilai per kejadian dan timest untuk level yang berubah terhadap waktu, karena rata-ratanya dihitung berbeda. (a) memakai filest karena SIMLIB sudah melacak list sendiri. Tabel dicetak lewat fungsi satu baris agar formatnya konsisten dan kasus belum ada data (n = 0) ditangani di satu tempat.
  • Uji diri: kenapa nilai hasil harus langsung disalin dari transfer[]? (Panggilan SIMLIB berikutnya menimpanya.)

Langkah 13: trace (baris 645 sampai 683)

src/bus-sim.c baris 645-683

/* Trace, printed only when ENABLE_TRACE is 1. */
 
const char *event_name(int type)
{
    switch (type) {
    case EVENT_ARRIVAL_T1:       return "ARRIVAL_T1";
    case EVENT_ARRIVAL_T2:       return "ARRIVAL_T2";
    case EVENT_ARRIVAL_RENTAL:   return "ARRIVAL_RENTAL";
    case EVENT_BUS_ARRIVAL:      return "BUS_ARRIVAL";
    case EVENT_END_UNLOAD:       return "END_UNLOAD";
    case EVENT_END_LOAD:         return "END_LOAD";
    case EVENT_MIN_STOP_REACHED: return "MIN_STOP_REACHED";
    case EVENT_END_SIMULATION:   return "END_SIMULATION";
    }
    return "UNKNOWN";
}
 
const char *bus_status_name(BusStatus status)
{
    switch (status) {
    case BUS_MOVING:    return "MOVING";
    case BUS_IDLE:      return "IDLE";
    case BUS_UNLOADING: return "UNLOADING";
    case BUS_LOADING:   return "LOADING";
    }
    return "?";
}
 
void trace_event(void)
{
#if ENABLE_TRACE
    printf("%9.3f  %-16s  bus@%d %-9s  on_bus=%2d  q1=%3d q2=%3d q3=%3d\n",
           sim_time, event_name(next_event_type), bus.location,
           bus_status_name(bus.status), bus_passenger_count(),
           list_size[LIST_QUEUE(LOC_TERMINAL_1)],
           list_size[LIST_QUEUE(LOC_TERMINAL_2)],
           list_size[LIST_QUEUE(LOC_CAR_RENTAL)]);
#endif
}
  • Kenapa ada: untuk memeriksa apakah bus dan antrian bergerak sesuai kronologi yang benar (teknik “trace”).
  • Kenapa dibutuhkan: tanpa melihat jalannya event, bug seperti bus macet hanya ketahuan dari angka akhir yang aneh.
  • Cara kerja: bila ENABLE_TRACE bernilai 1, setiap event mencetak satu baris: waktu (menit), nama event, lokasi dan status bus, jumlah di bus, dan panjang tiga antrian. Keadaan yang tampil adalah keadaan sebelum event diproses. Bila 0, trace_event tidak melakukan apa-apa.

Langkah 14: Makefile

Makefile

# Car-rental bus simulation (Law & Kelton, Problem 2.38) - C + SIMLIB.
#
#   make          build bus-sim
#   make run      build and run the 80-hour simulation (report also in bus-sim.out)
#   make trace    build bus-sim_trace (event trace, first 60 simulated minutes) and run it
#   make clean    remove build output
 
CC       = gcc
CFLAGS   = -std=c89 -Wall -Wextra -pedantic -O2 -Ilib/simlib
LIBFLAGS = -O2 -Ilib/simlib
LDLIBS   = -lm
 
ifeq ($(OS),Windows_NT)
EXE = .exe
else
EXE =
endif
 
TARGET        = bus-sim$(EXE)
TRACE_TARGET  = bus-sim_trace$(EXE)
TRACE_MINUTES = 60.0
 
SRC        = src/bus-sim.c
SIMLIB_HDR = lib/simlib/simlib.h lib/simlib/simlibdefs.h
SIMLIB_OBJ = simlib.o
 
.PHONY: all run trace clean
 
all: $(TARGET)
 
$(TARGET): $(SRC) $(SIMLIB_HDR) $(SIMLIB_OBJ)
	$(CC) $(CFLAGS) -o $@ $(SRC) $(SIMLIB_OBJ) $(LDLIBS)
 
$(TRACE_TARGET): $(SRC) $(SIMLIB_HDR) $(SIMLIB_OBJ)
	$(CC) $(CFLAGS) -DENABLE_TRACE=1 -DSIM_LENGTH=$(TRACE_MINUTES) -o $@ $(SRC) $(SIMLIB_OBJ) $(LDLIBS)
 
# SIMLIB is textbook code: compiled separately, without our strict warning flags.
$(SIMLIB_OBJ): lib/simlib/simlib.c lib/simlib/simlibdefs.h
	$(CC) $(LIBFLAGS) -c lib/simlib/simlib.c -o $@
 
run: $(TARGET)
	./$(TARGET)
 
trace: $(TRACE_TARGET)
	./$(TRACE_TARGET)
 
clean:
	$(RM) $(TARGET) $(TRACE_TARGET) $(SIMLIB_OBJ) bus-sim.out
  • Kenapa ada: menyingkat perintah compile yang panjang.
  • Cara kerja: make membangun bus-sim; make run membangun dan menjalankan 4800 menit; make trace membangun versi dengan trace dan hanya 60 menit simulasi; make clean menghapus hasil build.
  • Dua hal yang perlu diketahui: (1) kode buatan temanmu di-compile dengan -std=c89 -Wall -Wextra -pedantic (tidak boleh komentar // atau deklarasi variabel di tengah blok); (2) SIMLIB di-compile terpisah tanpa aturan ketat itu, karena itu kode buku. Di Windows butuh mingw32-make, atau jalankan lewat WSL.

Bagian 3: menelusuri hasil nyata (make trace, 60 menit simulasi)

Setiap baris: waktu (menit), event, posisi dan status bus, jumlah di bus, panjang antrian 1/2/3. Keadaan yang tampil adalah sebelum event diproses. Cuplikan dari make trace:

    9.000  BUS_ARRIVAL       bus@1 MOVING     on_bus= 0  q1=  2 q2=  0 q3=  6
    9.338  END_LOAD          bus@1 LOADING    on_bus= 0  q1=  1 q2=  0 q3=  6
    9.596  ARRIVAL_T1        bus@1 LOADING    on_bus= 1  q1=  0 q2=  0 q3=  6
    9.753  END_LOAD          bus@1 LOADING    on_bus= 1  q1=  1 q2=  0 q3=  6
   10.004  ARRIVAL_T2        bus@1 LOADING    on_bus= 2  q1=  0 q2=  0 q3=  6
   10.166  END_LOAD          bus@1 LOADING    on_bus= 2  q1=  0 q2=  1 q3=  6
   13.709  ARRIVAL_T1        bus@1 IDLE       on_bus= 3  q1=  0 q2=  1 q3=  6
   14.000  MIN_STOP_REACHED  bus@1 LOADING    on_bus= 3  q1=  0 q2=  1 q3=  6
   14.072  END_LOAD          bus@1 LOADING    on_bus= 3  q1=  0 q2=  1 q3=  6
   15.927  ARRIVAL_RENTAL    bus@2 MOVING     on_bus= 4  q1=  0 q2=  1 q3=  6
   16.072  BUS_ARRIVAL       bus@2 MOVING     on_bus= 4  q1=  0 q2=  1 q3=  7
   ...
   21.072  MIN_STOP_REACHED  bus@2 IDLE       on_bus= 6  q1=  2 q2=  0 q3=  8
   ...
   30.072  BUS_ARRIVAL       bus@3 MOVING     on_bus= 6  q1=  5 q2=  2 q3= 15
   30.412  END_UNLOAD        bus@3 UNLOADING  on_bus= 5  q1=  5 q2=  2 q3= 15
   30.707  END_UNLOAD        bus@3 UNLOADING  on_bus= 4  q1=  5 q2=  2 q3= 15

Cara membacanya:

  1. 9,000 bus tiba di lokasi 1. Waktu tempuh 3 → 1 adalah 4,5/30 × 60 = 9 menit, cocok. Antrian 1 berisi 2 orang. Tidak ada yang turun (bus kosong), jadi bus langsung memuat; END_LOAD pertama di 9,338.
  2. 9,596 satu orang datang di terminal 1 saat bus sedang memuat. Ia hanya masuk antrian dan diambil setelah muatan berjalan selesai.
  3. 13,709 orang datang saat bus IDLE di lokasi 1 (antrian sudah kosong, bus menunggu 5 menit). Ia langsung dimuat, terlihat dari baris berikutnya berstatus LOADING.
  4. 14,000 = 9,000 + 5 menit. Batas 5 menit tercapai, tetapi bus masih LOADING, jadi tidak berangkat. Baru setelah END_LOAD di 14,072 bus berangkat (baris berikutnya bus sudah menuju lokasi 2).
  5. 16,072 bus tiba di lokasi 2: 14,072 + 2 menit (1 mil). Tepat 5 menit kemudian, 21,072, bus IDLE dan langsung berangkat.
  6. 30,072 bus tiba di lokasi 3: 21,072 + 9 menit. on_bus 6, antrian rental 15 orang. Orang bertujuan lokasi 3 turun satu per satu (on_bus 5, 4, …). Jumlah di bus berkurang di awal tiap bongkar, itu sebabnya on_bus sudah turun saat END_UNLOAD tercetak.

Latihan: ambil 3 baris lain dari make trace dan jelaskan dengan cara yang sama.


Bagian 4: mesin status bus

DariPemicuKe
(awal)t = 0, bus meninggalkan rental mobilMOVING
MOVINGbus tiba, ada yang turun di siniUNLOADING
MOVINGbus tiba, tak ada yang turun, antrian ada, belum penuhLOADING
MOVINGbus tiba, tak ada yang dikerjakanIDLE
UNLOADINGselesai turun, masih ada yang turunUNLOADING
UNLOADINGselesai turun, antrian ada, belum penuhLOADING
UNLOADINGselesai turun, tak ada yang dikerjakanIDLE
LOADINGselesai naik, antrian ada, belum penuhLOADING
LOADINGselesai naik, tak ada yang dikerjakanIDLE
IDLEorang datang saat bus menunggu, belum penuhLOADING
IDLE5 menit tercapai dan idleMOVING

Hal yang selalu harus benar (bisa dipakai untuk menguji):

  • Jumlah di bus selalu antara 0 dan 20.
  • Saat MOVING, tidak ada END_LOAD/END_UNLOAD yang tertunda.
  • Setiap stop berlangsung minimal 5 menit; bus berangkat hanya bila min_stop_reached == 1 dan status IDLE.
  • Tidak ada penumpang di bus yang tujuannya sama dengan asalnya.
  • Begitu pemuatan dimulai di suatu stop, tidak ada lagi yang perlu diturunkan di situ.

Bagian 5: perbandingan dengan bus_sim.c kita

Halbus_sim.c (kita)bus-sim.c (temanmu)
Struktursatu berkas, ±335 barissatu berkas, 683 baris, bagian per entitas dan event
Satuan waktujammenit
Data penumpangtransfer[3..5]struct Passenger, disalin segera dari transfer[]
Aturan 5 menitbandingkan waktu, perlu TIME_EPS dan flag depart_check_scheduledevent MIN_STOP_REACHED yang mengisi flag; tak ada perbandingan desimal
Keberangkatancheck_departure dipanggil dari beberapa tempattry_depart dipanggil saat idle dan saat 5 menit habis
Statistiksudah lengkap di report()sudah diisi: hook ke SIMLIB dan stats_report
Nomor list, stream, rutesamasama
Tafsir soal (rute 4,5/1/4,5, muat dinamis, tujuan diundi saat tiba)yaya
Standar Cgnu11C89 ketat (-pedantic)

Logikanya sama. Sebelum modul disatukan, repo ini punya versi modular yang jam-based; trace versi modular itu dan trace versi satu file ini identik (urutan event, status bus, jumlah di bus, panjang antrian), hanya waktunya berbeda skala (angka versi lama × 60 = menit). Jadi penyatuan file tidak mengubah perilaku model.


Bagian 6: statistik (a) sampai (f): hasil nyata dan pemeriksaan kewajaran

Hasil make run (4800 menit simulasi, waktu jalan sekitar 0,06 detik), semua waktu dalam menit:

Simulation Results
------------------
Run length: 4800 minutes
Locations: 1 = Air terminal 1, 2 = Air terminal 2, 3 = Car rental
All times are in minutes
 
                                           |   Average |       Max |       Min
[a] Number in queue at location 1          |     7.368 |        27 |         -
    Number in queue at location 2          |     4.888 |        17 |         -
    Number in queue at location 3          |     9.321 |        29 |         -
[b] Delay in queue at location 1           |    31.414 |    91.934 |         -
    Delay in queue at location 2           |    29.820 |   130.860 |         -
    Delay in queue at location 3           |    24.130 |    67.485 |         -
[c] Number on the bus                      |    13.593 |        20 |         -
[d] Bus stop time at location 1            |     6.698 |    10.171 |     5.000
    Bus stop time at location 2            |     5.473 |     9.287 |     5.000
    Bus stop time at location 3            |    11.402 |    13.743 |     5.572
[e] Bus loop time                          |    43.573 |    48.069 |    35.715
[f] Time in system, arrived at location 1  |    51.927 |   112.294 |    17.738
    Time in system, arrived at location 2  |    45.544 |   147.004 |    13.929
    Time in system, arrived at location 3  |    41.724 |    88.289 |    11.532

Cara membaca:

  • (a) terminal 1: rata-rata 7,368 orang menunggu, paling banyak 27.
  • (b) orang di terminal 1 rata-rata menunggu 31,4 menit, paling lama 91,9 menit.
  • (c) rata-rata 13,6 dari 20 kursi terisi; bus pernah penuh (maks 20).
  • (d) bus berhenti paling singkat 5,000 menit, tepat batas bawah dari soal.
  • (e) 110 putaran, rata-rata 43,6 menit.
  • (f) orang dari terminal 1 rata-rata 51,9 menit di sistem, dari antrian sampai turun.

Pemeriksaan kewajaran

PemeriksaanBatas atau harapanHasilLolos?
maks (c)tidak lebih dari 2020ya
min (d)tidak kurang dari 5,000 menit5,000ya
min (e)tidak kurang dari 35 menit (20 menit jalan + 3 × 5 menit berhenti)35,715ya
jumlah putaran4800 ÷ rata-rata putaran ≈ 1104800 ÷ 43,573 = 110,2; hasil 110ya
Hukum Little terminal 1(14 ÷ 60) orang per menit × 31,414 menit7,33 vs (a) 7,368ya
Hukum Little rental(24 ÷ 60) orang per menit × 24,130 menit9,65 vs (a) 9,321ya, mendekati

Daftar uji dari DESIGN.md bagian 7, sudah dicek:

  • Compile tanpa warning dengan -std=c89 -Wall -Wextra -pedantic (0 warning).
  • Bus tiba di lokasi 1 pertama kali pada t = 9 menit (baris pertama trace di Bagian 3).
  • Bus mengunjungi stop berurutan 1, 2, 3 (terlihat di trace).
  • Tiap stop minimal 5 menit (min (d) = 5,000).
  • Bus tidak berangkat saat memuat (trace pada t = 14,000).
  • Orang yang datang saat bus menunggu idle langsung dimuat (trace pada t = 13,709).
  • Jumlah di bus tak pernah lebih dari 20 (maks (c)).
  • Kedatangan sesuai laju: terminal 1 mencatat 1120 orang naik bus (14 × 80), terminal 2 mencatat 784, rental 1854 (jumlah ini tidak dicetak di tabel; dicek dengan menambah kolom n sementara).
  • Tiap putaran minimal 35 menit (min (e) = 35,715).

Dibandingkan dengan bus_sim.c kita

Statistik (a), (b), (d), (e), (f) identik dengan hasil bus_sim.c (angka waktu bus_sim.c dikali 60). Dua program yang ditulis terpisah, dengan struktur kode berbeda, menghasilkan angka yang sama untuk lima dari enam statistik. Itu bukti kuat bahwa keduanya mengimplementasikan aturan yang sama dengan benar.

Satu-satunya beda ada di (c): 13,593 di sini vs 13,853 di bus_sim.c. Penyebabnya definisi, bukan bug. Di sini penumpang dihitung keluar dari bus di awal bongkar (bus_alight), sedangkan di bus_sim.c ia keluar di akhir bongkar, jadi rata-ratanya sedikit lebih tinggi. Soal tidak menentukan mana yang benar. Pilih satu dan nyatakan di deskripsi.

Jebakan yang sengaja dihindari

  1. Baca transfer[] segera. Setiap panggilan SIMLIB bisa menimpanya, jadi nilai disalin ke variabel lokal tepat setelah panggilan.
  2. Dua layout hasil berbeda. sampst memberi 4 nilai (rata-rata, n, maks, min); timest dan filest memberi 3 (rata-rata, maks, min). Tertukar indeks memberi angka salah tanpa error.
  3. Observasi kosong. sampst memberi ±1e30 bila belum ada data; dicegah dengan pengecekan count == 0.
  4. Yang belum selesai di t = 4800 tidak dihitung. Orang yang masih di antrian atau di bus tidak ikut (b) dan (f), dan putaran yang sedang berjalan tidak ikut (e).

Bagian 7: apa yang boleh dan tidak boleh kamu klaim

Boleh diklaim:

  • Repo ini di-compile tanpa warning dan jalan 4800 menit sampai selesai, dengan statistik yang sudah diisi.
  • Semua pemeriksaan kewajaran di Bagian 6 lolos.
  • Hasil (a), (b), (d), (e), (f) sama dengan implementasi satu berkas kita.
  • Rute 4,5 / 1 / 4,5 mil, penomoran list dan stream, dan aturan naik-turun-5 menit sesuai tafsiran yang kita sepakati.
  • Aturan 5 menit memakai event penanda, bukan perbandingan waktu desimal.
  • Penyatuan ke satu file tidak mengubah perilaku model (trace identik dengan versi modular).

Jangan diklaim:

  • “Model ini karya saya.” Modelnya ditulis temanmu; lib/simlib/ dari buku. Bagianmu: fungsi statistik, pengecekan, output, dan penjelasan. Fungsi statistik itu ditulis dengan bantuan asisten, jadi jelaskan hanya yang benar-benar kamu pahami.
  • “Hasilnya benar karena sama dengan bus_sim.c.” Dua program yang cocok mendukung kebenaran, tetapi keduanya memakai tafsir soal yang sama (rute, muat dinamis, tujuan diundi saat tiba). Jika tafsirnya keliru, keduanya keliru bersama.
  • “Jarak rute pasti sesuai maksud soal.” Itu tafsiran atas Gambar 2.72, bukan angka tertulis di soal.
  • “Hasil satu kali jalan ini representatif.” Itu satu replikasi dengan seed bawaan SIMLIB.
  • “(c) pasti 13,593.” Angka itu bergantung pada definisi kapan orang dianggap keluar dari bus.

Status git: perubahan statistik di src/bus-sim.c baru ada di salinan lokal (belum di-commit dan belum di-push). Pada riwayat repo, stats.c versi modular pernah ditambahkan lalu dihapus oleh commit “delete stats”. Beri tahu temanmu sebelum berkas ini digabung, supaya tidak ada dua orang mengubah fungsi stats_* yang sama.


Bagian 8: soal latihan

Jawab dengan kata-katamu, lalu cek ke langkah yang disebut.

  1. Kenapa semua angka soal dikumpulkan di bagian atas dan satuannya menit? (Langkah 1)
  2. Kenapa passenger_remove_first langsung menyalin data ke struct? (Langkah 4)
  3. Apa isi list 1-3 dan list 4-6? Kenapa list bus dibagi menurut tujuan? (Langkah 2, 6)
  4. Jelaskan perbedaan BUS_MOVING, BUS_IDLE, BUS_UNLOADING, BUS_LOADING. (Langkah 3, Bagian 4)
  5. Apa yang terjadi bila orang datang saat bus IDLE di lokasinya? Bagaimana bila bus sedang LOADING? (Langkah 7)
  6. Kenapa bus tidak berangkat tepat di menit ke-5 bila masih memuat? Kapan akhirnya berangkat? (Langkah 10, Bagian 3 baris 14,000)
  7. Kenapa keberangkatan bukan event tersendiri? (Langkah 10)
  8. Kenapa repo ini tidak kena bug bus macet yang kita alami di bus_sim.c? (Langkah 10, Bagian 5)
  9. Bagaimana laju 14 orang per jam menjadi 4,29 menit antar-kedatangan di kode? (Langkah 5)
  10. Apa beda sampst dan timest? Beri satu contoh statistik untuk masing-masing. (Bagian 0A)
  11. Kenapa statistik (a) tidak butuh hook, sedangkan (c) butuh? (Bagian 6)
  12. Apa arti sampst(0.0, -var)? Kenapa nilainya negatif? (Bagian 0A)
  13. Kenapa nilai (c) bisa berbeda antara versi ini dan bus_sim.c? (Bagian 6)
  14. Jika dosen bertanya “bagaimana kamu tahu angkanya benar?”, sebutkan tiga pemeriksaan yang kamu lakukan. (Bagian 6)
  15. Tulis ulang satu langkah dari Bagian 2 dengan format: “[bagian] ada karena [kalimat soal]. Tanpanya [akibat]. Cara kerjanya: [3-5 langkah].”

Bagian 9: cara menjalankan dari nol

Butuh gcc dan GNU make. Di Windows: mingw32-make (MSYS2), atau jalankan lewat WSL.

cd tubes1-simlib-bus
make          # build bus-sim
make run      # jalankan 4800 menit; hasil tercetak dan tertulis ke bus-sim.out
make trace    # versi trace: 60 menit simulasi, satu baris per event
make clean    # hapus hasil build (termasuk bus-sim.out)

Tanpa make:

gcc -Ilib/simlib -o bus-sim src/bus-sim.c lib/simlib/simlib.c -lm

Catatan: bus-sim.out ada di .gitignore, jadi tidak tersimpan di git. Buat sendiri dengan make run dan simpan salinannya sebagai output yang didemokan. Lama simulasi bisa diubah lewat -DSIM_LENGTH=... (dalam menit) pada baris compile.


Bagian J: pengumpulan dan persiapan demo

Yang dikumpulkan (dalam satu PDF)

ItemSumberCatatan
Deskripsi masalah, satu halamanBagian B (tabel keputusan) sebagai bahan; tulis ringkas dengan kata-katamutulis semua keputusan tafsir; definisi (c) ikut ditulis
Source codesrc/bus-sim.c (dan sebut bahwa lib/simlib/ adalah SIMLIB dari buku)harus sama persis dengan yang didemokan
Outputhasil make run, yaitu bus-sim.outjalankan ulang bila kode berubah, supaya sama

Sebelum demo

  1. Pastikan versi kode yang dikumpulkan dan yang didemokan sama; jalankan make run dari kode itu dan simpan bus-sim.out.
  2. Cek tidak ada warning saat compile.
  3. Cocokkan angka dengan tabel di Bagian 6 (harus identik karena seed bawaan SIMLIB tetap).
  4. Latih menjelaskan tiap bagian kode dengan format “ada karena …, tanpanya …, cara kerjanya …” (soal latihan di Bagian 8).

Urutan demo yang disarankan

LangkahYang ditunjukkanKata kunci penjelasan
1soal dan rute (Gambar 2.72)jarak 4,5 / 1 / 4,5 mil, hasil tafsiran kita
2struktur bus-sim.cparameter, entitas, event, statistik, trace
3make run dan tabel hasilsatuan menit; (a) sampai (f)
4make tracebukti aturan 5 menit dan urutan 1, 2, 3
5pemeriksaan kewajaranmin (d) = 5,000; min (e) ≥ 35; Hukum Little
6batassatu kali jalan, tafsiran kelompok

Pembagian peran yang jujur

BagianPemilik
Model di bus-sim.c (selain statistik)temanmu
Fungsi statistik, pemeriksaan, output, deskripsikamu (statistik ditulis dengan bantuan asisten)
lib/simlib/buku Law & Kelton

Dosen bisa bertanya ke siapa saja, jadi kalian berdua harus bisa menjelaskan seluruh program. Jelaskan hanya yang benar-benar dipahami, dan jangan mengaku menulis bagian yang bukan milikmu.


Bagian K: pertanyaan yang mungkin ditanyakan dosen

PertanyaanJawaban singkatRujukan
Kenapa memakai simulasi, bukan rumus antrian?Ada tiga antrian, satu pelayan yang berpindah, kapasitas, minimal 5 menit, dan waktu layanan acak; tidak ada rumus tertutup untuk gabungan itu.Bagian A
Apa itu event-driven?Jam simulasi melompat dari satu kejadian ke kejadian berikutnya, bukan berdetak per detik.Bagian A
Bagaimana jarak antar-halte didapat?Dijumlahkan dari ruas jalan di Gambar 2.72 menurut arah berlawanan jarum jam: 4,5 / 1 / 4,5 mil. Itu tafsiran kami.Bagian B
Kenapa tujuan penumpang diundi saat datang?Stream 6 dinamai “destination of an arrival”, dan dengan begitu stream dipakai tepat sekali per orang.Langkah 4
Bagaimana aturan 5 menit dibuat?Event MIN_STOP_REACHED mengisi flag; bus berangkat saat flag menyala dan tidak ada proses naik/turun.Langkah 10
Kenapa satuannya menit?Data soal campur jam, detik, dan mph; menit membuat angka seperti 5 menit tetap utuh. Konversi dilakukan sekali.Langkah 1
Apa beda sampst dan timest?sampst rata-rata per kejadian (delay); timest rata-rata terhadap waktu untuk level (jumlah di bus).Bagian 0A
Kenapa data penumpang disalin ke struct?event_schedule dan timing menimpa transfer[1] dan transfer[2].Langkah 4
Bagaimana kamu tahu hasilnya benar?Batas logis (maks di bus 20, min berhenti 5, min putaran 35), 110 putaran sesuai 4800 menit, Hukum Little, dan kecocokan dengan program satu berkas kami.Bagian 6
Kenapa (c) 13,6, bukan angka lain?Bergantung definisi: penumpang dihitung keluar saat mulai turun (13,593); bila saat selesai turun hasilnya 13,853.Bagian 6
Apakah hasilnya pasti?Tidak. Itu satu kali jalan dengan seed bawaan SIMLIB; untuk kesimpulan kuat perlu beberapa replikasi.Bagian 7
Masalah apa yang kalian temui?Pada versi satu berkas kami: data penumpang di transfer[1..2] tertimpa SIMLIB, dan perbandingan 5 menit desimal bisa membuat bus macet; keduanya dihindari di desain repo ini.Bagian 5
Bagaimana jika rute atau laju diubah?Cukup location_init (rute) atau konstanta di bagian atas.Langkah 5