Bab sebelumnya memakai np.random.default_rng() sebagai pintu masuk API modern tanpa menjelaskan apa yang sebenarnya dikembalikan fungsi tersebut. Bab ini membedahnya: sebuah objek Generator yang menyimpan keadaan sendiri dan ditopang BitGenerator PCG64. Memahami struktur ini membuat rng berhenti menjadi kotak hitam: kelas apa yang kita pegang, keadaan apa yang berubah setiap kali angka ditarik, dan bagaimana objek ini seharusnya diteruskan ke fungsi-fungsi program.

Membuat Generator dengan default_rng #

np.random.default_rng() dapat dipanggil tanpa argumen maupun dengan seed. Tanpa argumen, NumPy mengambil titik awal dari sumber entropi sistem operasi sehingga setiap eksekusi program menghasilkan urutan yang berbeda. Dengan seed, titik awal dikunci dan urutannya dapat direproduksi. Angka 42 pada contoh bukan angka khusus — bilangan bulat non-negatif apa pun sah dipakai.

python
import numpy as np

rng = np.random.default_rng(42)
print(type(rng))
print(rng)
print(type(rng.bit_generator))

Output yang Dihasilkan:

text
<class 'numpy.random._generator.Generator'>
Generator(PCG64)
<class 'numpy.random._pcg64.PCG64'>

Bedah baris demi baris:

  1. Baris 3 (rng = np.random.default_rng(42)): default_rng membuat objek Generator ber-seed 42.
  2. Baris 4 (print(type(rng))): kelasnya adalah Generator, yang tinggal di submodul internal numpy.random._generator. Awalan garis bawah pada nama modul menandai modul internal; kita tidak perlu mengimpornya langsung, cukup memakai objek hasil default_rng().
  3. Baris 5 (print(rng)): mencetak objeknya menampilkan Generator(PCG64) — repr yang memuat nama mesin pembangkit di baliknya.
  4. Baris 6 (print(type(rng.bit_generator))): atribut bit_generator mengekspos mesin tersebut: kelas PCG64 dari modul numpy.random._pcg64.

Keadaan yang Berubah Setiap Tarikan #

Generator adalah objek stateful: ia membawa keadaan internal yang berubah setiap kali angka ditarik. Keadaan BitGenerator dapat dilihat melalui atribut bit_generator.state, sebuah dict yang isinya bisa kita periksa sebelum dan sesudah tarikan:

python
import numpy as np

rng = np.random.default_rng(42)
print(rng.bit_generator.state)
print(rng.integers(0, 10, size=3))
print(rng.bit_generator.state)

Output yang Dihasilkan:

text
{'bit_generator': 'PCG64', 'state': {'state': 274674114334540486603088602300644985544, 'inc': 332724090758049132448979897138935081983}, 'has_uint32': 0, 'uinteger': 0}
[0 7 6]
{'bit_generator': 'PCG64', 'state': {'state': 180456145198944327624639367796045148994, 'inc': 332724090758049132448979897138935081983}, 'has_uint32': 1, 'uinteger': 1884968545}

Bedah baris demi baris:

  1. Baris 4: dict keadaan memuat nama mesin ('bit_generator': 'PCG64'), pasangan bilangan besar 'state' dan 'inc' yang menjadi inti algoritme PCG64, serta sisa cache bilangan 32-bit pada 'has_uint32' dan 'uinteger'.
  2. Baris 5: tarikan tiga angka memakai sekaligus memperbarui keadaan generator.
  3. Baris 7: nilai 'state' berubah dari 27467... menjadi 18045..., sedangkan 'inc' tetap; cache 32-bit kini berisi sisa nilai 1884968545 yang menunggu tarikan berikutnya. Inilah bukti nyata sifat stateful: posisi generator di dalam urutannya telah bergeser.
Kunci stateArti
'bit_generator'nama mesin pembangkit yang menopang Generator
'state'pasangan bilangan inti algoritme PCG64 ('state' dan 'inc')
'has_uint32', 'uinteger'sisa cache nilai 32-bit yang belum habis dipakai

Keadaan inilah yang membuat dua generator terpisah dengan seed sama dapat menghasilkan urutan identik, sekaligus membuat dua generator yang tak berkaitan tidak saling memengaruhi — dua sifat yang dibuktikan pada Chapter Reproducibility dan Random Seed.

Satu Generator per Bagian Program #

Praktik yang disarankan: buat satu Generator di awal bagian program — misalnya di atas sebuah modul atau di dalam fungsi utama — lalu teruskan sebagai parameter ke fungsi yang membutuhkan angka acak. Dengan cara ini aliran keacakan mudah dilacak, dan fungsi tetap mudah diuji karena kita bisa memberinya generator ber-seed khusus untuk pengujian.

python
import numpy as np

rng = np.random.default_rng(2024)

def undi_nomor_undian(rng, n):
    return rng.integers(1, 1000, size=n)

print(undi_nomor_undian(rng, 4))
print(undi_nomor_undian(rng, 4))

Output yang Dihasilkan:

text
[242 676  93 215]
[318 310 908 799]

Bedah baris demi baris:

  1. Baris 3: satu generator dibuat sekali di bagian atas program.
  2. Baris 5-6 (def undi_nomor_undian(...)): fungsi menerima rng sebagai parameter biasa; fungsi tidak membuat generator sendiri dan tidak menyentuh keadaan global apa pun.
  3. Baris 8-9: dua pemanggilan menghasilkan deret berbeda karena keduanya melanjutkan urutan generator yang sama.

Dengan memahami apa itu Generator, seluruh method pada section ini — integers, random, uniform, choice, dan seterusnya — dapat dibaca sebagai method milik satu objek stateful yang kita kendalikan penuh.