# 積分方法第一部 {%hackmd @themes/orangeheart %} <style> .likecoin-button { position: relative; width: 100%; max-width: 485px; max-height: 240px; margin: 0 auto; } .likecoin-button > div { padding-top: 49.48454%; } .likecoin-button > iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } </style> ###### tags: `Numerical Methods` 在統計學或經濟學中,我們常常會碰到以下的積分問題: $$ \begin{aligned} I = \int_a^b p(x) f(x) dx, \end{aligned} $$ 其中 $p(x)$ 是一個非負的**加權函數(weight function)**。例如,根據不自覺統計學家法則,假設給定一個機率密度函數$p(x)$,若要計算他的期望值,我們便可以利用上述的方法計算出 $I = \mathbb{E}[f(x)]$。如果說這個加權函數等於$1$,那麼積分的意義就變成是計算從下界$a$到上界$b$與該曲線所圍成的面積。 ## 定積分的近似值 給定一個區間 $[a,b] = [2, 20]$ 以及一個函數$g(x) = \frac{1}{1+x}$,而我們想要計算在給定的區間時曲線下的面積,即 $$ \begin{aligned} I = \int_a^b g(x) d x = \int_2^{20} \frac{1}{1+x} dx. \end{aligned} $$ 而根據計算機所算出的閉合解(close form),得到 $I = \log(7) \approx 1.9459\cdots$。我們可以在 $\texttt{Julia}$ 畫個圖。 ![](https://i.imgur.com/IkUoSWf.png) 根據我們在微積分課程學到的方法(大概率都是教黎曼和),我們可以在給定的區間內,將曲線下的面積切成好多個矩形並加總,當切成越多塊其就會越近似我們的答案。回到上面的例子,我們可以把區間分成10塊,即 $[2,4,6,8,10,12,14,16,18,20]$,接著可以在$[2,4]$、$[4,6]$$\cdots$ $[18,20]$找出節點(node),就可以進行加總運算。 ### 矩形加總法(Forward Rectangular Rule) 我們利用最左邊的端點當作區間,找出相對應的矩形進行計算。第一個矩形為$(4-2)g(2) = (4-2)\frac{1}{1+2}$,第二個則是$(6-4)g(4) = (6-4)\frac{1}{1+4}$ $\cdots$。因此我們可以得到 $$ \begin{aligned} I = \int_2^{20}\frac{1}{1+x}dx \approx (4-2)\frac{1}{1+2} + (6-4)\frac{1}{1+4} + \ldots, \end{aligned} $$ 我們可以推廣上述的計算方式: $$ \begin{aligned} I = \int_a^{b} g(x) dx \approx & \sum_{i=1}^n \omega_i g(\xi_i) \\ = & \frac{b-a}{n} \sum_{i=1}^n g(\xi_i) \end{aligned} $$ 在我們上述的例子中,區間$[a, b] = [2, 20]$,$n = 9$,$\omega_i = \frac{b-a}{n} = 2$則是一個矩形(區間)的長度,而 $\{\xi_i\} = \{2,4,6,8,10,12,14,16,18 \}$。 ```jula using Interact, WebIO, StatsPlots legend1 = LaTeXStrings.LaTeXString("\$\\frac{1}{1+x}\$") g(x) = 1/(1+x) true_value = 1.9459101490553133051053527434432 w_i = 3:2:19 g_i = @.g(w_i-1) bar(w_i, g_i, xticks=0:2:22, bar_width=2, fillalpha = 0.35, label="") plot!(g,xlim=[0,22], ylim=[0.0,0.4], xlabel="x", ylabel="g(x)", title="Forward Rectangular Rule", label=legend1, size=(450,280)) |> display @manipulate for n in (2, 3, 6, 9, 18, 36, 90, 180) # @manipulate for n in 1:90 step = (20-2)/n; w_i = 2+step/2:step:20-step/2; g_i = g.(w_i .- step/2); res1 = step*sum(g_i) err1 = res1 - true_value weights = fill(step, n); nodes = collect(w_i .- step/2); rule = collect(zip(weights, nodes)); @show weights @show nodes @show rule bar(w_i, g_i, xticks=0:2:22, bar_width=step, fillalpha = 0.35, label="area=$(res1)\n err=$(err1)") plot!(g, xlim=[0, 22], ylim=[0.0, 0.4], xlabel="x", ylabel="g(x)", title="Forward Rectangular Rule", label="", size=(650,380)) end ``` ![](https://i.imgur.com/LK3UDko.png) ### 中點法(Midpoint Rule 從上圖中我們可以看到,矩形與曲線相切時會出現誤差,而越接近最初始的矩形則誤差越大。而中點法可以試圖解決這個問題:我們找到一個區間的兩個點,並找出其中點。承接上面的例子,第一個矩形的計算方式改為$(4-2)*g((4+2)/2) = (4-2)*\frac{1}{1+(4+2)/2}$,其中$\frac{4+2}{2}$ 代表 $2$ 與 $4$ 的中點。同樣地,我們可以推廣它 $$ \begin{aligned} I = \int_a^b g(x) dx \approx & \sum_{i=1}^n \omega_i g(\tilde{\xi}_i) \\ = & \frac{b-a}{n} \sum_{i=1}^n g(\tilde{\xi}_i), \end{aligned} $$ 在我們的例子中,$\{\tilde{\xi}_i\} =\{(\xi_i + \xi_{i+1})/2\} = \{3,5,7,9,11,13,15,17,19 \}$ ```julia g2(x) = 1/(1+x) true_value = 1.9459101490553133051053527434432 x_i = 3:2:19 g_i = g2.(x_i) bar(x_i, g_i, xticks=1:2:21, bar_width=2, fillalpha = 0.35, label="") plot!(g2, xlim=[0,22], ylim=[0.0,0.4], title="Midpoint Rule", size=(450, 280), label=legend1, xlabel="x", ylabel="g2(x)") |> display @manipulate for n in (2, 3, 6, 9, 18, 36, 90, 180) step = (20-2)/n x_i = 2+step/2:step:20-step/2 g_i = g2.(x_i) res2 = step * sum(g_i) err2 = res2 - true_value weights = fill(step, n); nodes = collect(x_i); rule = collect(zip(weights, nodes)); @show weights @show nodes @show rule bar(x_i, g_i, xticks=1:1:21, bar_width=step, fillalpha = 0.35, label="area=$(res2)\n err=$(err2)") plot!(g2, xlim=[0,22], ylim=[0.0,0.4], title="Midpoint Rule", size=(650, 380), label="", xlabel="x", ylabel="g2(x)") end ``` ![](https://i.imgur.com/nOHHmxr.png) ### 梯形法(Trapezoidal Rule) 矩形加總法與中點法都是利用單點找出矩形,然後進行加總。當然,我們也可以找出兩點並加總,而梯形法正是其中一個例子:找出端點與區間並繪出梯形、加總。 $$ \begin{aligned} I = \int_a^b g(x) dx \approx & \sum_{i=1}^n \omega_i \frac{ g(\xi_i) + g(\xi_{i+1})}{2} \\ = & \frac{b-a}{n} \sum_{i=1}^n \frac{ g(\xi_i) + g(\xi_{i+1})}{2}, \end{aligned} $$ ```julia g3(x) = 1/(1+x) true_value = 1.9459101490553133051053527434432 w_i = 2:2:20 g_i = @.g3(w_i) plot(w_i, zeros(length(w_i)), fillrange=g_i, fillalpha = 0.35, label="") plot!(w_i, g_i, xticks=0:2:22, shape=:circle, markersize=2, label="", seriestype = :scatter) plot!(g3, xlim=[0, 22], ylim=[0.0, 0.4], title="Trapezoidal Rule", size=(450, 280), label=legend1, xlabel="x", ylabel="g(x)") |> display @manipulate for n in (2, 3, 6, 9, 18, 36, 90, 180) step = (20-2)/n w_i = 2:step:20 g_i = @.g3(w_i) g_i2 = [g_i[j] for j in 2:n+1] res3 = (step/2)*sum(g_i[1:n] .+ g_i2) err3 = res3 - true_value plot(w_i, zeros(length(w_i)), fillrange=g_i, fillalpha = 0.35, label="area=$(res3)\n err=$(err3) ") plot!(w_i, g_i, xticks=0:2:22, shape=:circle, markersize=2, label="", seriestype = :scatter) plot!(g3, xlim=[0, 22], ylim=[0.0, 0.4], title="Trapezoidal Rule", label="", xlabel="x", ylabel="g(x)") end ``` ![](https://i.imgur.com/voMCEFC.png) :::warning 經過計算,我們可以發現矩形加總計算出來的積分數值,其誤差為梯形法的一半! ::: 既然我們觀察到上面的現象,或許我們把這些方法**加總起來**就可以弭平誤差了。事實上,這個想法便是[辛普森法](https://en.wikipedia.org/wiki/Simpson%27s_rule)背後的邏輯。 ### 辛普森法(Simpson's Rule) $$ \begin{aligned} I = \int_a^b f(x) dx \approx \frac{b-a}{6n} \sum_{i=1}^n \left[ f(\xi_i) + 4f\left(\frac{\xi_i + \xi_{i+1}}{2}\right) + f(\xi_{i+1}) \right]. \end{aligned} $$ 它利用了三個點: $\xi_i$、$(\xi_i + \xi_{i+1})/2$ 與 $\xi_{i+1}$,權重則為 $\frac{b-a}{6n}$、$\frac{2(b-a)}{3n}$ 與 $\frac{b-a}{6n}$。有趣的是,辛普森法的採用的 $n$ 個節點 $I^{S(n)}$ 是梯形法($T$)與中點法($M$)的**加權平均**(因兩者均使用 $\frac{n}{2}$ 個節點),即: $$ \begin{aligned} I^{S(n)} = \frac{1}{3}I^{T\left(\frac{n}{2}\right)} + \frac{2}{3}I^{M\left(\frac{n}{2}\right)}. \end{aligned} $$ ```julia using Interact, WebIO, StatsPlots # Simpson's Rule g4(x) = 1/(1+x) true_value = 1.9459101490553133051053527434432 @manipulate for n in (2, 3, 6, 9, 18, 36, 90, 180) # number of segments a, b= 2, 20 step = (b-a)/n # length of one segment x = [a:step:b;] # points that divide the segments width = [(b-a)/(6n), 2(b-a)/(3n), (b-a)/(6n)] # coefficients of the 3 points = width of bars ω = repeat(width, outer=n) # width of bars, spread to all segments start = repeat(x[1:n], inner=3) # start of each segment (each seg has 3 bars); don't need the last elem of x bar_dist = repeat([0.5*width[1], width[1]+0.5*width[2], width[1]+width[2]+0.5*width[3]], outer=n) # mid of bar measured from the beginning of segment bar_mid = start .+ bar_dist # mid of each bar, for plotting purposes ξ_dist = repeat([0, width[1]+0.5*width[2], width[1]+width[2]+width[3]], outer=n) # ξ measured from the beginning of segment ξ = start .+ ξ_dist # the ξ in the formula g_height = g4.(ξ) # the height of the bar res = sum(ω .* g4.(ξ)) # the area = solution err = res - true_value rule = collect(zip(ω, ξ)); @show ω @show ξ @show rule bar(bar_mid, g_height, xticks=1:1:21, bar_width=ω, fillalpha=0.35, label="area = $res\n err = $err") # bar plot plot!(g4, xlim=[0, 22], ylim=[0.0, 0.4], title="Simpson's Rule", label="", xlabel="x", ylabel="g(x)") # plot g4 scatter!(ξ, g4.(ξ), label=false) # highlight the dividing points end ``` ![](https://i.imgur.com/MPjvktp.png) ## 數值積分法(Quadrature Methods)與多項式積分法(Polynomial Integration Methods) 上述幾個直觀的方法被歸類為數值積分法:利用**權重**與**節點**進行函數積分的估計。當然,我們也可以利用其他的方法,目的正是為了將誤差變小,或是對於其他不同類型的函數進行積分近似。我們可以將上述的幾個方法進行小結,數值積分法的想法是將函數與權重函數進行相乘積,並透過權重與節點進行近似估計: $$ \begin{aligned} I = \int_a^b p(x)f(x) dx \approx \sum_{i=1}^n \omega_i f(\xi_i), \end{aligned} $$ 不過,必須注意到以下幾個要點: - $p(x)$ 與 $\omega_i$ 是兩個完全不同的東西,雖然都叫做權重,不過前者指涉的通常是一個正函數,而後者則是根據函數計算出的權重。 - $p(x)$ 事實上是被積函數(integrand)的一部分,不過不會出現在等式的右邊(RHS)。 - $\{\omega_i, \xi_i\}$, $i=1,\cdots,n$ 的這個性質與被積函數(integrand)**相互獨立**。 我們在此可以得到一個結論:數值積分法正是一個不斷尋找 $\{\omega_i, \xi_i\}$ 的方法,而不同取值的方式會對於其近似程度(誤差)與效率有很大的關係[^1]。針對不同的 $\{\omega_i, \xi_i\}$ 有各種取值的方法,而這些方法則是依照被積函數而有不同。而在實務上最常被使用的方法是**高斯積分法(Gauss Quadrature Methods)**。在上述的例子中,我們的上下界為 $[a, b ] = [2, 20]$,$p(x) = 1$,$f(x) = \frac{1}{1+x}$,$\omega_i = 2$,而$\xi_i$則是端點(endpoints)或是中點(midpoint)。 ### 高斯積分法 高斯積分法基本上是處理不同類型的積分,並對於給定不同的上下界有專門的處理方式。我們可以給定**節點**數,產生$\{\omega_i, \xi_i\}$, $i=1,\cdots,n$。而常見的高斯積分法有以下四種類型,可以透過下表整理呈現[^2]: | 積分法 | 值域 | $$p(x)$$ | 近似值 | | :- | :- | :-: | :-: | |Gauss-Legendre |$$[-1,1]$$ |$$1$$ |$$\int_{-1}^1 f(x) \approx \sum_{i=1}^n \omega_i f(\xi_i)$$ | |Gauss-Chebyshev |$$[-1,1]$$| $$(1-x^2)^{-\frac{1}{2}}$$ |$$\int_{-1}^1 p(x)f(x) \approx \sum_{i=1}^n \omega_i f(\xi_i)$$ | |Gauss-Hermite |$$(-\infty,\infty)$$| $$e^{-x^2} $$| $$\int_{-\infty}^\infty p(x)f(x) \approx \sum_{i=1}^n \omega_i f(\xi_i)$$ | |Gauss-Laguerre |$$[0,\infty)$$ |$$e^{-x}$$ |$$\int_0^\infty p(x)f(x) \approx \sum_{i=1}^n \omega_i f(\xi_i)$$ | 那麼,我們應該用何種積分法進行積分運算呢?事實上,根據上表中的值域我們不難看出,針對不同的上下界,我們就要使用不同的積分方法。我們以 Gauss-Legendre 積分法為例: - 給定節點數,根據 Legendre 多項式: $$ (n+1)P_{n+1}(\xi) = (2n+1)\xi P_n(\xi) - n P_{n-1}(\xi) $$ 透過遞迴(recursively)的方式,可得到 $\{\xi_i\}$。其中 $P_0(\xi)=1$, $P_1(\xi)=\xi$。 - 而權重 $\{\omega_i\}$ 的計算方式可以藉由下列式子得到: $$ \omega_{i}={\frac {2}{\left(1-\xi_{i}^{2}\right)\left[P'_{n}(\xi_{i})\right]^{2}}} $$ 在實務上有各種不同計算節點數與權重的方式,例如取得一個特定矩陣的特徵值(eigenvalues)與特徵向量(eigenvectors)。[^3] ### 積分變數變換 令 $x= \rho(t)$,則 $$ \begin{aligned} \int_a^b g(x) dx = \int_{\rho^{-1}(a)}^{\rho^{-1}(b)} g(\rho(t)) \rho'(t) dt, \end{aligned} $$ 其中的 $\rho^{-1}(t)$ 我們稱之為 Jacobian 項。我們簡單的來說明一下何謂 Jacobian項,以及其與梯度(gradient)及Hessian矩陣的差別。 - **梯度(Gradient)**:梯度本質上是方向導數,其是一個由函數 $f$ 從 $\mathbb{R}^n$ 映射到[^4] $\mathbb{R}$ 組成的向量。例如給定以下的 $f$: $$f(x,y) = x^2 + xy$$ 其梯度為 $$(2x+y, x)$$ - **Hessian 矩陣**:其為一個函數的二階偏導數構成的矩陣。同樣地給定$f$: $$f(x,y) = x^2 +xy$$ 則 Hessian 矩陣為 \begin{bmatrix} 2, 1 \\ 1, 0 \end{bmatrix} - **Jacobian 項**: 雅可比矩阵是函数的一阶偏导数以一定方式排列成的矩阵 Jacobian 項是由一個函數的一階偏導數以一定方式排列的矩陣。如 $$f(x,y) = (x^2 + xy, x^3 - 2xy)$$ 其 Jacobian 項為 \begin{bmatrix} 2x+y, x\\ 3x^2-2y, -2x \end{bmatrix} 以下給出有用的 Jacobian 項與轉換法[^2]: x 值域 | 轉換規則 | t 值域 | Jacobian 項| :---: | :---: | :--- | --- $$[a, b]$$ | $$x = \frac{a+b}{2} + \frac{b-a}{2}t$$ | $$[-1,1]$$ | $$\frac{b-a}{2}$$ $$[-\infty, \infty]$$ | $$x = \frac{t}{1-t^2}$$ | $$[-1,1]$$ | $$\frac{t^2 + 1}{(t^2 -1)^2}$$ $$[a, \infty]$$ | $$x = a + \frac{1+t}{1-t}$$ | $$[-1,1]$$ | $$\frac{2}{(t-1)^2}$$ $$[-\infty, b]$$ | $$x = b - \frac{1-t}{1+t}$$ | $$[-1,1]$$ | $$\frac{2}{(t+1)^2}$$ ### Gauss-Legendre Gauss-Legendre 積分法是最常見的數值積分方法,通常被積函數的形式都會是: $$ \begin{aligned} I = \int_a^b g(x) d x. \end{aligned} $$ 因此我們要將其上下界轉換為 $[-1, 1]$。回到上面的例子,我們如果要計算 $$ \begin{aligned} I = \int_a^b g(x) dx = \int^{20}_{2} \frac{1}{1+x} dx \end{aligned} $$ 首先,其上下界為 $[2, 20]$,參照上方的轉換規則,可以得到 $$ \begin{aligned} & x = \rho(t) = \frac{a+b}{2} + \frac{b-a}{2}t \end{aligned} $$ 要檢驗的方式就是將 $t$ 的上下界代入,如果 $t$ 代 $-1$,就會得到 $a$;如果 $t$ 代入 $1$,就會得到 $b$。接著我們要求出 Jacobian 項,作法就是對轉換規則取一次導函數,我們會得到 $$ \rho'(t) =\frac{\partial \rho(t)}{ \partial t} = \frac{b-a}{2} $$ 最後,只要將原本函數中的 $x$ 替換為轉換規則,再乘上 Jacobian 項即可。在 $\verb|Julia|$ 中,我們可以利用 `FastGaussQuadrature` 這個套件產生節點,接著再進行一些基本的設定就可以計算積分了。 $$ \begin{aligned} I = \int_a^b g(x) dx & = \int_{-1}^1 g\left( \frac{1}{2}[ (b+a) + (b-a)t ] \right) \frac{b-a}{2}dt. \end{aligned} $$ ```julia ## 引入套件 using FastGaussQuadrature ## 設定積分上下界 a = 2; b=20 ## 設定節點數 n=30 ## 設定被積函數 g(x) = 1/(1+x) ## 設定轉換規則與 Jacobian 項 trans_rule(t) = (a + b)/2 + t*(b-a)/2 jacobian = (b-a)/2 ## 重新設定積分形式 f(t) = g( trans_rule(t) ) * jacobian ## 產生 x 與節點並計算 xi, wi = gausslegendre(n) answer = sum(wi .* f.(xi)) ## 1.9459101490553121 ``` 我們也可以用另一個套件 `QuadGK`進行檢驗,看看我們剛剛的方法是否正確。[^5] ```julia using QuadGK quadgk(x-> 1/(1+x), 2, 20, rtol=1e-8) ## 1.9459101490553135 ``` 可以看到我們利用 Gauss-Legendre 積分的結果與實際值相差不大。 [^1]: 從上述的矩形加總法到中點法就可看出,當取節點越少時,其誤差便會稍稍的增加。 [^2]:由國立臺灣大學經濟學系[王泓仁](https://homepage.ntu.edu.tw/~wangh/)教授整理。 [^3]: *Mathematics of Computation*, vol. 35, no. 151, 1980, https://doi.org/10.1090/mcom/1980-35-151. [^4]: 數學上我們將**映射**到記作:$\mapsto$。 [^5]: `rtol` 是該套件中容忍相對誤差的指令。 <div class="likecoin-embed likecoin-button"> <div></div> <iframe scrolling="no" frameborder="0" src="https://button.like.co/in/embed/xiaolong70701/button?referrer=hackmd.io"></iframe> </div>