knitr::opts_chunk$set(
echo = TRUE, message = FALSE, warning = FALSE,
fig.align = "center", fig.width = 8, fig.height = 5, comment = "#>"
)
options(scipen = 999)
paket <- c("deSolve", "lpSolve", "GA")
belum_terpasang <- paket[
!vapply(paket, requireNamespace, logical(1), quietly = TRUE)
]
if (length(belum_terpasang) > 0) {
install.packages(belum_terpasang, repos = "https://cloud.r-project.org")
}
library(deSolve)
## Warning: package 'deSolve' was built under R version 4.5.3
library(lpSolve)
## Warning: package 'lpSolve' was built under R version 4.5.2
library(GA)
## Warning: package 'GA' was built under R version 4.5.3
## Loading required package: foreach
## Loading required package: iterators
## Package 'GA' version 3.2.5
## Type 'citation("GA")' for citing this R package in publications.
##
## Attaching package: 'GA'
## The following object is masked from 'package:utils':
##
## de
Tentukan akar \(x^3-2x-5=0\) dengan
bisection, Newton-Raphson, dan secant pada toleransi \(10^{-10}\), lalu verifikasi menggunakan
uniroot().
f_akar <- function(x) x^3 - 2 * x - 5
df_akar <- function(x) 3 * x^2 - 2
bisection <- function(fun, a, b, tol = 1e-10, maxit = 1000) {
fa <- fun(a); fb <- fun(b)
if (abs(fa) <= tol) return(list(root = a, iter = 0L, converged = TRUE))
if (abs(fb) <= tol) return(list(root = b, iter = 0L, converged = TRUE))
if (fa * fb > 0) stop("f(a) dan f(b) harus memiliki tanda berbeda.")
for (i in seq_len(maxit)) {
tengah <- (a + b) / 2
ft <- fun(tengah)
if (abs(ft) <= tol || (b - a) / 2 <= tol) {
return(list(root = tengah, iter = i, converged = TRUE))
}
if (fa * ft < 0) {
b <- tengah; fb <- ft
} else {
a <- tengah; fa <- ft
}
}
list(root = (a + b) / 2, iter = maxit, converged = FALSE)
}
newton_raphson <- function(fun, dfun, x0, tol = 1e-10, maxit = 1000) {
x <- x0
for (i in seq_len(maxit)) {
fx <- fun(x); dfx <- dfun(x)
if (!is.finite(dfx) || abs(dfx) < .Machine$double.eps) {
stop("Turunan terlalu dekat dengan nol.")
}
x_baru <- x - fx / dfx
if (abs(x_baru - x) <= tol || abs(fun(x_baru)) <= tol) {
return(list(root = x_baru, iter = i, converged = TRUE))
}
x <- x_baru
}
list(root = x, iter = maxit, converged = FALSE)
}
secant <- function(fun, x0, x1, tol = 1e-10, maxit = 1000) {
f0 <- fun(x0); f1 <- fun(x1)
for (i in seq_len(maxit)) {
penyebut <- f1 - f0
if (!is.finite(penyebut) || abs(penyebut) < .Machine$double.eps) {
stop("Penyebut metode secant terlalu dekat dengan nol.")
}
x2 <- x1 - f1 * (x1 - x0) / penyebut
if (abs(x2 - x1) <= tol || abs(fun(x2)) <= tol) {
return(list(root = x2, iter = i, converged = TRUE))
}
x0 <- x1; f0 <- f1
x1 <- x2; f1 <- fun(x1)
}
list(root = x1, iter = maxit, converged = FALSE)
}
tol_akar <- 1e-10
r_bis <- bisection(f_akar, 2, 3, tol_akar)
r_new <- newton_raphson(f_akar, df_akar, 2.5, tol_akar)
r_sec <- secant(f_akar, 2, 3, tol_akar)
r_uni <- uniroot(f_akar, interval = c(2, 3), tol = tol_akar)
akar <- c(r_bis$root, r_new$root, r_sec$root, r_uni$root)
tabel_akar <- data.frame(
Metode = c("Bisection", "Newton-Raphson", "Secant", "uniroot (verifikasi)"),
Akar = akar,
Iterasi = c(r_bis$iter, r_new$iter, r_sec$iter, r_uni$iter),
Residual = abs(f_akar(akar)),
Konvergen = c(r_bis$converged, r_new$converged, r_sec$converged, TRUE)
)
knitr::kable(tabel_akar, digits = 12, caption = "Perbandingan metode pencarian akar")
| Metode | Akar | Iterasi | Residual | Konvergen |
|---|---|---|---|---|
| Bisection | 2.094551 | 32 | 0.000000000033 | TRUE |
| Newton-Raphson | 2.094551 | 4 | 0.000000000088 | TRUE |
| Secant | 2.094551 | 6 | 0.000000000000 | TRUE |
| uniroot (verifikasi) | 2.094551 | 7 | 0.000000000069 | TRUE |
Akar persamaan berada di sekitar 2,094551. Ketiga metode seharusnya
menghasilkan hampiran yang sangat berdekatan. Jumlah iterasi dapat
berbeda karena cara memperbarui hampiran berbeda; uniroot()
dipakai sebagai verifikasi.
Selesaikan SPL tiga variabel dengan gauss(),
jacobi(), dan solve(). Setelah itu, perkecil
salah satu elemen diagonal dan amati perilaku Jacobi.
gauss <- function(A, b, tol = 1e-12) {
A <- as.matrix(A); b <- as.numeric(b); n <- nrow(A)
if (n != ncol(A) || length(b) != n) stop("Dimensi A dan b tidak sesuai.")
Ab <- cbind(A, b)
if (n > 1) {
for (k in seq_len(n - 1)) {
p <- which.max(abs(Ab[k:n, k])) + k - 1
if (abs(Ab[p, k]) < tol) stop("Matriks singular atau hampir singular.")
if (p != k) Ab[c(k, p), ] <- Ab[c(p, k), ]
for (i in seq.int(k + 1, n)) {
pengali <- Ab[i, k] / Ab[k, k]
Ab[i, k:(n + 1)] <- Ab[i, k:(n + 1)] - pengali * Ab[k, k:(n + 1)]
}
}
}
if (abs(Ab[n, n]) < tol) stop("Matriks singular atau hampir singular.")
x <- numeric(n)
for (i in seq.int(n, 1)) {
jumlah <- if (i < n) sum(Ab[i, (i + 1):n] * x[(i + 1):n]) else 0
x[i] <- (Ab[i, n + 1] - jumlah) / Ab[i, i]
}
x
}
jacobi <- function(A, b, x0 = rep(0, length(b)),
tol = 1e-10, maxit = 1000, divergence_limit = 1e10) {
A <- as.matrix(A); b <- as.numeric(b); D <- diag(A)
if (any(abs(D) < .Machine$double.eps)) {
return(list(x = rep(NA_real_, length(b)), iter = 0L,
converged = FALSE, reason = "Diagonal nol atau terlalu kecil."))
}
R <- A - diag(D); x <- as.numeric(x0)
for (k in seq_len(maxit)) {
x_baru <- as.numeric((b - R %*% x) / D)
if (any(!is.finite(x_baru)) || max(abs(x_baru)) > divergence_limit) {
return(list(x = x_baru, iter = k, converged = FALSE,
reason = "Iterasi divergen atau nilainya membesar."))
}
if (max(abs(x_baru - x)) < tol) {
return(list(x = x_baru, iter = k, converged = TRUE, reason = "Konvergen."))
}
x <- x_baru
}
list(x = x, iter = maxit, converged = FALSE,
reason = "Belum konvergen sampai iterasi maksimum.")
}
A_spl <- matrix(c(10, -1, 2, -1, 11, -1, 2, -1, 10), nrow = 3, byrow = TRUE)
x_diketahui <- c(1, 2, -1)
b_spl <- as.vector(A_spl %*% x_diketahui)
x_gauss <- gauss(A_spl, b_spl)
r_jacobi <- jacobi(A_spl, b_spl)
x_solve <- solve(A_spl, b_spl)
knitr::kable(
data.frame(Variabel = c("x1", "x2", "x3"),
Gauss = x_gauss, Jacobi = r_jacobi$x, solve = x_solve),
digits = 10, caption = "Perbandingan solusi SPL"
)
| Variabel | Gauss | Jacobi | solve |
|---|---|---|---|
| x1 | 1 | 1 | 1 |
| x2 | 2 | 2 | 2 |
| x3 | -1 | -1 | -1 |
knitr::kable(
data.frame(
Metode = c("Gauss", "Jacobi", "solve"),
Norma_Residu = c(
sqrt(sum((A_spl %*% x_gauss - b_spl)^2)),
sqrt(sum((A_spl %*% r_jacobi$x - b_spl)^2)),
sqrt(sum((A_spl %*% x_solve - b_spl)^2))
)
),
digits = 12, caption = "Norma residu setiap metode"
)
| Metode | Norma_Residu |
|---|---|
| Gauss | 0.000000000000 |
| Jacobi | 0.000000000163 |
| solve | 0.000000000000 |
A_kecil <- A_spl
A_kecil[1, 1] <- 0.1
knitr::kable(
data.frame(
Baris = 1:3,
Nilai_Diagonal = abs(diag(A_kecil)),
Jumlah_Nondiagonal = rowSums(abs(A_kecil)) - abs(diag(A_kecil))
),
digits = 4, caption = "Dominasi diagonal setelah elemen diagonal diperkecil"
)
| Baris | Nilai_Diagonal | Jumlah_Nondiagonal |
|---|---|---|
| 1 | 0.1 | 3 |
| 2 | 11.0 | 2 |
| 3 | 10.0 | 3 |
r_jacobi_kecil <- jacobi(
A_kecil, b_spl, tol = 1e-10, maxit = 100, divergence_limit = 1e8
)
knitr::kable(
data.frame(Iterasi = r_jacobi_kecil$iter,
Konvergen = r_jacobi_kecil$converged,
Keterangan = r_jacobi_kecil$reason),
caption = "Perilaku Jacobi setelah perubahan diagonal"
)
| Iterasi | Konvergen | Keterangan |
|---|---|---|
| 19 | FALSE | Iterasi divergen atau nilainya membesar. |
Dominasi diagonal pada sistem awal mendukung konvergensi Jacobi. Ketika elemen diagonal pertama diperkecil, syarat tersebut tidak lagi terpenuhi pada baris pertama, sehingga Jacobi dapat menjadi tidak stabil atau divergen.
Interpolasikan \(f(x)=1/(1+25x^2)\) pada \([-1,1]\) menggunakan 11 titik berjarak sama. Bandingkan interpolasi Lagrange dan spline kubik.
f_runge <- function(x) 1 / (1 + 25 * x^2)
lagrange_eval <- function(x, y, xout) {
n <- length(x)
w <- vapply(seq_len(n), function(i) 1 / prod(x[i] - x[-i]), numeric(1))
vapply(xout, function(t) {
titik_sama <- which(abs(t - x) < 1e-12)
if (length(titik_sama) > 0) return(y[titik_sama[1]])
q <- w / (t - x)
sum(q * y) / sum(q)
}, numeric(1))
}
x_titik <- seq(-1, 1, length.out = 11)
y_titik <- f_runge(x_titik)
x_plot <- seq(-1, 1, length.out = 500)
y_asli <- f_runge(x_plot)
y_lagrange <- lagrange_eval(x_titik, y_titik, x_plot)
y_spline <- splinefun(x_titik, y_titik, method = "natural")(x_plot)
knitr::kable(
data.frame(
Metode = c("Lagrange", "Spline kubik natural"),
Galat_Maksimum_Absolut = c(
max(abs(y_lagrange - y_asli)),
max(abs(y_spline - y_asli))
)
),
digits = 6, caption = "Galat maksimum terhadap fungsi asli"
)
| Metode | Galat_Maksimum_Absolut |
|---|---|
| Lagrange | 1.915621 |
| Spline kubik natural | 0.021974 |
plot(x_plot, y_asli, type = "l", lwd = 2, lty = 1,
xlab = "x", ylab = "f(x)",
main = "Interpolasi Lagrange dan Spline Kubik",
ylim = range(c(y_asli, y_lagrange, y_spline)))
lines(x_plot, y_lagrange, lwd = 2, lty = 2)
lines(x_plot, y_spline, lwd = 2, lty = 3)
points(x_titik, y_titik, pch = 19)
legend("top", legend = c("Fungsi asli", "Lagrange derajat 10",
"Spline kubik", "Titik data"),
lty = c(1, 2, 3, NA), pch = c(NA, NA, NA, 19),
lwd = c(2, 2, 2, NA), bty = "n")
Polinom Lagrange berderajat tinggi dapat mengalami osilasi dekat batas interval pada fungsi Runge. Spline kubik menggunakan polinom berderajat rendah pada setiap subinterval dan biasanya menghasilkan kurva yang lebih stabil. Tabel galat maksimum memberi ukuran kuantitatif untuk membandingkan keduanya.
Hitung \(\int_{0.001}^{2}\sin(x)/x\,dx\) dengan
aturan trapesium, Simpson 1/3, dan integrate(). Gunakan
hasil integrate() sebagai nilai acuan dan plot galat
terhadap \(n\) pada skala
logaritmik.
f_integral <- function(x) sin(x) / x
trapesium <- function(fun, a, b, n) {
x <- seq(a, b, length.out = n + 1)
h <- (b - a) / n
h * (sum(fun(x)) - (fun(a) + fun(b)) / 2)
}
simpson <- function(fun, a, b, n) {
if (n %% 2 != 0) stop("Simpson 1/3 membutuhkan n genap.")
x <- seq(a, b, length.out = n + 1)
h <- (b - a) / n
h / 3 * (
fun(x[1]) + fun(x[n + 1]) +
4 * sum(fun(x[seq(2, n, by = 2)])) +
2 * sum(fun(x[seq(3, n - 1, by = 2)]))
)
}
batas_bawah <- 0.001
batas_atas <- 2
hasil_integrate <- integrate(
f_integral, lower = batas_bawah, upper = batas_atas,
rel.tol = 1e-12, abs.tol = 1e-14
)
nilai_acuan <- hasil_integrate$value
n_laporan <- 1000
hasil_trapesium <- trapesium(f_integral, batas_bawah, batas_atas, n_laporan)
hasil_simpson <- simpson(f_integral, batas_bawah, batas_atas, n_laporan)
knitr::kable(
data.frame(
Metode = c(paste0("Trapesium, n = ", n_laporan),
paste0("Simpson 1/3, n = ", n_laporan), "integrate()"),
Nilai_Integral = c(hasil_trapesium, hasil_simpson, nilai_acuan),
Galat_Absolut_vs_integrate = c(
abs(hasil_trapesium - nilai_acuan),
abs(hasil_simpson - nilai_acuan),
NA_real_
)
),
digits = 14, caption = "Perbandingan hasil integrasi"
)
| Metode | Nilai_Integral | Galat_Absolut_vs_integrate |
|---|---|---|
| Trapesium, n = 1000 | 1.604413 | 0.00000014487650 |
| Simpson 1/3, n = 1000 | 1.604413 | 0.00000000000002 |
| integrate() | 1.604413 | NA |
n_values <- 2^(2:10)
nilai_trapesium <- sapply(
n_values, function(n) trapesium(f_integral, batas_bawah, batas_atas, n)
)
nilai_simpson <- sapply(
n_values, function(n) simpson(f_integral, batas_bawah, batas_atas, n)
)
galat_trapesium <- abs(nilai_trapesium - nilai_acuan)
galat_simpson <- abs(nilai_simpson - nilai_acuan)
plot(n_values, galat_trapesium, type = "b", log = "xy", lwd = 2, pch = 16,
xlab = "Jumlah subinterval (n)",
ylab = "Galat absolut terhadap integrate()",
main = "Galat Integrasi pada Skala Log-Log")
lines(n_values, galat_simpson, type = "b", lwd = 2, pch = 17, lty = 2)
legend("bottomleft", legend = c("Trapesium", "Simpson 1/3"),
lty = c(1, 2), pch = c(16, 17), lwd = 2, bty = "n")
knitr::kable(
data.frame(n = n_values, Galat_Trapesium = galat_trapesium,
Galat_Simpson = galat_simpson),
digits = 12, caption = "Galat untuk berbagai nilai n"
)
| n | Galat_Trapesium | Galat_Simpson |
|---|---|---|
| 4 | 0.009075373797 | 0.000083720013 |
| 8 | 0.002264978410 | 0.000005153385 |
| 16 | 0.000566003948 | 0.000000320873 |
| 32 | 0.000141485960 | 0.000000020036 |
| 64 | 0.000035370551 | 0.000000001252 |
| 128 | 0.000008842579 | 0.000000000078 |
| 256 | 0.000002210641 | 0.000000000005 |
| 512 | 0.000000552660 | 0.000000000000 |
| 1024 | 0.000000138165 | 0.000000000000 |
Aturan trapesium memiliki galat teoritis \(O(h^2)\), sedangkan Simpson 1/3 memiliki
galat \(O(h^4)\) untuk fungsi yang
cukup mulus. Karena itu, galat Simpson umumnya turun lebih cepat saat
jumlah subinterval bertambah. integrate() menggunakan
integrasi adaptif dan tidak memerlukan parameter \(n\), sehingga dipakai sebagai nilai
acuan.
Selesaikan \(y'=-2y+t\), \(y(0)=1\), pada \(t\in[0,2]\) dengan Euler dan RK4 untuk
\(h=0.2\) dan \(h=0.05\). Bandingkan dengan
ode() dari paket deSolve.
f_pdb <- function(t, y) -2 * y + t
solusi_eksak <- function(t) 0.5 * t - 0.25 + 1.25 * exp(-2 * t)
euler_pdb <- function(fun, t0, y0, h, t_akhir) {
n <- round((t_akhir - t0) / h)
waktu <- seq(t0, by = h, length.out = n + 1)
y <- numeric(n + 1)
y[1] <- y0
for (i in seq_len(n)) y[i + 1] <- y[i] + h * fun(waktu[i], y[i])
data.frame(waktu = waktu, y = y)
}
rk4_pdb <- function(fun, t0, y0, h, t_akhir) {
n <- round((t_akhir - t0) / h)
waktu <- seq(t0, by = h, length.out = n + 1)
y <- numeric(n + 1)
y[1] <- y0
for (i in seq_len(n)) {
k1 <- fun(waktu[i], y[i])
k2 <- fun(waktu[i] + h / 2, y[i] + h * k1 / 2)
k3 <- fun(waktu[i] + h / 2, y[i] + h * k2 / 2)
k4 <- fun(waktu[i] + h, y[i] + h * k3)
y[i + 1] <- y[i] + h * (k1 + 2 * k2 + 2 * k3 + k4) / 6
}
data.frame(waktu = waktu, y = y)
}
ode_acuan <- function(waktu) {
hasil <- deSolve::ode(
y = c(y = 1), times = waktu,
func = function(t, y, parms) list(-2 * y[1] + t),
parms = NULL
)
as.data.frame(hasil)
}
bandingkan_pdb <- function(h) {
waktu <- seq(0, 2, by = h)
E <- euler_pdb(f_pdb, 0, 1, h, 2)
R <- rk4_pdb(f_pdb, 0, 1, h, 2)
O <- ode_acuan(waktu)
data.frame(h = h, waktu = waktu, Eksak = solusi_eksak(waktu),
Euler = E$y, RK4 = R$y, deSolve = O$y)
}
hasil_pdb <- do.call(rbind, lapply(c(0.2, 0.05), bandingkan_pdb))
hasil_pdb$Galat_Euler <- abs(hasil_pdb$Euler - hasil_pdb$deSolve)
hasil_pdb$Galat_RK4 <- abs(hasil_pdb$RK4 - hasil_pdb$deSolve)
knitr::kable(
aggregate(cbind(Galat_Euler, Galat_RK4) ~ h, data = hasil_pdb, FUN = max),
digits = 10, caption = "Galat maksimum terhadap hasil deSolve()"
)
| h | Galat_Euler | Galat_RK4 |
|---|---|---|
| 0.05 | 0.02400155 | 0.0000015320 |
| 0.20 | 0.11166138 | 0.0001338167 |
knitr::kable(head(hasil_pdb, 12), digits = 8,
caption = "Sebagian hasil numerik untuk h = 0.2")
| h | waktu | Eksak | Euler | RK4 | deSolve | Galat_Euler | Galat_RK4 |
|---|---|---|---|---|---|---|---|
| 0.20 | 0.0 | 1.0000000 | 1.0000000 | 1.0000000 | 1.0000000 | 0.00000000 | 0.00000000 |
| 0.20 | 0.2 | 0.6879001 | 0.6000000 | 0.6880000 | 0.6879016 | 0.08790161 | 0.00009839 |
| 0.20 | 0.4 | 0.5116612 | 0.4000000 | 0.5117952 | 0.5116614 | 0.11166138 | 0.00013382 |
| 0.20 | 0.6 | 0.4264928 | 0.3200000 | 0.4266275 | 0.4264950 | 0.10649496 | 0.00013254 |
| 0.20 | 0.8 | 0.4023706 | 0.3120000 | 0.4024911 | 0.4023718 | 0.09037177 | 0.00011930 |
| 0.20 | 1.0 | 0.4191691 | 0.3472000 | 0.4192700 | 0.4191699 | 0.07196992 | 0.00010010 |
| 0.20 | 1.2 | 0.4633974 | 0.4083200 | 0.4634786 | 0.4633983 | 0.05507826 | 0.00008036 |
| 0.20 | 1.4 | 0.5260126 | 0.4849920 | 0.5260761 | 0.5260133 | 0.04102130 | 0.00006277 |
| 0.20 | 1.6 | 0.6009528 | 0.5709952 | 0.6010014 | 0.6009534 | 0.02995816 | 0.00004804 |
| 0.20 | 1.8 | 0.6841546 | 0.6625971 | 0.6841913 | 0.6841554 | 0.02155831 | 0.00003590 |
| 0.20 | 2.0 | 0.7728945 | 0.7575583 | 0.7729219 | 0.7728951 | 0.01533685 | 0.00002674 |
| 0.05 | 0.0 | 1.0000000 | 1.0000000 | 1.0000000 | 1.0000000 | 0.00000000 | 0.00000000 |
par(mfrow = c(1, 2))
for (h_sekarang in c(0.2, 0.05)) {
d <- subset(hasil_pdb, h == h_sekarang)
plot(d$waktu, d$deSolve, type = "l", lwd = 2, lty = 1,
xlab = "Waktu (t)", ylab = "y(t)",
main = paste("Perbandingan metode, h =", h_sekarang))
lines(d$waktu, d$Eksak, lwd = 2, lty = 2)
lines(d$waktu, d$Euler, lwd = 2, lty = 3)
lines(d$waktu, d$RK4, lwd = 2, lty = 4)
legend("topright", legend = c("deSolve", "Eksak", "Euler", "RK4"),
lty = 1:4, lwd = 2, bty = "n", cex = 0.8)
}
par(mfrow = c(1, 1))
Euler merupakan metode orde satu, sedangkan RK4 merupakan metode orde empat. Umumnya RK4 memberikan hampiran lebih dekat terhadap solusi acuan untuk ukuran langkah yang sama. Pengurangan \(h\) juga diharapkan memperkecil galat Euler.
Minimumkan \(f(x,y)=x^4+y^4-4xy+1\)
dengan gradient descent, Newton multivariabel, dan optim()
metode BFGS. Tentukan seluruh titik stasioner dan periksa
Hessian-nya.
f_opt <- function(p) {
x <- p[1]; y <- p[2]
x^4 + y^4 - 4 * x * y + 1
}
grad_opt <- function(p) {
x <- p[1]; y <- p[2]
c(4 * x^3 - 4 * y, 4 * y^3 - 4 * x)
}
hess_opt <- function(p) {
x <- p[1]; y <- p[2]
matrix(c(12 * x^2, -4, -4, 12 * y^2), nrow = 2, byrow = TRUE)
}
gradient_descent <- function(start, alpha = 0.01, tol = 1e-8, maxit = 100000) {
p <- as.numeric(start)
konvergen <- FALSE
for (i in seq_len(maxit)) {
g <- grad_opt(p)
if (sqrt(sum(g^2)) < tol) {
konvergen <- TRUE
break
}
p_baru <- p - alpha * g
if (any(!is.finite(p_baru))) break
p <- p_baru
}
list(par = p, value = f_opt(p), iter = i, converged = konvergen,
grad_norm = sqrt(sum(grad_opt(p)^2)))
}
newton_optimasi <- function(start, tol = 1e-10, maxit = 100) {
p <- as.numeric(start)
konvergen <- FALSE
for (i in seq_len(maxit)) {
g <- grad_opt(p)
if (sqrt(sum(g^2)) < tol) {
konvergen <- TRUE
break
}
langkah <- tryCatch(solve(hess_opt(p), g), error = function(e) NULL)
if (is.null(langkah)) break
# Backtracking sederhana agar langkah tidak menaikkan fungsi objektif.
skala <- 1
while (skala > 1e-8 && f_opt(p - skala * langkah) > f_opt(p)) {
skala <- skala / 2
}
if (skala <= 1e-8) break
p <- p - skala * langkah
}
list(par = p, value = f_opt(p), iter = i, converged = konvergen,
grad_norm = sqrt(sum(grad_opt(p)^2)))
}
bandingkan_optimasi <- function(start, label_start) {
gd <- gradient_descent(start)
nw <- newton_optimasi(start)
bg <- optim(
par = start, fn = f_opt, gr = grad_opt, method = "BFGS",
control = list(reltol = 1e-12, maxit = 10000)
)
rbind(
data.frame(Titik_Awal = label_start, Metode = "Gradient descent",
x = gd$par[1], y = gd$par[2], Nilai_f = gd$value,
Iterasi = gd$iter, Evaluasi_Fungsi = NA_integer_,
Konvergen = gd$converged),
data.frame(Titik_Awal = label_start, Metode = "Newton multivariabel",
x = nw$par[1], y = nw$par[2], Nilai_f = nw$value,
Iterasi = nw$iter, Evaluasi_Fungsi = NA_integer_,
Konvergen = nw$converged),
data.frame(Titik_Awal = label_start, Metode = "optim BFGS",
x = bg$par[1], y = bg$par[2], Nilai_f = bg$value,
Iterasi = NA_integer_, Evaluasi_Fungsi = unname(bg$counts[1]),
Konvergen = (bg$convergence == 0))
)
}
hasil_optimasi <- rbind(
bandingkan_optimasi(c(2, 2), "(2, 2)"),
bandingkan_optimasi(c(-2, -2), "(-2, -2)")
)
knitr::kable(hasil_optimasi, digits = 10,
caption = "Hasil tiga metode optimasi dari dua titik awal")
| Titik_Awal | Metode | x | y | Nilai_f | Iterasi | Evaluasi_Fungsi | Konvergen |
|---|---|---|---|---|---|---|---|
| (2, 2) | Gradient descent | 1.0000000 | 1.0000000 | -1 | 239 | NA | TRUE |
| (2, 2) | Newton multivariabel | 1.0000000 | 1.0000000 | -1 | 7 | NA | TRUE |
| (2, 2) | optim BFGS | 0.9999999 | 0.9999999 | -1 | NA | 10 | TRUE |
| (-2, -2) | Gradient descent | -1.0000000 | -1.0000000 | -1 | 239 | NA | TRUE |
| (-2, -2) | Newton multivariabel | -1.0000000 | -1.0000000 | -1 | 7 | NA | TRUE |
| (-2, -2) | optim BFGS | -0.9999999 | -0.9999999 | -1 | NA | 10 | TRUE |
Gradien sama dengan nol jika \(4x^3-4y=0\) dan \(4y^3-4x=0\). Titik stasioner riilnya adalah \((0,0)\), \((1,1)\), dan \((-1,-1)\). Klasifikasi dilakukan berdasarkan nilai eigen Hessian.
titik_stasioner <- data.frame(x = c(0, 1, -1), y = c(0, 1, -1))
titik_stasioner$Nilai_f <- apply(
titik_stasioner[, c("x", "y")], 1,
function(p) f_opt(as.numeric(p))
)
nilai_eigen <- t(vapply(
seq_len(nrow(titik_stasioner)),
function(i) eigen(
hess_opt(c(titik_stasioner$x[i], titik_stasioner$y[i])),
symmetric = TRUE, only.values = TRUE
)$values,
numeric(2)
))
titik_stasioner$Eigen_Min <- apply(nilai_eigen, 1, min)
titik_stasioner$Eigen_Maks <- apply(nilai_eigen, 1, max)
titik_stasioner$Klasifikasi <- ifelse(
titik_stasioner$Eigen_Min > 0, "Minimum lokal",
ifelse(titik_stasioner$Eigen_Maks < 0, "Maksimum lokal", "Titik pelana")
)
knitr::kable(titik_stasioner, digits = 6,
caption = "Klasifikasi titik stasioner berdasarkan Hessian")
| x | y | Nilai_f | Eigen_Min | Eigen_Maks | Klasifikasi |
|---|---|---|---|---|---|
| 0 | 0 | 1 | -4 | 4 | Titik pelana |
| 1 | 1 | -1 | 8 | 16 | Minimum lokal |
| -1 | -1 | -1 | 8 | 16 | Minimum lokal |
Titik \((1,1)\) dan \((-1,-1)\) memiliki nilai fungsi \(-1\) dan Hessian definit positif, sehingga keduanya merupakan minimum lokal sekaligus minimum global. Titik \((0,0)\) adalah titik pelana karena Hessian memiliki nilai eigen dengan tanda berbeda.
Keuntungan per unit produk \(x\) dan
\(y\) adalah 40 dan 30. Kendala: \(2x+y\leq100\), \(x+y\leq80\), \(x\leq40\), dan \(x,y\geq0\). Tentukan produksi optimal
menggunakan lpSolve dan gambarkan daerah layak.
keuntungan <- c(40, 30)
kendala_lp <- rbind(c(2, 1), c(1, 1), c(1, 0))
arah_kendala <- c("<=", "<=", "<=")
ruas_kanan <- c(100, 80, 40)
hasil_lp <- lpSolve::lp(
direction = "max",
objective.in = keuntungan,
const.mat = kendala_lp,
const.dir = arah_kendala,
const.rhs = ruas_kanan
)
solusi_x <- hasil_lp$solution[1]
solusi_y <- hasil_lp$solution[2]
knitr::kable(
data.frame(
Status = ifelse(hasil_lp$status == 0, "Optimal", "Periksa status solver"),
Produk_X = solusi_x,
Produk_Y = solusi_y,
Keuntungan_Maksimum = hasil_lp$objval
),
digits = 6, caption = "Solusi optimal pemrograman linear"
)
| Status | Produk_X | Produk_Y | Keuntungan_Maksimum |
|---|---|---|---|
| Optimal | 20 | 60 | 2600 |
pemakaian <- as.vector(kendala_lp %*% hasil_lp$solution)
knitr::kable(
data.frame(
Kendala = c("2x + y", "x + y", "x"),
Nilai_Kiri = pemakaian,
Batas = ruas_kanan,
Sisa_Kapasitas = ruas_kanan - pemakaian
),
digits = 6, caption = "Pemeriksaan kendala pada solusi optimal"
)
| Kendala | Nilai_Kiri | Batas | Sisa_Kapasitas |
|---|---|---|---|
| 2x + y | 100 | 100 | 0 |
| x + y | 80 | 80 | 0 |
| x | 20 | 40 | 20 |
plot(NA, xlim = c(0, 45), ylim = c(0, 90),
xlab = "Jumlah produk X", ylab = "Jumlah produk Y",
main = "Daerah Layak Pemrograman Linear")
# Titik sudut daerah layak: (0,0), (40,0), (40,20), (20,60), (0,80).
polygon(
x = c(0, 40, 40, 20, 0),
y = c(0, 0, 20, 60, 80),
col = gray(0.9), border = "gray40"
)
xx <- seq(0, 45, length.out = 300)
lines(xx, 100 - 2 * xx, lty = 1, lwd = 2)
lines(xx, 80 - xx, lty = 2, lwd = 2)
abline(v = 40, lty = 3, lwd = 2)
points(solusi_x, solusi_y, pch = 19, cex = 1.3)
text(solusi_x, solusi_y, labels = "(20, 60)", pos = 4)
legend(
"topright",
legend = c("2x + y = 100", "x + y = 80", "x = 40", "Daerah layak", "Optimum"),
lty = c(1, 2, 3, 0, 0), pch = c(NA, NA, NA, 15, 19),
lwd = c(2, 2, 2, 0, 0), pt.cex = 1.2, bty = "n"
)
Solusi optimal adalah 20 unit produk X dan 60 unit produk Y, dengan keuntungan maksimum 2600 satuan keuntungan. Solusi tersebut memenuhi semua kendala dan berada pada perpotongan \(2x+y=100\) dan \(x+y=80\).
Bandingkan BFGS, Nelder-Mead, SANN, dan algoritma genetika (GA) pada
fungsi Rastrigin lima dimensi. Lakukan 20 ulangan acak dan laporkan
rata-rata serta simpangan baku nilai akhir dan waktu komputasi dari
system.time().
rastrigin <- function(x) {
10 * length(x) + sum(x^2 - 10 * cos(2 * pi * x))
}
grad_rastrigin <- function(x) {
2 * x + 20 * pi * sin(2 * pi * x)
}
# Minimum global fungsi Rastrigin berada di (0, 0, ..., 0), dengan nilai 0.
Pengaturan dibuat tetap agar hasil dapat direproduksi. BFGS dan Nelder-Mead memakai titik awal acak yang sama pada setiap ulangan. SANN dan GA menggunakan bilangan acak internal; seed diatur pada setiap ulangan.
jumlah_ulangan <- 20
metode_uji <- c("BFGS", "Nelder-Mead", "SANN", "GA")
set.seed(2026)
titik_awal <- replicate(
jumlah_ulangan,
runif(5, min = -5.12, max = 5.12),
simplify = FALSE
)
hasil_benchmark <- vector("list", length(metode_uji) * jumlah_ulangan)
indeks_hasil <- 1
for (nama_metode in metode_uji) {
for (i in seq_len(jumlah_ulangan)) {
set.seed(100000 + 1000 * match(nama_metode, metode_uji) + i)
awal <- titik_awal[[i]]
if (nama_metode == "BFGS") {
waktu <- system.time({
fit <- optim(
par = awal, fn = rastrigin, gr = grad_rastrigin,
method = "BFGS",
control = list(maxit = 2000, reltol = 1e-10)
)
nilai_akhir <- fit$value
})[["elapsed"]]
} else if (nama_metode == "Nelder-Mead") {
waktu <- system.time({
fit <- optim(
par = awal, fn = rastrigin, method = "Nelder-Mead",
control = list(maxit = 5000, reltol = 1e-10)
)
nilai_akhir <- fit$value
})[["elapsed"]]
} else if (nama_metode == "SANN") {
waktu <- system.time({
fit <- optim(
par = awal, fn = rastrigin, method = "SANN",
control = list(maxit = 20000, temp = 20, tmax = 10, trace = 0)
)
nilai_akhir <- fit$value
})[["elapsed"]]
} else if (nama_metode == "GA") {
waktu <- system.time({
fit_ga <- GA::ga(
type = "real-valued",
fitness = function(x) -rastrigin(x),
lower = rep(-5.12, 5),
upper = rep(5.12, 5),
popSize = 50,
maxiter = 150,
run = 30,
monitor = FALSE
)
solusi_ga <- as.numeric(fit_ga@solution[1, ])
nilai_akhir <- rastrigin(solusi_ga)
})[["elapsed"]]
}
hasil_benchmark[[indeks_hasil]] <- data.frame(
Metode = nama_metode,
Ulangan = i,
Nilai_Akhir = as.numeric(nilai_akhir),
Waktu_Detik = as.numeric(waktu)
)
indeks_hasil <- indeks_hasil + 1
}
}
data_benchmark <- do.call(rbind, hasil_benchmark)
data_benchmark$Metode <- factor(data_benchmark$Metode, levels = metode_uji)
knitr::kable(
head(data_benchmark, 12), digits = 6,
caption = "Sebagian hasil dari 20 ulangan setiap metode"
)
| Metode | Ulangan | Nilai_Akhir | Waktu_Detik |
|---|---|---|---|
| BFGS | 1 | 22.88395 | 0.03 |
| BFGS | 2 | 50.74253 | 0.00 |
| BFGS | 3 | 69.64664 | 0.00 |
| BFGS | 4 | 54.72224 | 0.00 |
| BFGS | 5 | 62.68200 | 0.00 |
| BFGS | 6 | 51.73749 | 0.00 |
| BFGS | 7 | 47.75772 | 0.00 |
| BFGS | 8 | 19.89912 | 0.00 |
| BFGS | 9 | 50.74234 | 0.00 |
| BFGS | 10 | 41.78799 | 0.00 |
| BFGS | 11 | 52.73252 | 0.00 |
| BFGS | 12 | 63.67684 | 0.00 |
rata_rata <- aggregate(
cbind(Nilai_Akhir, Waktu_Detik) ~ Metode,
data = data_benchmark, FUN = mean
)
simpangan_baku <- aggregate(
cbind(Nilai_Akhir, Waktu_Detik) ~ Metode,
data = data_benchmark, FUN = sd
)
tabel_benchmark <- data.frame(
Metode = rata_rata$Metode,
Rata_Rata_Nilai_Akhir = rata_rata$Nilai_Akhir,
SD_Nilai_Akhir = simpangan_baku$Nilai_Akhir,
Rata_Rata_Waktu_Detik = rata_rata$Waktu_Detik,
SD_Waktu_Detik = simpangan_baku$Waktu_Detik
)
knitr::kable(
tabel_benchmark, digits = 6,
caption = "Ringkasan kinerja metode optimasi Rastrigin 5 dimensi"
)
| Metode | Rata_Rata_Nilai_Akhir | SD_Nilai_Akhir | Rata_Rata_Waktu_Detik | SD_Waktu_Detik |
|---|---|---|---|---|
| BFGS | 45.519026 | 19.025927 | 0.0015 | 0.006708 |
| Nelder-Mead | 45.270328 | 20.463082 | 0.0020 | 0.006156 |
| SANN | 24.687925 | 13.702407 | 0.1260 | 0.040833 |
| GA | 0.559775 | 0.926565 | 0.3565 | 0.125416 |
par(mfrow = c(1, 2))
boxplot(
Nilai_Akhir ~ Metode, data = data_benchmark,
xlab = "Metode", ylab = "Nilai fungsi akhir",
main = "Distribusi Nilai Akhir", las = 2
)
boxplot(
Waktu_Detik ~ Metode, data = data_benchmark,
xlab = "Metode", ylab = "Waktu (detik)",
main = "Distribusi Waktu Komputasi", las = 2
)
par(mfrow = c(1, 1))
Fungsi Rastrigin mempunyai banyak minimum lokal dan minimum global bernilai 0 pada titik asal. BFGS dan Nelder-Mead dapat berhenti pada minimum lokal bergantung pada titik awal. SANN dan GA dirancang untuk mengeksplorasi ruang pencarian secara lebih global, tetapi hasilnya tetap dipengaruhi parameter dan bilangan acak. Kesimpulan akhir perlu didasarkan pada rata-rata, simpangan baku, dan waktu pada tabel, bukan hanya satu ulangan.
Virgania Sari, S.Pd., M.Si. Komputasi Numerik dan Optimasi: Modul Materi dan Praktikum Menggunakan R. Mata Kuliah Komputasi Statistika.
Dokumentasi paket R: deSolve, lpSolve,
GA, dan fungsi dasar R seperti optim(),
uniroot(), integrate(), serta
solve().