在 r 中快速扩展系列。

Fast series expansion in r

提问人:spellard 提问时间:7/27/2023 最后编辑:ThomasIsCodingspellard 更新时间:8/10/2023 访问量:96

问:

我想生成一个平滑函数的级数展开,类似于泰勒级数(但使用所谓的切比雪夫多项式)。但是,我还没有找到一种快速计算给定系数的级数展开的方法。特别是,对(切比雪夫/泰勒)多项式进行硬编码似乎比使用现有函数快得多。

显然,当近似阶数很高时,对多项式进行硬编码是不可行的,但问题是,如何用更快的代码实现这一点。这是我尝试过的:

library(mpoly)
library(foreach)

n=5

# Hard code the Chebyshev polynomials of order 1 to 4.

cheb = function(x) {x} # first order Cheb polynomial
cheb2 = function(x) {-1 + 2*x^2} # second order Chebyshev polynomial
cheb3 = function(x) {-3*x+4*x^3} # etc...
cheb4 = function(x) {1-8*x^2+8*x^4}

# Generate Chebyshev polynomials up to order n-1 using chebyshev() from library mpoly

cheb_funs = foreach(n_ind = 1:n) %do% as.function(chebyshev(n_ind-1)) 

# Calculate the series expansion using hard coded polynomials

Cheb_app1 = function(x)
{
  p = x$p
  v = x$v_cheb
  v[1] + v[2]*cheb(p) + v[3]*cheb2(p) + v[4]*cheb3(p) + v[5]*cheb4(p)
} 

# Calculate the series expansion using foreach() and cheb_funs 

Cheb_app2 = function(x)
{
  p = x$p;
  v = x$v_cheb;
  foreach(p_ind = p, .combine = "c") %do%
    (foreach(n_ind = 1:n, .combine = "sum") %do%
       (v[n_ind]*(cheb_funs[[n_ind]](p_ind))))
}

# Microbenchmark

z = list(p = runif(10, -1,1), v_cheb = runif(n, -1,1))
microbenchmark(Cheb_app1(z), Cheb_app2(z))

Unit: microseconds
         expr       min        lq       mean    median        uq      max neval
 Cheb_app1(z)     3.473     4.011   120.6585     7.955    12.944 11217.96   100
 Cheb_app2(z) 30249.810 33243.943 35605.0237 34127.602 38195.954 47322.77   100

R 函数 性能 数学 数值方法

评论

0赞 Limey 7/27/2023
不是每个人都熟悉切比切夫多项式。我认为,如果您在问题中定义了一般切比切夫多项式,或者至少提供了指向它的链接,那么您获得有用答案的机会会增加。另外,您可以接受什么执行速度?此外,请考虑设置代码的格式,以便问题中最相关的功能同时可见,而无需滚动。
0赞 spellard 7/27/2023
谢谢,我试图让它更具可读性。在速度方面,接近硬编码版本的速度会更好。现在它慢了几个数量级。
0赞 Ute 7/27/2023
很奇怪。我复制了你的代码,添加了 ,得到了非常不同的结果。在较旧的计算机 (i7-6700 CPU) 上使用 R 4.2.2 - 您使用的是哪个版本?library(foreach)Unit: nanoseconds expr min lq mean median uq max neval Cheb_app1(z) 3900 4000 4536.5 4100 4300 23800 1000 Cheb_app2(z) 200 200 341.0 200 300 15600 1000
0赞 spellard 7/27/2023
显然,当我编辑代码以提高可读性时,我在代码中添加了一个错别字。现在它应该是正确的。你现在的样子如何?
1赞 Mark 7/27/2023
这肯定是属于代码审查的问题类型吗?

答:

0赞 spellard 7/27/2023 #1

最后,这是我想出的一个解决方案。

library(pracma)
library(microbenchmark)

rm(list = ls())
n=5

# Hard code the Chebyshev polynomials of order 1 to 4.

cheb = function(x) {x} # first order Cheb polynomial
cheb2 = function(x) {-1 + 2*x^2} # second order Chebyshev polynomial
cheb3 = function(x) {-3*x+4*x^3} # etc...
cheb4 = function(x) {1-8*x^2+8*x^4}

# Generate Chebyshev polynomials up to order n-1 using chebyshev() from library mpoly

cheb_funs = foreach(n_ind = 1:n) %do% as.function(chebyshev(n_ind-1)) 

# Calculate the series expansion using hard coded polynomials

Cheb_app1 = function(x)
{
  p = x$p
  v = x$v_cheb
  v[1] + v[2]*cheb(p) + v[3]*cheb2(p) + v[4]*cheb3(p) + v[5]*cheb4(p)
} 

## Another solution ##

power <- function(x, y) sign(x)^y * abs(x)^y
polyMat = chebPoly(n-1)

Cheb_app3 = function(x) {colSums(polyMat %*% t(outer(x$p, (n-1):0, power))*x$v_cheb)}

# Microbenchmark

z = list(p = runif(10, -1,1), v_cheb = runif(n, -1,1))
microbenchmark(Cheb_app1(z),Cheb_app3(z))


Unit: microseconds
         expr    min      lq      mean  median      uq       max neval
 Cheb_app1(z)  3.366  3.6930 115.26432  4.0750  4.3250 11087.282   100
 Cheb_app3(z) 14.305 15.1085  66.43251 15.4965 15.8955  4943.902   100
0赞 jblood94 7/27/2023 #2

do.call增加了一些额外的开销,但时间非常相似:Cheb_app2

library(mpoly)

n <- 5

# Hard code the Chebyshev polynomials of order 1 to 4.

cheb <- function(x) {x} # first order Cheb polynomial
cheb2 <- function(x) {-1 + 2*x^2} # second order Chebyshev polynomial
cheb3 <- function(x) {-3*x+4*x^3} # etc...
cheb4 <- function(x) {1-8*x^2+8*x^4}

cheb_all <- as.function(
  Reduce("+", lapply(0:(n - 1), \(i) chebyshev(i)*mp(paste0("v", i)))),
  varorder = c("x", paste0("v", 0:(n - 1))),
  vector = FALSE
)
#> f(x, v0, v1, v2, v3, v4)
cheb_allc <- compiler::cmpfun(cheb_all)
cheb_funs <- as.function(chebyshev(0:(n - 1)))

Cheb_app1 <- function(x) {
  p <- x$p
  v <- x$v_cheb
  v[1] + v[2]*cheb(p) + v[3]*cheb2(p) + v[4]*cheb3(p) + v[5]*cheb4(p)
}

Cheb_app2 <- function(x) do.call("cheb_all", c(list(x$p), as.list(x$v_cheb)))
Cheb_app2c <- function(x) do.call("cheb_allc", c(list(x$p), as.list(x$v_cheb)))
Cheb_app3 <- function(x) c(cheb_funs(x$p) %*% x$v_cheb)

# Microbenchmark

z <- list(p = runif(10, -1,1), v_cheb = runif(n, -1,1))
microbenchmark::microbenchmark(
  Cheb_app1(z),
  Cheb_app2(z),
  Cheb_app2c(z),
  Cheb_app3(z),
  check = "equal",
  times = 1e3
)
#> Unit: microseconds
#>           expr  min   lq    mean median   uq    max neval
#>   Cheb_app1(z)  3.2  3.5 14.1300    3.6  3.8 8363.5  1000
#>   Cheb_app2(z)  8.6  8.9 11.0045    9.1  9.3 1515.1  1000
#>  Cheb_app2c(z)  6.4  6.8  8.5209    7.0  7.3 1292.8  1000
#>   Cheb_app3(z) 41.8 43.2 48.3163   44.0 45.3 2089.1  1000

评论

0赞 spellard 7/28/2023
谢谢!这似乎对我不起作用,但我在函数中出现以下错误:“错误:”Reduce(“+”, lapply(0:(n - 1), \“” 中的意外输入cheb_all
0赞 jblood94 7/28/2023
也许你有一个旧版本的 R,请尝试将反斜杠更改为 .function
0赞 ThomasIsCoding 7/29/2023 #3

如果你不介意速度:你可以为切布舍夫多项式定义一个函数,如下所示(例如,两个选项):

  • 递归表示
chebpoly <- Vectorize(function(x, N) {
    if (N %in% c(0, 1)) {
        return(x^N)
    }
    2 * x * Recall(x, N - 1) - Recall(x, N - 2)
}, vectorize.args = "N")
  • 三角表示
chebpoly <- Vectorize(function(x, N) {
    ifelse(abs(x) <= 1,
        cos(N * acos(x)),
        ifelse(x > 1,
            cosh(N * acosh(x)),
            (-1)^N * cosh(N * acosh((-x)))
        )
    )
}, vectorize.args = "N")

然后,你可以通过Cheb_app0

Cheb_app <- function(x) {
    p <- x$p
    v <- x$v_cheb
    colSums(v * t(chebpoly(p, seq_along(v) - 1)))
}

标杆

set.seed(0)
n <- 5
z <- list(p = runif(10, -1, 1), v_cheb = runif(n, -1, 1))
microbenchmark(
    Cheb_app0(z),
    Cheb_app1(z),
    check = "equivalent"
)

Unit: microseconds
         expr  min    lq    mean median    uq     max neval
 Cheb_app0(z) 67.0 69.85 148.407  73.45 82.85  6503.1   100
 Cheb_app1(z)  3.4  3.70 174.139   4.20  5.00 16914.2   100