Bagian 1 meninggalkan persamaan di atas kertas; sekarang kita jalankan. Dua berkas menjadi tulang punggung tahap ini: modelPertumbuhan.m yang menghitung turunan, dan jalankanSimulasi.m yang memanggil solver ode45 dengan pengaturan yang membuat hasilnya stabil dan bisa diuji. Setiap keputusan implementasi — bentuk argumen, vektor waktu, toleransi — diambil agar hasil simulasi layak dipercaya dan layak dibandingkan dengan solusi analitik.
Yang kita bangun pada bagian ini:
modelPertumbuhan.m— fungsi ODE tiga argumen,jalankanSimulasi.m— panggilanode45deterministik denganodeset,- validasi terhadap solusi analitik lewat
assert, - struktur hasil berupa struct dengan
t,P, danwaktuSetengahK.
Fungsi Model: modelPertumbuhan.m #
Solver ODE di MATLAB tidak menerima persamaan sebagai simbol; solver menerima fungsi yang diberi waktu t dan keadaan P, lalu mengembalikan turunan dP/dt. Berikut seluruh isi src/modelPertumbuhan.m:
function dPdt = modelPertumbuhan(t, P, params)
% MODELPERTUMBUHAN Laju pertumbuhan biomassa model logistik.
% dPdt = modelPertumbuhan(t, P, params) menghitung turunan dP/dt
% untuk persamaan logistik:
% dP/dt = r * P * (1 - P/K)
% dengan params struct berisi field:
% r - laju pertumbuhan intrinsik (per hari)
% K - kapasitas lingkungan (satuan biomassa)
%
% Argumen t tidak muncul di rumus (model otonom) tetapi wajib
% diterima karena ode45 selalu memanggil fungsi model dengan
% pasangan (t, P).
r = params.r;
K = params.K;
dPdt = r * P * (1 - P / K);
endMari kita bedah baris-baris kode di atas:
- Baris 1 (
function dPdt = modelPertumbuhan(t, P, params)): tanda tangan fungsi meniru kontrak solver — waktu dulu, keadaan kemudian — lalu menambah argumen ketigaparamsuntuk parameter model. - Baris 10-12 (komentar tentang argumen
t):ttidak dipakai rumus karena model ini otonom. Tetap menerimatmembuat fungsi kompatibel dengan solver tanpa pembungkus tambahan, dan dengan solver non-otonom di proyek lain (misalnya laju musiman di Bagian 4). - Baris 13-14 (
r = params.r; K = params.K;): parameter diambil dari struct sekali di awal. Seluruh proyek memakai structparams— bukan variabel global — sehingga tiap simulasi membawa fotokopi parameternya sendiri. - Baris 15 (
dPdt = r * P * (1 - P / K);): seluruh model logistik hidup dalam satu baris. Operasinya skalar demi skalar karena keadaanPdi sini memang skalar (satu jenis biomassa).
Bentuk alternatif yang juga sah adalah anonymous function @(t, P) r * P * (1 - P/K). Kita tetap memilih berkas fungsi karena tiga alasan: parameter masuk sebagai argumen sehingga fungsi bisa dipakai ulang oleh sweep tanpa menangkap ulang variabel, fungsi bisa diuji langsung dari tests/, dan blok komentar dokumentasinya menjadi kontrak yang terbaca lewat help modelPertumbuhan.
Menjalankan ode45: jalankanSimulasi.m #
MATLAB menyediakan banyak solver ODE; ode45 adalah kerja kuda untuk model non-stiff dan halus seperti logistik. Solver ini menjalankan Runge-Kutta adaptif orde 4(5) — langkahnya membesar saat kurva landai dan mengecil saat kurva curam — dan sudah kita pakai pada chapter Simulasi Sistem dan Pemodelan Fisika. Berikut seluruh isi src/jalankanSimulasi.m:
function hasil = jalankanSimulasi(params, tspan)
% JALANKANSIMULASI Menjalankan simulasi logistik dengan ode45.
% hasil = jalankanSimulasi() menjalankan parameter default:
% r = 0.08 per hari, K = 100 satuan, P0 = 1,
% output setiap hari selama 120 hari (tspan = 0:1:120).
% hasil = jalankanSimulasi(params) memakai params kustom (struct
% dengan field r, K, P0).
% hasil = jalankanSimulasi(params, tspan) memakai vektor waktu
% output sendiri (harus menaik, dimulai dari 0).
%
% hasil : struct berisi field
% t - vektor waktu (hari), 121x1 untuk default
% P - biomassa (satuan), sepanjang t
% params - parameter yang dipakai simulasi
% waktuSetengahK - t saat P = K/2 (interpolasi linear, hari);
% NaN bila P tidak pernah mencapai K/2 pada
% rentang tspan
%
% Dua keputusan membuat hasil deterministik lintas MATLAB/Octave:
% tspan diberikan sebagai vektor waktu penuh sehingga kedua mesin
% wajib mengeluarkan nilai tepat pada waktu itu, dan toleransi
% diikat ketat lewat odeset. Dua pemanggilan menghasilkan matriks
% yang identik (isequal).
if nargin < 1 || isempty(params)
params = struct('r', 0.08, 'K', 100, 'P0', 1);
end
if nargin < 2 || isempty(tspan)
tspan = 0:1:120; % output tepat pada hari ke-0 sampai ke-120
end
opsi = odeset('RelTol', 1e-10, 'AbsTol', 1e-12);
[t, P] = ode45(@(t, P) modelPertumbuhan(t, P, params), tspan, ...
params.P0, opsi);
t = t(:);
P = P(:);
% P monoton naik sehingga interp1(P, t, ...) sah dipakai.
waktuSetengahK = interp1(P, t, params.K / 2);
hasil = struct('t', t, 'P', P, 'params', params, ...
'waktuSetengahK', waktuSetengahK);
endMari kita bedah baris-baris kode di atas:
- Baris 24-26 (
if nargin < 1 ... end): pola optional arguments klasik.jalankanSimulasi()mendapat parameter default;jalankanSimulasi(params)membawa parameternya sendiri — bentuk yang dipakaisweepParameternanti. - Baris 27-29 (
tspan = 0:1:120;): inilah keputusan determinisme pertama.tspandengan lebih dari dua elemen memaksa solver mengeluarkan nilai tepat pada tiap waktu yang diminta — di sini setiap hari ke-0 sampai ke-120, 121 titik — bukan pada waktu langkah internalnya yang bebas dipilih. - Baris 31 (
opsi = odeset('RelTol', 1e-10, 'AbsTol', 1e-12);): keputusan determinisme kedua. Toleransi defaultode45longgar; mengikatnya ketat membuat tiap langkah internal begitu akurat sehingga nilai yang diinterpolasi ke grid harian konvergen ke solusi eksak sampai jauh di bawah 1e-6 — di kedua mesin. - Baris 32-33 (
ode45(@(t, P) modelPertumbuhan(t, P, params), ...)): anonymous function membungkusmodelPertumbuhanmenjadi fungsi dua argumen(t, P)seperti yang diminta solver, sambil menangkapparamsdari ruang lingkup pemanggil. Kondisi awal diambil dariparams.P0. - Baris 34-35 (
t = t(:); P = P(:);): menegaskan kedua keluaran berbentuk vektor kolom — bentuk yang diandalkananalisisFasedan uji otomatis. - Baris 38 (
waktuSetengahK = interp1(P, t, params.K / 2);):Pmonoton naik, jadiinterp1boleh dipakai terbalik — mencari waktu pada biomassa tertentu. Hasilnya t50. - Baris 40-41 (
hasil = struct(...)): seluruh keluaran dirangkai satu struct — data, parameter, dan metrik — sehingga fungsi-fungsi berikutnya cukup menerima satu objek.
Menjalankannya dari Command Window pada folder src/:
>> hasil = jalankanSimulasi();
>> fprintf('P(120) = %.4f\n', hasil.P(end));
P(120) = 99.3340
>> fprintf('t50 = %.4f hari\n', hasil.waktuSetengahK);
t50 = 57.4390 hariValidasi terhadap Solusi Analitik #
Kekuatan proyek ini: kita punya jawaban eksak dari Bagian 1, jadi hasil numerik bisa diadili. Potongan berikut diambil langsung dari tests/uji_simulasi.m:
% Solusi analitik P(t) = K/(1 + (K/P0 - 1)*exp(-r*t)) sebagai pembanding.
Panalitik = hasil.params.K ./ ...
(1 + (hasil.params.K / hasil.params.P0 - 1) * ...
exp(-hasil.params.r * hasil.t));
galatMaks = max(abs(hasil.P - Panalitik));Mari kita bedah baris-baris kode di atas:
- Baris 2-4 (
Panalitik = ...): rumus analitik Bagian 1 dievaluasi pada seluruh grid waktuhasil.tsekaligus — operasi vektor elemen demi elemen, tanpa loop. - Baris 5 (
galatMaks = max(abs(hasil.P - Panalitik));): galat mutlak terbesar di seluruh 121 titik. Satu angka yang merangkum seberapa jujur solver pada model ini.
Uji otomatis kemudian menuntut galatMaks < 1e-6; kasusnya lolos dengan pesan LULUS: galat terhadap solusi analitik < 1e-6. Bagian ringkasan bernilai dari tests/uji_simulasi.m mencetak:
Output yang Dihasilkan:
Ringkasan simulasi default (r = 0.08/hari, K = 100, P0 = 1):
titik output : 121 (hari 0 sampai 120)
P(0) : 1.0000
P(120) : 99.3340
t50 : 57.4390 hari
laju maksimum : 1.9997 per hari
galat maksimum terhadap analitik: 0.000000Struktur Hasil dan Makna t50 #
Struct hasil membawa empat field yang menjadi bahasa bersama seluruh proyek:
| Field | Isi | Pemakai utama |
|---|---|---|
t | waktu (hari), 121x1 | semua fungsi |
P | biomassa (satuan), 121x1 | semua fungsi |
params | salinan parameter r, K, P0 | sweep, skenario |
waktuSetengahK | t50 hasil interpolasi (hari) | visualisasi, laporan |
Nilai t50 hasil interp1 adalah 57.4390 hari — berimpit dengan rumus analitik ln(99)/0.08 = 57.4390 sampai empat desimal. Selisih keduanya di bawah 0.001 hari, sisa galat interpolasi linear pada grid harian. Metrik kedua yang dicetak uji, laju maksimum = 1.9997 per hari, hanya 0.0003 di bawah nilai teoretis r*K/4 = 2 — dan itu puncak yang dicari diff(P)./diff(t) pada grid satu hari, bukan turunan eksak.
Sensitivitas Toleransi #
Seberapa penting odeset tadi? Tabel berikut menjalankan simulasi yang sama dengan tiga pengaturan toleransi, dicatat dari Octave:
| Pengaturan | Galat maks vs analitik | t50 | P(120) |
|---|---|---|---|
default (RelTol 1e-3) | 6.29e-03 | 57.4363 | 99.3340 |
RelTol 1e-6 | 1.70e-04 | 57.4390 | 99.3339 |
RelTol 1e-10 + AbsTol 1e-12 | 3.74e-08 | 57.4390 | 99.3340 |
| solusi analitik | — | 57.4390 | 99.3340 |
Dua baris pertama memakai toleransi longgar, dan dua angka pada baris itu (galat dan t50) adalah hasil urutan langkah adaptif solver: MATLAB bisa memilih langkah yang berbeda sehingga digit-digitnya ikut berbeda — orde besarannya tetap sama. Baris ketiga bebas dari masalah itu: toleransi yang diikat membuat hasil konvergen penuh ke analitik, sama di mesin mana pun.
Toleransi default sebenarnya cukup untuk menggambar kurva — galat 6e-3 tak terlihat mata. Tapi proyek ini mencetak angka dan menguji klaim seperti “laju maksimum di P = K/2”, jadi kita butuh angka yang stabil hingga empat desimal; baris ketiga itulah wilayah kerja simulasi ini.
Mesin dan solver akhirnya bekerja dan hasilnya lulus audit analitik. Angka yang bisa dipercaya sudah ada di tangan; Bagian 3 mengubahnya menjadi tiga gambar yang bisa dibaca dalam hitungan detik — kurva, laju, dan perbandingan skenario.