Semua tulisan
Algorithms7 menit baca

Simulasi Monte Carlo dan Crush-ku pada Hayley Williams

Aku belajar simulasi Monte Carlo pakai TypeScript dengan menghitung dunia paralel tempat aku dan Hayley Williams tinggal bareng.

Di artikel ini 7 bagian

Aku naksir Hayley Williams sejak 2012, semester pertama SMA. Lagu Paramore pertama yang aku dengar itu "crushcrushcrush", dan aku jadi fan karena musik pop punk mereka.

Aku pengen belajar simulasi Monte Carlo, jadi aku kasih dia pertanyaan yang aku suka: di berapa banyak dunia paralel aku dan Hayley tinggal bareng?

Hayley Williams

Hayley Williams lahir tanggal 27 Desember 1988 di Meridian, Mississippi. Dia pindah ke Franklin, Tennessee tahun 2002, waktu umurnya 13 tahun, dan Paramore terbentuk di sana tahun 2004. "crushcrushcrush" ada di Riot!, album kedua mereka, yang rilis tanggal 12 Juni 2007. Dia nulis lagu itu bareng Josh Farro, dan lagunya mentok di posisi 54 di Billboard Hot 100. Dia masih nyanyiin lagu itu, dan bulan Februari 2025 dia nyanyiin bareng Finneas di Ryman Auditorium, Nashville.

Dia dan Taylor York menang Best Rock Song di Grammy 2015 lewat "Ain't It Fun". Tahun 2024, Paramore menang Best Rock Album dan Best Alternative Music Performance lewat This Is Why. Tahun 2016 dia bikin brand pewarna rambut Good Dye Young bareng hairstylist lamanya, Brian O'Connor, dan produk pertama mereka itu Riot, warna oranye khas dia.

Album solo ketiganya, Ego Death at a Bachelorette Party, rilis tahun 2025 lewat label miliknya sendiri, Post Atlantic. Album itu dapat empat nominasi Grammy tahun 2026. Paramore lagi rehat sejak September 2025. Kata dia sendiri, "We always take huge breaks."

Dia tinggal di Nashville, sekitar 16.400 km dari Jakarta. Paramore udah tiga kali main di Indonesia: Bali tanggal 17 Agustus 2011 dan Jakarta tanggal 19 Agustus 2011, sebelum aku jadi fan, dan ICE BSD di Tangerang tanggal 25 Agustus 2018.

Awal mula simulasi Monte Carlo

Tahun 1946, matematikawan Stanislaw Ulam lagi pemulihan dari sakit dan main solitaire. Dia bertanya "what are the chances that a Canfield solitaire laid out with 52 cards will come out successfully?" Dia ngabisin banyak waktu buat hitungan kombinatorik murni. Terus dia kepikiran apakah cara yang lebih praktis itu "to lay it out say one hundred times and simply observe and count the number of successful plays."

Ide itu adalah simulasi Monte Carlo: kamu memperkirakan sebuah peluang atau rata-rata dengan menjalankan banyak percobaan acak, lalu menghitung hasilnya.

Ulam cerita soal ide itu ke John von Neumann di akhir 1946. Tanggal 11 Maret 1947, von Neumann ngirim surat ke Robert Richtmyer di Los Alamos, isinya pendekatan statistik untuk difusi neutron di material fisil. Di artikel yang sama, Nicholas Metropolis nulis bahwa dia yang mengusulkan nama itu, karena "Stan had an uncle who would borrow money from relatives because he 'just had to go to Monte Carlo.'" Perhitungan Monte Carlo pertama di komputer jalan di ENIAC bulan April 1948, dan Metropolis dan Ulam menerbitkan The Monte Carlo Method tahun 1949.

Los Alamos masih mengelola MCNP, kode Monte Carlo untuk neutron, foton, dan partikel lain. Renderer berbasis fisika pakai Monte Carlo karena persamaan transport cahaya umumnya nggak punya solusi analitik. AlphaGo menggabungkan Monte Carlo tree search dengan neural network dan mengalahkan juara Go Eropa 5 lawan 0.

Memperkirakan pi dengan titik acak

Gimana caranya angka acak bisa ngasih kamu pi?

Gambar persegi dengan sisi sepanjang 1. Di dalamnya, gambar seperempat lingkaran dengan jari-jari 1, berpusat di pojok kiri bawah. Luas perseginya 1, dan luas seperempat lingkarannya pi / 4. Kalau kamu jatuhin satu titik di tempat acak di dalam persegi, titik itu masuk ke seperempat lingkaran dengan peluang pi / 4.

Titik (x, y) ada di dalam seperempat lingkaran kalau x2 + y2 ≤ 1. Jadi kamu jatuhin banyak titik, hitung yang masuk, lalu kalikan pecahannya dengan 4.

Math.random() di JavaScript nggak bisa diatur seed-nya, jadi tiap kali dijalankan angkanya beda. Seed adalah nilai awal dari sebuah random number generator, dan seed yang sama selalu menghasilkan urutan yang sama. Aku pengen output kamu sama dengan punyaku, jadi kodenya pakai sfc32, generator kecil buatan Chris Doty-Humphrey yang berstatus public domain. Versi JavaScript-nya diambil dari koleksi PRNG milik bryc.

Generator yang state-nya cuma 32 bit, kayak mulberry32, terlalu kecil buat ini. Pedoman Art Owen adalah pakai nggak lebih dari kira-kira akar kuadrat dari periode sebuah generator. Untuk periode 232, itu sekitar 65.536 angka, sedangkan satu run 100 juta dunia butuh lebih dari satu miliar angka. sfc32 menyimpan state 128 bit.

typescript
// random.ts
export function sfc32(seed: number) {
  let a = 0;
  let b = seed | 0;
  let c = 0;
  let d = 1;
  const next = (): number => {
    const t = (((a + b) | 0) + d) | 0;
    d = (d + 1) | 0;
    a = b ^ (b >>> 9);
    b = (c + (c << 3)) | 0;
    c = (c << 21) | (c >>> 11);
    c = (c + t) | 0;
    return (t >>> 0) / 4294967296;
  };
  // Mix the seed before the first use.
  for (let i = 0; i < 12; i++) next();
  return next;
}

next() mengembalikan angka dari 0 sampai kurang dari 1. Kode seeding milik PractRand sendiri juga menjalankan generator 12 kali sebelum dipakai.

typescript
// pi.ts
import { sfc32 } from "./random.ts";

const random = sfc32(2012);

function estimatePi(n: number): number {
  let inside = 0;
  for (let i = 0; i < n; i++) {
    const x = random();
    const y = random();
    if (x * x + y * y <= 1) inside++;
  }
  return (4 * inside) / n;
}

const sizes = [100, 10_000, 1_000_000];
for (const n of sizes) {
  const pi = estimatePi(n);
  const error = Math.abs(pi - Math.PI);
  console.log(n, pi, error.toFixed(5));
}
text
$ node pi.ts
100 3.04 0.10159
10000 3.1568 0.01521
1000000 3.142596 0.00100

Aku jalankan file-file ini pakai Node 24.19, yang bisa langsung menjalankan file TypeScript tanpa build. Seed 2012 itu tahun aku dengar "crushcrushcrush".

Kenapa titik yang lebih banyak ngasih perkiraan yang lebih baik

Hukum bilangan besar bilang bahwa rata-rata dari banyak percobaan acak yang saling independen makin dekat ke nilai sebenarnya seiring jumlah percobaannya bertambah. Tiap titik di estimatePi() adalah satu percobaan yang nilainya 4 atau 0, dan perkiraannya adalah rata-rata nilai itu.

Error tipikal dari sebuah perkiraan Monte Carlo adalah standar deviasi satu percobaan dibagi akar kuadrat dari jumlah percobaan, jadi error-nya turun pelan. Satu digit desimal yang benar tambahan butuh percobaan 100 kali lebih banyak. Untuk perkiraan pi, standar deviasi satu percobaan adalah 4 kali akar kuadrat dari (pi / 4)(1 − pi / 4), sekitar 1,64.

TitikPerkiraanError sebenarnyaError tipikal
1003,040,101590,16422
10.0003,15680,015210,01642
1.000.0003,1425960,001000,00164

Tiap baris punya titik 100 kali lebih banyak dari baris di atasnya, dan error tipikalnya jadi 10 kali lebih kecil. Error sebenarnya itu acak, jadi bisa di atas atau di bawah error tipikal.

Mensimulasikan dunia-dunianya

Tiap dunia mulai di 2012, waktu aku dengar "crushcrushcrush". Tiap tahun sampai 2026, dunia itu bisa maju ke langkah berikutnya dalam sebuah rantai. Peluangnya tebakanku sendiri.

LangkahPeluang tiap tahunAlasan
Aku nonton Paramore live1 dari 20Mereka main di Indonesia sekali dari 2012 sampai 2026
Aku ketemu dia setelah show1 dari 1.000Show di Ancol tahun 2011 didatangi puluhan ribu orang
Dia ingat aku1 dari 10Tebakan
Kita jadi teman1 dari 5Tebakan
Aku pindah ke Nashville1 dari 50Jaraknya 16.400 km dari Jakarta
Kita tinggal bareng1 dari 100Tebakan

Sebuah dunia maju paling banyak satu langkah per tahun, dan langkah yang gagal di satu tahun bisa berhasil di tahun berikutnya. Jadi jawabannya tergantung kapan tiap langkah terjadi, dan mengalikan enam peluang itu nggak menjawab pertanyaannya.

typescript
// worlds.ts
import { sfc32 } from "./random.ts";

const random = sfc32(2012);

const steps: [string, number][] = [
  ["see Paramore live", 1 / 20],
  ["meet her after a show", 1 / 1000],
  ["she remembers me", 1 / 10],
  ["we become friends", 1 / 5],
  ["I move to Nashville", 1 / 50],
  ["we live together", 1 / 100],
];

const YEARS = 15; // 2012 to 2026

function liveOneWorld(): number {
  let step = 0;
  for (let y = 0; y < YEARS; y++) {
    if (step === steps.length) break;
    const [, odds] = steps[step];
    if (random() < odds) step++;
  }
  return step;
}

function simulate(n: number): number[] {
  const reached = steps.map(() => 0);
  for (let i = 0; i < n; i++) {
    const last = liveOneWorld();
    for (let s = 0; s < last; s++) {
      reached[s]++;
    }
  }
  return reached;
}

const n = Number(process.argv[2]);
const reached = simulate(n);
steps.forEach(([name], i) => {
  console.log(name, reached[i]);
});

liveOneWorld() mengembalikan berapa langkah yang dicapai satu dunia. simulate() menghitung, untuk tiap langkah, berapa dunia yang sampai sejauh itu.

text
$ node worlds.ts 1000000
see Paramore live 537100
meet her after a show 4209
she remembers me 1483
we become friends 646
I move to Nashville 38
we live together 1

Dari sejuta dunia, ada 537.100 dunia tempat aku nonton show Paramore. Ada satu dunia tempat aku dan Hayley tinggal bareng.

Kejadian langka butuh lebih banyak dunia

Apakah satu dunia dari sejuta berarti peluangnya 1 dari sejuta?

Belum tentu. Kalau kejadiannya langka, error-nya tergantung jumlah hit yang kamu hitung, bukan jumlah dunia yang kamu jalankan. Error relatifnya sekitar 1 dibagi akar kuadrat dari jumlah hit yang diharapkan. Dengan sekitar satu hit, error relatifnya sekitar 100 persen, jadi satu hit nggak bisa membedakan 1 dari 500.000 dengan 1 dari 5 juta.

Supaya error relatifnya turun ke sekitar 10 persen, aku butuh sekitar 100 hit. Untuk kejadian yang peluangnya sekitar 1 dari sejuta, itu artinya sekitar 100 juta dunia.

text
$ node worlds.ts 100000000
see Paramore live 53674077
meet her after a show 424208
she remembers me 146467
we become friends 64906
I move to Nashville 3212
we live together 66

Run itu makan waktu 14 detik di laptop Apple M5-ku. 66 dunia dari 100 juta itu sekitar 1 dari 1,5 juta, dengan error relatif sekitar 12 persen.

Rantai ini cukup kecil untuk dihitung secara eksak, tahun demi tahun, jadi aku cek simulasinya dengan hitungan itu. Jawaban eksaknya 1 dari 1.617.113. Untuk 100 juta dunia, itu memprediksi sekitar 62 hit, dan 66 masih di dalam error-nya.

Apa arti angkanya

Angka 1 dari 1,6 juta itu berasal dari peluang yang aku karang sendiri. Kalau aku ubah "Aku pindah ke Nashville" dari 1 dari 50 per tahun jadi 1 dari 5, jawaban eksaknya jadi sekitar 1 dari 205.000, hampir 8 kali lebih tinggi. Simulasinya menghitung dengan teliti, tapi nggak bisa bikin tebakanku jadi lebih akurat.

Di gambaran many worlds, bobot sebuah cabang berasal dari fisika kuantum, bukan dari hitungan dunia yang dibayangkan, jadi hitunganku nggak bilang apa-apa soal cabang yang beneran ada.

Di dunia ini, aku masih belum pernah nonton Paramore live, dan band-nya lagi rehat. Aku berharap langkah pertama dari rantaiku terjadi di dunia yang ini.

Topik

Makasih udah baca

Gimana menurutmu?

Kasih reaksi atau komentar di bawah.

Memuat reaksi…

Bagikan tulisan ini

Memuat komentar...

Tulisan terkait

Semua tulisan