Hampir semua keluaran perhitungan numerik adalah floating point, dan seperti telah kita lihat pada Chapter Floating Point Comparison, membandingkannya dengan == hampir selalu keliru. Untuk pengujian otomatis, NumPy menyediakan np.testing.assert_allclose(actual, desired) — asersi yang membandingkan array dengan toleransi relatif dan absolut sekaligus, lalu melaporkan selisih terbesar bila melebihi batas.

Bab ini membedah dua parameter intinya, rtol dan atol, beserta rumus penggabungannya; menunjukkan cara memilih toleransi untuk data keuangan dan data ilmiah; serta perilaku bawaannya yang memperlakukan nan sebagai nilai yang sah. Nilai bawaannya berbeda dari np.isclose: di sini rtol=1e-07 dan atol=0.

Toleransi Bawaan rtol=1e-07, atol=0 #

Dengan pengaturan bawaan, assert_allclose mengizinkan selisih relatif sekitar tujuh digit signifikan. Cara tercepat merasakannya adalah menguji dua nilai yang selisihnya makin besar:

python
import numpy as np
from numpy.testing import assert_allclose

try:
    assert_allclose(np.array([100.000001]), np.array([100.0]))
    print("selisih 1e-6: lulus")
except AssertionError:
    print("selisih 1e-6: tidak lulus")
try:
    assert_allclose(np.array([100.0001]), np.array([100.0]))
    print("selisih 1e-4: lulus")
except AssertionError as e:
    print("selisih 1e-4: tidak lulus")
    print(e)

Output yang Dihasilkan:

text
selisih 1e-6: lulus
selisih 1e-4: tidak lulus

Not equal to tolerance rtol=1e-07, atol=0

Mismatched elements: 1 / 1 (100%)
Max absolute difference among violations: 0.0001
Max relative difference among violations: 1.e-06
 ACTUAL: array([100.0001])
 DESIRED: array([100.])

Mari kita bedah:

  1. Baris 5-8: selisih 1e-6 pada nilai 100.0 berarti galat relatif 1e-8 — di bawah rtol=1e-07 dikalikan 100.0 yang bernilai 1e-5 — sehingga pengujian lulus dalam keheningan.
  2. Baris 9-14: selisih 1e-4 melampaui batas dan memunculkan AssertionError. Pesannya langsung menampilkan pengaturan yang dipakai: Not equal to tolerance rtol=1e-07, atol=0.
  3. Laporan kesalahan: Max absolute difference dan Max relative difference menunjukkan seberapa jauh nilai meleset pada dua ukuran sekaligus — informasi yang memudahkan memutuskan toleransi mana yang harus dilonggarkan.

Rumus Penggabungan atol dan rtol #

Kedua toleransi tidak dipilih salah satu, melainkan dijumlahkan. Pengujian lulus ketika |actual - desired| <= atol + rtol * |desired|. Sifat ini paling terasa saat membandingkan nilai dengan nol, karena suku relatifnya otomatis nol:

python
import numpy as np
from numpy.testing import assert_allclose

try:
    assert_allclose(np.array([1e-7]), np.array([0.0]))
    print("default: lulus")
except AssertionError:
    print("default: tidak lulus")
try:
    assert_allclose(np.array([1e-7]), np.array([0.0]), atol=1e-6)
    print("atol=1e-6: lulus")
except AssertionError:
    print("atol=1e-6: tidak lulus")

selisih = abs(1e-7 - 0.0)
print("|a-b| =", selisih, "<= atol + rtol*|b| =", 0.0 + 1e-7 * 0.0)

Output yang Dihasilkan:

text
default: tidak lulus
atol=1e-6: lulus
|a-b| = 1e-07 <= atol + rtol*|b| = 0.0

Penjelasan tiap bagian:

  1. Baris 4-8: dengan atol=0, selisih sekecil apa pun terhadap nol ditolak — toleransi relatif rtol * |0.0| tidak pernah memberi longgar.
  2. Baris 9-13: menetapkan atol=1e-6 memberi jendela absolut ±1e-6 di sekitar nol, dan nilai 1e-7 kini diterima.
  3. Baris 15-16: baris terakhir menampilkan evaluasi rumus secara langsung: |a-b| = 1e-07 jelas lebih besar dari atol + rtol*|b| = 0.0, persis alasan kasus bawaan ditolak.

Memilih Toleransi: Keuangan vs Ilmiah #

Toleransi yang tepat ditentukan oleh makna data, bukan selera. Untuk harga dalam rupiah, longgar yang bermakna adalah nilai uang terkecil — misalnya lima sen — sehingga atol yang memimpin. Untuk hasil pengukuran ilmiah, kesalahan biasanya dinilai relatif terhadap besaran yang diukur:

python
import numpy as np
from numpy.testing import assert_allclose

harga = np.array([14999.99, 21999.95, 8000.00])
target = np.array([15000.00, 22000.00, 8000.00])
try:
    assert_allclose(harga, target, atol=0.05, rtol=0)
    print("atol=0.05: lulus")
except AssertionError:
    print("atol=0.05: tidak lulus")
try:
    assert_allclose(harga, target)
    print("default: lulus")
except AssertionError:
    print("default: tidak lulus")

ukur = np.array([9.81])
teori = np.array([9.80665])
try:
    assert_allclose(ukur, teori)
    print("rtol default: lulus")
except AssertionError:
    print("rtol default: tidak lulus")
try:
    assert_allclose(ukur, teori, rtol=1e-3)
    print("rtol=1e-3   : lulus")
except AssertionError:
    print("rtol=1e-3   : tidak lulus")

Output yang Dihasilkan:

text
atol=0.05: lulus
default: tidak lulus
rtol default: tidak lulus
rtol=1e-3   : lulus

Bedah kedua kasus:

  1. Kasus harga, baris 6-14: selisih terbesar hanya 0.05 rupiah, jadi atol=0.05 dengan rtol=0 meluluskannya. Pengaturan bawaan justru menolak — galat relatif 3.3e-6 terhadap 15000 melebihi rtol=1e-07 — bukti bahwa toleransi relatif saja kurang pas untuk data uang.
  2. Kasus pengukuran, baris 16-24: pengukuran 9.81 melawan nilai acuan 9.80665 meleset sekitar 0.03%; rtol=1e-3 menerima tingkat ketidakpastian itu, sedangkan rtol=1e-07 terlalu ketat untuk pengukuran eksperimen.

Kedua parameter menerima array juga scalar, sehingga toleransi per elemen bisa ditetapkan bila akurasi tiap kolom berbeda — misalnya atol kecil untuk kolom rasio dan besar untuk kolom jumlah uang.

Perbandingan yang Sadar NaN #

Seperti fungsi numpy.testing lainnya, assert_allclose memperlakukan nan pada posisi yang sama sebagai kesamaan. Perilaku ini diatur parameter equal_nan yang secara bawaan bernilai True:

python
import numpy as np
from numpy.testing import assert_allclose

a = np.array([np.nan, 1.0, 250.0000005])
b = np.array([np.nan, 1.0, 250.0])
try:
    assert_allclose(a, b, rtol=1e-7, atol=1e-6)
    print("default equal_nan: lulus")
except AssertionError:
    print("default equal_nan: tidak lulus")
try:
    assert_allclose(a, b, rtol=1e-7, atol=1e-6, equal_nan=False)
    print("equal_nan=False: lulus")
except AssertionError:
    print("equal_nan=False: tidak lulus")

Output yang Dihasilkan:

text
default equal_nan: lulus
equal_nan=False: tidak lulus

Mari kita baca:

  1. Baris 6-10: elemen nan di posisi pertama dianggap cocok, dan elemen ketiga lulus karena selisih 5e-7 berada di dalam jendela atol=1e-6 — pengujian lulus tanpa perlu filter terlebih dahulu.
  2. Baris 11-15: equal_nan=False mengembalikan semantik murni nan, dan pengujian tidak lulus pada elemen pertama. Opsi ini berguna ketika kehadiran nan justru harus terdeteksi sebagai keluaran yang salah.

Tabel ringkas parameter assert_allclose sebagai penutup:

ParameterBawaanPeran
actualwajibhasil perhitungan yang sedang diuji
desiredwajibnilai acuan; suku rtol dihitung terhadap `
rtol1e-07longgar relatif untuk nilai besar
atol0longgar absolut untuk wilayah dekat nol
equal_nanTruenan dianggap cocok dengan nan pada posisi sama

Dengan assert_allclose dalam genggaman, kita sudah punya alat utama pengujian numerik. Bab berikutnya menyusunnya menjadi rangkaian kasus uji yang utuh — lengkap dengan kasus batas seperti array kosong dan input yang membuat hasil meluap.