求解非线性方程组,即寻找能够使 \(f(\boldsymbol{X})=\boldsymbol{0}\) 的 \(\boldsymbol{X}\).
具体实现上,有多种算法,如不动点迭代法(fixed-point
iteration)、牛顿法(Newton-Raphson method)、割线法(secant
method)、二分法(bisection
method)等。不动点法的收敛速度较慢,也更可能导致不收敛;最常用的是牛顿法和二分法。
手写 Newton-Raphson 算法
一元方程
Newton_root <- function(f, start,
tolerance = 1e-7, max_iter = 50) {
root <- start
for (i in 1:max_iter) {
f_prime <- numDeriv::genD(f, root)$D[1, 1]
# $D 是一个矩阵,其中不仅包含一阶导数,还有二阶导数
dx <- f(root) / f_prime
root <- root - dx
print(str_c("root = ", root))
if (abs(dx) < tolerance) {
return(list(
root = root, f.root = f(root),
iterator = i, estimate_precise = abs(dx)
))
}
}
print("Maximum number of iterations exceeded.")
}
transformation0 <- function(x) {
x^3 - sin(x)^2
}
Newton_root(f = transformation0, start = 1)
#> [1] "root = 0.860369147925258"
#> [1] "root = 0.809891223391771"
#> [1] "root = 0.802931615098361"
#> [1] "root = 0.802803774492958"
#> [1] "root = 0.802803731737894"
#> $root
#> [1] 0.8028037
#>
#> $f.root
#> [1] 4.440892e-15
#>
#> $iterator
#> [1] 5
#>
#> $estimate_precise
#> [1] 4.275506e-08
多元方程组
Newton_Raphson <- function(transformation, start, tolerance = 1e-7, max_iter = 50) {
dimension <- length(start)
root <- start
for (i in 1:max_iter) {
f <- transformation(root)
Jacobi <- numDeriv::genD(transformation, root)$D[1:dimension, 1:dimension]
dx <- solve(Jacobi, f)
root <- root - dx
str_c(root, collapse = ", ") %>%
str_c("root = [", ., "]") %>%
print()
precise <- sqrt(sum(dx * dx))
if (precise < tolerance) {
return(list(
root = root, f.root = transformation(root),
iterator = i, estimate_precise = precise
))
}
}
print("Maximum number of iterations exceeded.")
}
transformation1 <- function(x) {
c(
f1 = x[1] + x[2] - 2.5,
f2 = x[1] * x[2] - 1
)
}
Newton_Raphson(transformation1, start = c(0, 3))
#> [1] "root = [0.33333333333384, 2.16666666666507]"
#> [1] "root = [0.484848484848594, 2.01515151514307]"
#> [1] "root = [0.499849984998516, 2.00015001500144]"
#> [1] "root = [0.499999984999999, 2.000000015]"
#> [1] "root = [0.5, 2]"
#> $root
#> [1] 0.5 2.0
#>
#> $f.root
#> f1 f2
#> 0.000000e+00 -2.220446e-16
#>
#> $iterator
#> [1] 5
#>
#> $estimate_precise
#> [1] 2.121321e-08
rootSolve 包实现 Newton-Raphson 算法
算法详情见 CRAN 上的 rootSolve.pdf,可能对标准的
Newton-Raphson 算法进行了若干优化,防止发散
一元方程
寻找 root,使传入的函数作用于 root 时返回的标量为0
fs <- function(s) {
s^3 - 4 * s^2 - 10 * s + 4
}
# 一次性在4个初始位置开始迭代,求出了所有三个数值解
rootSolve::multiroot(f = fs, start = c(-5, 0, 2.5, 8))
#> $root
#> [1] -2.0000000 0.3542487 0.3542487 5.6457513
#>
#> $f.root
#> [1] -6.375274e-10 -8.881784e-16 4.440892e-16 1.421085e-14
#>
#> $iter
#> [1] 7
#>
#> $estim.precis
#> [1] 1.593857e-10
# 求出参数区间内的所有解
rootSolve::uniroot.all(f = fs, interval = c(-10, 10))
#> [1] -2.000000 0.354230 5.645754
二元方程组 without Jacobi
一般来说,不需要显示给出偏导数的 Jacobi 矩阵
transformation2 <- function(x) {
c(
f1 = 10 * x[1] + 3 * x[2]^2 - 3,
f2 = x[1]^2 - exp(x[2]) - 2
)
}
rootSolve::multiroot(f = transformation2, start = c(1, 1))
#> $root
#> [1] -1.445552 -2.412158
#>
#> $f.root
#> f1 f2
#> 5.117684e-12 -6.084022e-14
#>
#> $iter
#> [1] 10
#>
#> $estim.precis
#> [1] 2.589262e-12
二元方程组 with Jacobi
显示给出偏导数的 Jacobi 矩阵,在某些时候可以加快迭代速度
derivs <- function(x) {
c(
10, 6 * x[2], # f11, f12
2 * x[1], -exp(x[2]) # f21, f22
) %>%
matrix(nrow = 2, byrow = T)
}
rootSolve::multiroot(f = transformation2, start = c(0, 0), jacfunc = derivs)
#> $root
#> [1] -1.445552 -2.412158
#>
#> $f.root
#> f1 f2
#> 1.166651e-09 -1.390243e-11
#>
#> $iter
#> [1] 29
#>
#> $estim.precis
#> [1] 5.902766e-10
方程组中有若干参数
transformation3 <- function(x, parms) {
c(
f1 = x[1] + x[2] + x[3]^2 - parms[1],
f2 = x[1]^2 - x[2] + x[3] - parms[2],
f3 = 2 * x[1] - x[2]^2 + x[3] - 1
)
}
parms <- c(12, 2)
rootSolve::multiroot(transformation3, start = c(1, 1, 1), parms = parms)
#> $root
#> [1] 1 2 3
#>
#> $f.root
#> f1 f2 f3
#> 3.087877e-10 4.794444e-09 -8.678146e-09
#>
#> $iter
#> [1] 6
#>
#> $estim.precis
#> [1] 4.593792e-09
rootSolve::multiroot(transformation3, c(0, 0, 0), parms = parms * 2)
#> $root
#> [1] -0.8103629 1.4861978 4.8295098
#>
#> $f.root
#> f1 f2 f3
#> -4.973799e-14 4.143352e-12 -5.280221e-13
#>
#> $iter
#> [1] 11
#>
#> $estim.precis
#> [1] 1.573704e-12
矩阵形式的方程组
rootSolve::multiroot()
的第一个参数(非线性变换)可以返回一个矩阵。计算出的解将保证这个矩阵为全零矩阵。
例:求解 25 元非线性方程组
\[
\mathbf{X} \cdot \mathbf{X} \cdot \mathbf{X}=\left[\begin{array}{lllll}
1 & 2 & 3 & 4 & 5 \\
6 & 7 & 8 & 9 & 10 \\
11 & 12 & 13 & 14 & 15 \\
16 & 17 & 18 & 19 & 20 \\
21 & 22 & 23 & 24 & 25
\end{array}\right]
\]
transformation4 <- function(x) {
X <- matrix(x, nrow = 5)
X %*% X %*% X - matrix(1:25, nrow = 5, byrow = TRUE)
}
x <- rootSolve::multiroot(transformation4, start = 1:25)$root
x
#> [1] -0.67506260 -0.03483809 0.60668343 1.24815964 1.88640623 -0.34547775
#> [7] 0.16864884 0.68350802 1.19974369 1.71457060 -0.01800918 0.37530821
#> [13] 0.76025826 1.15042418 1.54265669 0.31230570 0.57358465 0.84421247
#> [19] 1.10046033 1.36971978 0.64043429 0.77971070 0.91597598 1.06007417
#> [25] 1.19373256
X <- matrix(nrow = 5, x)
X %*% X %*% X
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] 1 2 3 4 5
#> [2,] 6 7 8 9 10
#> [3,] 11 12 13 14 15
#> [4,] 16 17 18 19 20
#> [5,] 21 22 23 24 25
LS0tDQp0aXRsZTogIk5ld3Rvbi1SYXBoc29uIE1ldGhvZCINCnN1YnRpdGxlOiAi5rGC6Kej6Z2e57q/5oCn5pa556iLL+aWueeoi+e7hCINCmF1dGhvcjogIkh1bW9vbiINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgc2VsZl9jb250YWluZWQ6IGZhbHNlDQogICAgbGliX2RpcjogbGlicw0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIGNzczogLi4vY3NzL3N0eWxlLmNzcw0KICAgIGZpZ19jYXB0aW9uOiB5ZXMNCiAgICB0aGVtZTogdW5pdGVkDQogICAgaGlnaGxpZ2h0OiBoYWRkb2NrDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgbnVtYmVyX3NlY3Rpb25zOiBubw0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiB5ZXMNCiAgICAgIHNtb290aF9zY3JvbGw6IHllcw0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiBubw0KICAgIHRvY19kZXB0aDogMw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQpkb2N1bWVudGNsYXNzOiBjdGV4YXJ0DQpjbGFzc29wdGlvbjogaHlwZXJyZWYsDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGUgPSBGQUxTRX0NCnNvdXJjZSgiLi4vUm1hcmtkb3duLXRlbXBsYXRlL1JtYXJrZG93bl9jb25maWcuUiIpDQoNCiMjIGdsb2JhbCBvcHRpb25zID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQprbml0cjo6b3B0c19jaHVuayRzZXQoDQogIHdpZHRoID0gY29uZmlnJHdpZHRoLA0KICBmaWcud2lkdGggPSBjb25maWckZmlnLndpZHRoLA0KICBmaWcuYXNwID0gY29uZmlnJGZpZy5hc3AsDQogIG91dC53aWR0aCA9IGNvbmZpZyRvdXQud2lkdGgsDQogIGZpZy5hbGlnbiA9IGNvbmZpZyRmaWcuYWxpZ24sDQogIGZpZy5wYXRoID0gY29uZmlnJGZpZy5wYXRoLA0KICBmaWcuc2hvdyA9IGNvbmZpZyRmaWcuc2hvdywNCiAgd2FybiA9IGNvbmZpZyR3YXJuLA0KICB3YXJuaW5nID0gY29uZmlnJHdhcm5pbmcsDQogIG1lc3NhZ2UgPSBjb25maWckbWVzc2FnZSwNCiAgZWNobyA9IGNvbmZpZyRlY2hvLA0KICBldmFsID0gY29uZmlnJGV2YWwsDQogIHRpZHkgPSBjb25maWckdGlkeSwNCiAgY29tbWVudCA9IGNvbmZpZyRjb21tZW50LA0KICBjb2xsYXBzZSA9IGNvbmZpZyRjb2xsYXBzZSwNCiAgY2FjaGUgPSBjb25maWckY2FjaGUsDQogIGNhY2hlLmNvbW1lbnRzID0gY29uZmlnJGNhY2hlLmNvbW1lbnRzLA0KICBhdXRvZGVwID0gY29uZmlnJGF1dG9kZXANCikNCmBgYA0KDQo8YnI+DQo8YnI+DQoNCuaxguino+mdnue6v+aAp+aWueeoi+e7hO+8jOWNs+Wvu+aJvuiDveWkn+S9vyAkZihcYm9sZHN5bWJvbHtYfSk9XGJvbGRzeW1ib2x7MH0kIOeahCAkXGJvbGRzeW1ib2x7WH0kLiANCg0K5YW35L2T5a6e546w5LiK77yM5pyJ5aSa56eN566X5rOV77yM5aaC5LiN5Yqo54K56L+t5Luj5rOV77yIZml4ZWQtcG9pbnQgaXRlcmF0aW9u77yJ44CB54mb6aG/5rOV77yITmV3dG9uLVJhcGhzb24gbWV0aG9k77yJ44CB5Ymy57q/5rOV77yIc2VjYW50IG1ldGhvZO+8ieOAgeS6jOWIhuazle+8iGJpc2VjdGlvbiBtZXRob2TvvInnrYnjgILkuI3liqjngrnms5XnmoTmlLbmlZvpgJ/luqbovoPmhaLvvIzkuZ/mm7Tlj6/og73lr7zoh7TkuI3mlLbmlZvvvJvmnIDluLjnlKjnmoTmmK/niZvpob/ms5XlkozkuozliIbms5XjgIINCg0KIyMg5omL5YaZIE5ld3Rvbi1SYXBoc29uIOeul+azlQ0KDQojIyMg5LiA5YWD5pa556iLDQoNCmBgYHtyfQ0KTmV3dG9uX3Jvb3QgPC0gZnVuY3Rpb24oZiwgc3RhcnQsDQogICAgICAgICAgICAgICAgICAgICAgICB0b2xlcmFuY2UgPSAxZS03LCBtYXhfaXRlciA9IDUwKSB7DQogIHJvb3QgPC0gc3RhcnQNCiAgZm9yIChpIGluIDE6bWF4X2l0ZXIpIHsNCiAgICBmX3ByaW1lIDwtIG51bURlcml2OjpnZW5EKGYsIHJvb3QpJERbMSwgMV0NCiAgICAjICREIOaYr+S4gOS4quefqemYte+8jOWFtuS4reS4jeS7heWMheWQq+S4gOmYtuWvvOaVsO+8jOi/mOacieS6jOmYtuWvvOaVsA0KICAgIGR4IDwtIGYocm9vdCkgLyBmX3ByaW1lDQogICAgcm9vdCA8LSByb290IC0gZHgNCiAgICBwcmludChzdHJfYygicm9vdCA9ICIsIHJvb3QpKQ0KICAgIGlmIChhYnMoZHgpIDwgdG9sZXJhbmNlKSB7DQogICAgICByZXR1cm4obGlzdCgNCiAgICAgICAgcm9vdCA9IHJvb3QsIGYucm9vdCA9IGYocm9vdCksDQogICAgICAgIGl0ZXJhdG9yID0gaSwgZXN0aW1hdGVfcHJlY2lzZSA9IGFicyhkeCkNCiAgICAgICkpDQogICAgfQ0KICB9DQogIHByaW50KCJNYXhpbXVtIG51bWJlciBvZiBpdGVyYXRpb25zIGV4Y2VlZGVkLiIpDQp9DQoNCnRyYW5zZm9ybWF0aW9uMCA8LSBmdW5jdGlvbih4KSB7DQogIHheMyAtIHNpbih4KV4yDQp9DQoNCk5ld3Rvbl9yb290KGYgPSB0cmFuc2Zvcm1hdGlvbjAsIHN0YXJ0ID0gMSkNCmBgYA0KDQojIyMg5aSa5YWD5pa556iL57uEDQoNCmBgYHtyfQ0KTmV3dG9uX1JhcGhzb24gPC0gZnVuY3Rpb24odHJhbnNmb3JtYXRpb24sIHN0YXJ0LCB0b2xlcmFuY2UgPSAxZS03LCBtYXhfaXRlciA9IDUwKSB7DQogIGRpbWVuc2lvbiA8LSBsZW5ndGgoc3RhcnQpDQogIHJvb3QgPC0gc3RhcnQNCiAgZm9yIChpIGluIDE6bWF4X2l0ZXIpIHsNCiAgICBmIDwtIHRyYW5zZm9ybWF0aW9uKHJvb3QpDQogICAgSmFjb2JpIDwtIG51bURlcml2OjpnZW5EKHRyYW5zZm9ybWF0aW9uLCByb290KSREWzE6ZGltZW5zaW9uLCAxOmRpbWVuc2lvbl0NCiAgICBkeCA8LSBzb2x2ZShKYWNvYmksIGYpDQogICAgcm9vdCA8LSByb290IC0gZHgNCiAgICBzdHJfYyhyb290LCBjb2xsYXBzZSA9ICIsICIpICU+JQ0KICAgICAgc3RyX2MoInJvb3QgPSBbIiwgLiwgIl0iKSAlPiUNCiAgICAgIHByaW50KCkNCg0KICAgIHByZWNpc2UgPC0gc3FydChzdW0oZHggKiBkeCkpDQogICAgaWYgKHByZWNpc2UgPCB0b2xlcmFuY2UpIHsNCiAgICAgIHJldHVybihsaXN0KA0KICAgICAgICByb290ID0gcm9vdCwgZi5yb290ID0gdHJhbnNmb3JtYXRpb24ocm9vdCksDQogICAgICAgIGl0ZXJhdG9yID0gaSwgZXN0aW1hdGVfcHJlY2lzZSA9IHByZWNpc2UNCiAgICAgICkpDQogICAgfQ0KICB9DQogIHByaW50KCJNYXhpbXVtIG51bWJlciBvZiBpdGVyYXRpb25zIGV4Y2VlZGVkLiIpDQp9DQoNCnRyYW5zZm9ybWF0aW9uMSA8LSBmdW5jdGlvbih4KSB7DQogIGMoDQogICAgZjEgPSB4WzFdICsgeFsyXSAtIDIuNSwNCiAgICBmMiA9IHhbMV0gKiB4WzJdIC0gMQ0KICApDQp9DQoNCk5ld3Rvbl9SYXBoc29uKHRyYW5zZm9ybWF0aW9uMSwgc3RhcnQgPSBjKDAsIDMpKQ0KYGBgDQoNCiMjIHJvb3RTb2x2ZSDljIXlrp7njrAgTmV3dG9uLVJhcGhzb24g566X5rOVDQoNCueul+azleivpuaDheingSBDUkFOIOS4iueahCByb290U29sdmUucGRm77yMKirlj6/og73lr7nmoIflh4bnmoQgTmV3dG9uLVJhcGhzb24g566X5rOV6L+b6KGM5LqG6Iul5bmy5LyY5YyW77yM6Ziy5q2i5Y+R5pWjKioNCg0KIyMjIOS4gOWFg+aWueeoiw0KDQrlr7vmib4gcm9vdO+8jOS9v+S8oOWFpeeahOWHveaVsOS9nOeUqOS6jiByb290IOaXtui/lOWbnueahOagh+mHj+S4ujANCg0KYGBge3J9DQpmcyA8LSBmdW5jdGlvbihzKSB7DQogIHNeMyAtIDQgKiBzXjIgLSAxMCAqIHMgKyA0DQp9DQoNCiMg5LiA5qyh5oCn5ZyoNOS4quWIneWni+S9jee9ruW8gOWni+i/reS7o++8jOaxguWHuuS6huaJgOacieS4ieS4quaVsOWAvOinow0Kcm9vdFNvbHZlOjptdWx0aXJvb3QoZiA9IGZzLCBzdGFydCA9IGMoLTUsIDAsIDIuNSwgOCkpDQoNCiMg5rGC5Ye65Y+C5pWw5Yy66Ze05YaF55qE5omA5pyJ6KejDQpyb290U29sdmU6OnVuaXJvb3QuYWxsKGYgPSBmcywgaW50ZXJ2YWwgPSBjKC0xMCwgMTApKQ0KYGBgDQoNCiMjIyDkuozlhYPmlrnnqIvnu4Qgd2l0aG91dCBKYWNvYmkNCg0K5LiA6Iis5p2l6K+077yM5LiN6ZyA6KaB5pi+56S657uZ5Ye65YGP5a+85pWw55qEIEphY29iaSDnn6npmLUNCg0KYGBge3J9DQp0cmFuc2Zvcm1hdGlvbjIgPC0gZnVuY3Rpb24oeCkgew0KICBjKA0KICAgIGYxID0gMTAgKiB4WzFdICsgMyAqIHhbMl1eMiAtIDMsDQogICAgZjIgPSB4WzFdXjIgLSBleHAoeFsyXSkgLSAyDQogICkNCn0NCnJvb3RTb2x2ZTo6bXVsdGlyb290KGYgPSB0cmFuc2Zvcm1hdGlvbjIsIHN0YXJ0ID0gYygxLCAxKSkNCmBgYA0KDQojIyMg5LqM5YWD5pa556iL57uEIHdpdGggSmFjb2JpDQoNCuaYvuekuue7meWHuuWBj+WvvOaVsOeahCBKYWNvYmkg55+p6Zi177yM5Zyo5p+Q5Lqb5pe25YCZ5Y+v5Lul5Yqg5b+r6L+t5Luj6YCf5bqmDQoNCmBgYHtyfQ0KZGVyaXZzIDwtIGZ1bmN0aW9uKHgpIHsNCiAgYygNCiAgICAxMCwgNiAqIHhbMl0sICMgZjExLCBmMTINCiAgICAyICogeFsxXSwgLWV4cCh4WzJdKSAjIGYyMSwgZjIyDQogICkgJT4lDQogICAgbWF0cml4KG5yb3cgPSAyLCBieXJvdyA9IFQpDQp9DQpyb290U29sdmU6Om11bHRpcm9vdChmID0gdHJhbnNmb3JtYXRpb24yLCBzdGFydCA9IGMoMCwgMCksIGphY2Z1bmMgPSBkZXJpdnMpDQpgYGANCg0KIyMjIOaWueeoi+e7hOS4reacieiLpeW5suWPguaVsA0KDQpgYGB7cn0NCnRyYW5zZm9ybWF0aW9uMyA8LSBmdW5jdGlvbih4LCBwYXJtcykgew0KICBjKA0KICAgIGYxID0geFsxXSArIHhbMl0gKyB4WzNdXjIgLSBwYXJtc1sxXSwNCiAgICBmMiA9IHhbMV1eMiAtIHhbMl0gKyB4WzNdIC0gcGFybXNbMl0sDQogICAgZjMgPSAyICogeFsxXSAtIHhbMl1eMiArIHhbM10gLSAxDQogICkNCn0NCnBhcm1zIDwtIGMoMTIsIDIpDQpyb290U29sdmU6Om11bHRpcm9vdCh0cmFuc2Zvcm1hdGlvbjMsIHN0YXJ0ID0gYygxLCAxLCAxKSwgcGFybXMgPSBwYXJtcykNCnJvb3RTb2x2ZTo6bXVsdGlyb290KHRyYW5zZm9ybWF0aW9uMywgYygwLCAwLCAwKSwgcGFybXMgPSBwYXJtcyAqIDIpDQpgYGANCg0KIyMjIOefqemYteW9ouW8j+eahOaWueeoi+e7hA0KDQpgcm9vdFNvbHZlOjptdWx0aXJvb3QoKWDnmoTnrKzkuIDkuKrlj4LmlbDvvIjpnZ7nur/mgKflj5jmjaLvvInlj6/ku6Xov5Tlm57kuIDkuKrnn6npmLXjgILorqHnrpflh7rnmoTop6PlsIbkv53or4Hov5nkuKrnn6npmLXkuLrlhajpm7bnn6npmLXjgIINCg0K5L6L77ya5rGC6KejIDI1IOWFg+mdnue6v+aAp+aWueeoi+e7hA0KDQokJA0KXG1hdGhiZntYfSBcY2RvdCBcbWF0aGJme1h9IFxjZG90IFxtYXRoYmZ7WH09XGxlZnRbXGJlZ2lue2FycmF5fXtsbGxsbH0NCjEgJiAyICYgMyAmIDQgJiA1IFxcDQo2ICYgNyAmIDggJiA5ICYgMTAgXFwNCjExICYgMTIgJiAxMyAmIDE0ICYgMTUgXFwNCjE2ICYgMTcgJiAxOCAmIDE5ICYgMjAgXFwNCjIxICYgMjIgJiAyMyAmIDI0ICYgMjUNClxlbmR7YXJyYXl9XHJpZ2h0XQ0KJCQNCg0KDQpgYGB7cn0NCnRyYW5zZm9ybWF0aW9uNCA8LSBmdW5jdGlvbih4KSB7DQogIFggPC0gbWF0cml4KHgsIG5yb3cgPSA1KQ0KICBYICUqJSBYICUqJSBYIC0gbWF0cml4KDE6MjUsIG5yb3cgPSA1LCBieXJvdyA9IFRSVUUpDQp9DQoNCnggPC0gcm9vdFNvbHZlOjptdWx0aXJvb3QodHJhbnNmb3JtYXRpb240LCBzdGFydCA9IDE6MjUpJHJvb3QNCngNClggPC0gbWF0cml4KG5yb3cgPSA1LCB4KQ0KWCAlKiUgWCAlKiUgWA0KYGBg