求解非线性方程组,即寻找能够使 \(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