Satu simulasi menjawab satu skenario; pertanyaan nyata justru tentang banyak skenario: apa yang terjadi bila laju tumbuhnya berbeda, tanahnya lebih subur, atau semainya lebih besar? Bagian penutup ini menyapu grid 5 x 4 kombinasi parameter, membaca polanya lewat tabel dan heatmap, lalu merangkai seluruh proyek menjadi satu perintah jalankanStudi yang menghasilkan gambar, laporan teks, dan lulus 17 uji otomatis.

Yang kita bangun pada bagian ini:

  • sweepParameter.m — sweep grid r x K menjadi tiga matriks hasil,
  • pembacaan hasil — tabel, heatmap, dan siapa mengendalikan apa,
  • jalankanStudi.m — orkestrasi lengkap sampai laporan.txt,
  • ide pengembangan untuk melampaui model logistik dasar.

Sweep Grid Parameter #

Sweep adalah dua loop dan tiga matriks. Berikut inti src/sweepParameter.m:

matlab
if nargin < 1 || isempty(daftarR)
    daftarR = [0.04 0.06 0.08 0.10 0.12];
end
if nargin < 2 || isempty(daftarK)
    daftarK = [60 80 100 120];
end

nR = numel(daftarR);
nK = numel(daftarK);
Pakhir = zeros(nR, nK);
T50 = zeros(nR, nK);
LajuMaks = zeros(nR, nK);

params = struct('r', 0.08, 'K', 100, 'P0', 1);
for i = 1:nR
    for j = 1:nK
        params.r = daftarR(i);
        params.K = daftarK(j);
        hasil = jalankanSimulasi(params);
        Pakhir(i, j) = hasil.P(end);
        T50(i, j) = hasil.waktuSetengahK;
        laju = diff(hasil.P) ./ diff(hasil.t);
        LajuMaks(i, j) = max(laju);
    end
end

sweep = struct('r', daftarR(:), 'K', daftarK(:), 'Pakhir', Pakhir, ...
    'T50', T50, 'LajuMaks', LajuMaks);

Mari kita bedah baris-baris kode di atas:

  1. Baris 1-6 (grid default): lima nilai r dan empat nilai K dipilih melintasi rentang wajar Bagian 1. Keduanya bisa diganti lewat argumen — sweep ke stuktur yang sama dengan data lain.
  2. Baris 8-12 (zeros(nR, nK)): tiga matriks hasil dialokasikan penuh sebelum loop — matriks hasil sweep selalu persegi panjang nR x nK, bentuk yang nanti diuji otomatis.
  3. Baris 15-25 (loop bersarang): baris luar menjalankan r, kolom dalam menjalankan K — 20 simulasi penuh, masing-masing 121 titik.
  4. Baris 17-19 (params.r = ...; hasil = jalankanSimulasi(params);): satu struct params dipakai ulang; cukup dua field ditimpa per kombinasi karena P0 tetap. Determinisme Bagian 2 terbayar di sini: hasil tiap sel bisa dihitung sekali dan dipercaya selamanya.
  5. Baris 20-23 (tiga metrik per sel): P akhir, t50, dan laju maksimum — biomassa akhir, kecepatan, dan intensitas puncak.
  6. Baris 27-28 (sweep = struct(...)): hasil dirangkai dengan daftarR(:) dan daftarK(:) sebagai vektor kolom, sehingga orientasi grid selalu jelas bagi pemanggil.

Fungsi ini juga mencetak ringkasannya sendiri — header sejajar kolom karena tiap sel data diformat 7 karakter (' %6.2f') dan header K diformat sama lebar ('%7s'):

Output yang Dihasilkan:

text
== Sweep parameter: 5 nilai r x 4 nilai K (P0 = 1, 120 hari) ==
P akhir hari ke-120 (satuan biomassa):
           K=60   K=80  K=100  K=120
  r=0.04  40.39  48.48  55.10  60.63
  r=0.06  57.47  75.54  93.12 110.21
  r=0.08  59.76  79.57  99.33 119.04
  r=0.10  59.98  79.96  99.94 119.91
  r=0.12  60.00  80.00  99.99 119.99
Rentang t50: 33.98 sampai 119.48 hari
Rentang laju maks: 0.60 sampai 3.60 per hari

Membaca Hasil: Tabel dan Heatmap #

Tabel P akhir di atas dibaca dua arah. Turun satu kolom (K tetap, r naik): nilai menuju batas kolomnya — di K=100, dari 55.10 ke 99.99. Berjalan satu baris (r tetap, K naik): nilai naik hampir sebesar selisih K — di r=0.12, P akhir praktis menyamai K tiap kolom. Kesimpulan pertamanya: K mengendalikan langit-langit, r mengendalikan seberapa dekat langit-langit itu tercapai dalam 120 hari.

Matriks t50 menceritakan bagian waktu:

t50 (hari)K=60K=80K=100K=120
r=0.04101.94109.24114.88119.48
r=0.0667.9672.8276.5979.65
r=0.0850.9754.6257.4459.74
r=0.1040.7843.6945.9547.79
r=0.1233.9836.4138.2939.83

Barisnya menyusut tajam saat r naik; kolomnya merambat lambat saat K naik. Laju maksimum lebih patuh lagi: seluruh matriksnya persis r*K/4 (0.60 sampai 3.60) — rumus titik belok yang terbukti ulang di 20 sel.

Heatmap Bagian 3 memvisualkan matriks P akhir: kontur nyaris horizontal di wilayah r besar berarti P akhir peka terhadap K, kontur miring rapat di wilayah r kecil menandai daerah transisi tempat r dan K sama-sama berpengaruh. Membaca heatmap sweep berarti membaca arah kontur, bukan warnanya.

Sensitivitas dan Trade-off #

Rumus analitik menjelaskan polanya. Pertama, t50 = ln(K/P0 - 1)/r berbanding balik lurus dengan r: di kolom K=100, menggandakan r dari 0.04 ke 0.08 memangkas t50 dari 114.88 menjadi 57.44 hari. K hanya masuk lewat logaritma — menggandakan K dari 60 ke 120 hanya menambah t50 dari 50.97 ke 59.74 (17 persen). Kedua, P akhir dibatasi K dari atas dan jaraknya ke K menyusut lewat exp(-r*t): baris r=0.04 yang hanya mencapai 67 persen K di kolom 60 (50.5 persen di kolom 120) membuktikan 120 hari belum cukup untuk rem selonggar itu.

KeputusanAkibat pada 120 hariKeterangan
r besar (0.12), K kecil (60)jenuh cepat: P(120) = 60.00, t50 = 33.98panen awal, laju puncak 1.80/hari
r besar, K besar (120)P(120) = 119.99, t50 = 39.83hasil maksimal, tapi laju puncak 3.60/hari — kebutuhan hara/air puncak paling tinggi
r kecil (0.04), K besarP(120) = 60.63 = 50.5% Kkapasitas nganggur: investasi K tidak terbayar dalam musim
r sedang (0.08), K=100P(120) = 99.33, t50 = 57.44titik seimbang proyek ini

Trade-off praktisnya: laju tinggi membeli waktu dengan mahalnya puncak kebutuhan, sementara kapasitas besar hanya berarti bila r cukup mengejarnya sebelum musim habis. Simulasi memberi angka pada pergulatan itu; keputusan tetap milik petani.

Orkestrasi Lengkap: jalankanStudi.m #

jalankanStudi(dirOutput) menjalankan seluruh alur Bagian 1 sampai 3 — simulasi dasar, analisis fase, tiga gambar, sweep, heatmap — lalu menuang ringkasannya ke laporan.txt. Menulis laporan berarti fprintf dengan file ID:

matlab
% ---- 6. laporan.txt ----
namaLaporan = 'laporan.txt';
fid = fopen(fullfile(dirOutput, namaLaporan), 'w');
fprintf(fid, 'LAPORAN STUDI PERTUMBUHAN TANAMAN\n');
fprintf(fid, '================================\n');
fprintf(fid, 'Model     : dP/dt = r*P*(1 - P/K)\n');
fprintf(fid, 'Parameter : r = %.2f per hari, K = %g, P0 = %g\n', ...
    hasil.params.r, hasil.params.K, hasil.params.P0);

Mari kita bedah baris-baris kode di atas:

  1. Baris 3 (fid = fopen(..., 'w');): mode 'w' membuka berkas baru — laporan lama tertimpa, laporan selalu konsisten dengan kode terbaru.
  2. Baris 4-8 (fprintf(fid, ...)): pola fprintf ke file sama dengan ke layar; bedanya hanya argumen pertama fid. Sisa laporan (P(120), t50, durasi fase, ringkasan sweep) mengikuti pola yang sama hingga fclose(fid);.

Bagian penutup fungsi merangkai struct hasil — dengan satu jebakan yang sengaja diberi komentar:

matlab
% Perhatian: field berkas diisi lewat penugasan langsung, bukan
% struct(...), karena struct() memecah nilai cell array menjadi
% struct array 1x5 - jebakan klasik fungsi struct().
laporan = struct();
laporan.hasil = hasil;
laporan.fase = fase;
laporan.sweep = sweep;
laporan.berkas = [berkasGambar, {namaHeatmap, namaLaporan}];

Mari kita bedah baris-baris kode di atas:

  1. Baris 1-3 (komentar peringatan): struct('berkas', C) dengan C berupa cell array 1x5 tidak menghasilkan satu struct berfield cell — melainkan struct array 1x5. Bug ini ditemukan nyata saat uji pertama proyek (kasus “lima berkas tersimpan” gagal) dan diperbaiki dengan penugasan langsung.
  2. Baris 8 (laporan.berkas = [berkasGambar, ...]): penggabungan cell [cell, {a, b}] menghasilkan satu daftar datar — lima nama berkas yang bisa di-loop pemanggil.

Menjalankan studi penuh dari folder src/:

matlab
% driver: jalankan dari folder src/
jalankanStudi('hasil-studi');

Output yang Dihasilkan:

text
== Studi pertumbuhan tanaman (model logistik) ==
Folder output: hasil-studi
Simulasi dasar: r = 0.08/hari, K = 100, P0 = 1, 120 hari
P(120) = 99.3340 (99.33% dari K), t50 = 57.44 hari
Fase eksponensial: 46.85 hari (P < 30)
Fase transisi    : 21.19 hari (30 <= P <= 70)
Fase jenuh       : 51.97 hari (P > 70)
Laju maksimum    : 1.9997 per hari di t = 57.50 (sekitar K/2)
Gambar 1: kurva-pertumbuhan.png - kurva P, garis K dan K/2, t50 = 57.44 hari
Gambar 2: laju-pertumbuhan.png - laju dP/dt, puncak 1.9997 per hari di t = 57.50 hari
Gambar 3: skenario-r.png - perbandingan r = 0.05, 0.08, 0.12 per hari
== Sweep parameter: 5 nilai r x 4 nilai K (P0 = 1, 120 hari) ==
P akhir hari ke-120 (satuan biomassa):
           K=60   K=80  K=100  K=120
  r=0.04  40.39  48.48  55.10  60.63
  r=0.06  57.47  75.54  93.12 110.21
  r=0.08  59.76  79.57  99.33 119.04
  r=0.10  59.98  79.96  99.94 119.91
  r=0.12  60.00  80.00  99.99 119.99
Rentang t50: 33.98 sampai 119.48 hari
Rentang laju maks: 0.60 sampai 3.60 per hari
Heatmap: heatmap-sweep.png - P akhir pada grid r x K
Laporan : laporan.txt

Lima berkas tersimpan di hasil-studi/, dan laporan.txt berbunyi:

text
LAPORAN STUDI PERTUMBUHAN TANAMAN
================================
Model     : dP/dt = r*P*(1 - P/K)
Parameter : r = 0.08 per hari, K = 100, P0 = 1
Durasi    : 120 hari, output setiap hari

P(120)    : 99.3340 satuan (99.33% dari K)
t50       : 57.44 hari

Fase eksponensial : 46.85 hari
Fase transisi     : 21.19 hari
Fase jenuh        : 51.97 hari
Laju maksimum     : 1.9997 per hari di t = 57.50

Sweep parameter: 5 nilai r x 4 nilai K
P akhir minimum  : 40.39 (r kecil, K kecil)
P akhir maksimum : 119.99 (r besar, K besar)
t50 tercepat     : 33.98 hari
t50 terlama      : 119.48 hari

Uji Otomatis Menyeluruh #

tests/uji_simulasi.m membungkus klaim-klaim proyek dalam 17 kasus assert — validasi analitik, monotonitas, konsistensi t50, bentuk sweep, determinisme dua kali berjalan. Empat kasus sweep misalnya:

text
LULUS: bentuk matriks sweep: 5 baris r x 4 kolom K
LULUS: sweep: P akhir tidak melebihi K tiap kolom
LULUS: sweep: t50 menurun ketika r naik
LULUS: sweep: t50 menaik ketika K naik

Kelima belas kasus lain ikut lulus hingga baris penutupnya:

text
Ringkasan: 17 lulus, 0 gagal (total 17)

Keluaran uji ditulis ke folder sementara lalu dihapus, sehingga pengujian tidak meninggalkan residu di folder proyek.

Ide Pengembangan #

Model dan infrastrukturnya kini teruji; tiga arah pengembangan paling alami berikutnya:

  • Laju musiman — ganti r konstan dengan r(t) = r0*(1 + a*sin(2*pi*t/periode)). Fungsi modelPertumbuhan sudah menerima t sejak Bagian 2, jadi model non-otonom ini hanya mengubah isi fungsi, bukan kerangkanya.
  • Panen berkala — simulasi berhenti sejenak setiap P melewati ambang (misalnya 0.8K), biomassa dipangkas, lalu dilanjutkan dari nilai sisa. Sweep sweepParameter tinggal menerima ambang baru sebagai argumen.
  • Pertumbuhan stokastik — tambahkan derau pada laju dan jalankan banyak realisasi dengan rng(42). Determinisme berganti menjadi uji statistik (rata-rata dan sebaran lintas realisasi), dan klaim isequal dua kali berjalan tidak lagi berlaku — kesadaran penting bahwa tiap jenis model menuntut jenis pengujian sendiri.

Proyek lengkap: persamaan sampai laporan otomatis, tiga gambar, heatmap 5 x 4, dan 17 uji penjaganya. Pola perancangan-model, validasi-analitik, visualisasi, dan sweep ini bukan milik botani — ganti persamaannya, dan kerangkanya siap membawa simulasi Anda berikutnya.