自然三次样条插值推导笔记

问题情境

假设我们有一组数据点(称为控制点):
x 0 , x 1 , x 2 , … , x n x_0, x_1, x_2, \ldots, x_n x0,x1,x2,,xn
这些点是已知的,表示我们要拟合的曲线在等距离参数点(比如参数取为0,1,2,…,n)下的函数值。

我们的目标是寻找一条具有足够平滑度的插值曲线,确保曲线在每一个参数节点 ( i ) ( i ) (i) 上能够满足 ( p ( i ) = x i ) ( p(i) = x_i ) (p(i)=xi)。为达到光滑且自然的效果,我们希望这条曲线是由每两个相邻数据点间的一条三次多项式所构成,这样的拼接曲线就是三次样条(Cubic Spline)

基本假设与记号约定

  1. 分段定义
    将整个区间分为 ( n ) ( n ) (n) 段,每一段曲线记为 ( p i ( s ) ) ( p_i(s) ) (pi(s)),其中 ( i = 0 , 1 , … , n − 1 ) ( i = 0, 1, \ldots, n-1 ) (i=0,1,,n1) 。为了简化,我们令每一段的参数区间为 ( [ 0 , 1 ] ) ([0,1]) ([0,1]),即对第 ( i ) ( i ) (i) 段来说,当 ( s = 0 ) ( s=0 ) (s=0) 时对应全局参数点 ( i ) ( i ) (i),当 ( s = 1 ) ( s=1 ) (s=1) 时对应全局参数点 ( i + 1 ) ( i+1 ) (i+1)。这样,每段的长度在参数空间中都规一化为1。

    因此,第 ( i ) ( i ) (i) 段曲线满足:
    p i ( 0 ) = x i , p i ( 1 ) = x i + 1 . p_i(0) = x_i,\quad p_i(1) = x_{i+1}. pi(0)=xi,pi(1)=xi+1.

  2. 多项式形式
    每一段的曲线使用三次多项式表示:
    p i ( s ) = a i + b i s + c i s 2 + d i s 3 . p_i(s) = a_i + b_i s + c_i s^2 + d_i s^3. pi(s)=ai+bis+cis2+dis3.
    在这里, ( a i , b i , c i , d i ) ( a_i, b_i, c_i, d_i ) (ai,bi,ci,di) 是该段曲线的待定系数。

    因为我们有 ( n ) ( n ) (n) 段曲线,每段有4个系数,总共4n个未知数。如果我们只有节点值的条件(即 ( p i ( 0 ) = x i , p i ( 1 ) = x i + 1 ) ( p_i(0) = x_i, p_i(1) = x_{i+1} ) (pi(0)=xi,pi(1)=xi+1)),我们将获得每段2个方程(端点值匹配),这样仅有 ( 2 n ) ( 2n ) (2n) 个方程。显然方程数不足以唯一确定 ( 4 n ) ( 4n ) (4n) 个参数。

    因此,我们需要更多条件来限制解的形式。

  3. 光滑性条件 (C²连续)
    为了让整条样条曲线在节点处足够光滑,我们要求不仅是函数值连续,还需要一阶导数、一阶导数的连续性,以及二阶导数的连续性。

    具体来说,在相邻两段 ( p i − 1 ( s ) ) ( p_{i-1}(s) ) (pi1(s)) ( p i ( s ) ) ( p_i(s) ) (pi(s)) 的连接点(即 ( s = 1 ) ( s=1 ) (s=1) 对应 ( p i − 1 ) ( p_{i-1} ) (pi1) 的末端与 ( s = 0 ) ( s=0 ) (s=0) 对应 ( p i ) ( p_i ) (pi) 的开端)上,我们要求:
    p i − 1 ( 1 ) = p i ( 0 ) ( 函数值连续 ) p_{i-1}(1) = p_i(0) \quad(\text{函数值连续}) pi1(1)=pi(0)(函数值连续)
    p i − 1 ′ ( 1 ) = p i ′ ( 0 ) ( 一阶导数连续 ) p_{i-1}'(1) = p_i'(0) \quad(\text{一阶导数连续}) pi1(1)=pi(0)(一阶导数连续)
    p i − 1 ′ ′ ( 1 ) = p i ′ ′ ( 0 ) ( 二阶导数连续 ) p_{i-1}''(1) = p_i''(0) \quad(\text{二阶导数连续}) pi1′′(1)=pi′′(0)(二阶导数连续)

    这样,每相邻两个段落在连接点处多出来2个连续性条件(因为函数值的连续性其实已经通过端点值匹配实现了)。

    总结一下,对于 ( i = 1 , 2 , … , n − 1 ) ( i = 1,2,\ldots,n-1 ) (i=1,2,,n1) 的中间节点(注意最左和最右节点暂不包含),我们有:

    • 函数值连续条件: ( p i − 1 ( 1 ) = p i ( 0 ) ) ( p_{i-1}(1)=p_i(0) ) (pi1(1)=pi(0)) 已满足,因为 ( p i − 1 ( 1 ) = x i ) ( p_{i-1}(1)=x_i ) (pi1(1)=xi) ( p i ( 0 ) = a i = x i ) ( p_i(0)=a_i=x_i ) (pi(0)=ai=xi)
    • 导数连续条件: ( p i − 1 ′ ( 1 ) = p i ′ ( 0 ) ) ( p_{i-1}'(1)=p_i'(0) ) (pi1(1)=pi(0))
    • 二阶导数连续条件: ( p i − 1 ′ ′ ( 1 ) = p i ′ ′ ( 0 ) ) ( p_{i-1}''(1)=p_i''(0) ) (pi1′′(1)=pi′′(0))

    这样对每个中间节点提供了2个新方程。因为我们有 ( n − 1 ) ( n-1 ) (n1) 个中间节点,所以又得到 ( 2 ( n − 1 ) ) ( 2(n-1) ) (2(n1)) 个方程。

    目前已有方程数量:

    • 端点值匹配:每段2个方程,共 ( 2 n ) ( 2n ) (2n)
    • 中间节点的一、二阶导数连续性:每个中间节点2个方程,共 ( 2 ( n − 1 ) ) ( 2(n-1) ) (2(n1))

    合计现在有 ( 2 n + 2 ( n − 1 ) = 2 n + 2 n − 2 = 4 n − 2 ) ( 2n + 2(n-1) = 2n + 2n - 2 = 4n - 2 ) (2n+2(n1)=2n+2n2=4n2) 个方程,但我们有 ( 4 n ) ( 4n ) (4n) 个未知数。还差2个方程。

  4. 自然边界条件 (Natural Spline)
    为了得到一个具有良好特性的解,最常用的选择是在最左端和最右端的二阶导数设为0,即:
    p 0 ′ ′ ( 0 ) = 0 , p n − 1 ′ ′ ( 1 ) = 0. p_0''(0)=0,\quad p_{n-1}''(1)=0. p0′′(0)=0,pn1′′(1)=0.

    这给出了最后2个方程,从而方程组达成闭合,可以求解。

  5. 将方程整合
    到目前为止,我们已经有足够的方程来求出所有 ( a i , b i , c i , d i ) ( a_i, b_i, c_i, d_i ) (ai,bi,ci,di)。但这样直接求解会比较复杂。为了简化过程,人们通常会利用一个更有结构的方式来描述未知量。

    请注意:
    p i ( 0 ) = a i = x i p_i(0)=a_i=x_i pi(0)=ai=xi
    所以 ( a i ) ( a_i ) (ai) 是已知的(与 ( x i ) ( x_i ) (xi) 相同)。这使得每段的首个系数 ( a i ) ( a_i ) (ai) 不需要再未知,因为 ( x i ) ( x_i ) (xi) 已经是给定数据。所以在求解时不用特别关注 ( a i ) ( a_i ) (ai)

    考虑导数条件:
    一阶导数为 ( p i ′ ( s ) = b i + 2 c i s + 3 d i s 2 ) ( p_i'(s)=b_i+2c_i s + 3 d_i s^2 ) (pi(s)=bi+2cis+3dis2)。在 ( s = 0 ) ( s=0 ) (s=0) 点上:
    p i ′ ( 0 ) = b i p_i'(0)=b_i pi(0)=bi
    因此 ( b i ) ( b_i ) (bi) 可以被诠释为该段曲线在左端点的导数值。

    如果我们定义每个节点处的导数为 ( D i = p i ′ ( 0 ) ) ( D_i = p_i'(0) ) (Di=pi(0)) (这里需要稍加思考:对于第 ( i ) ( i ) (i) 段, ( p i ′ ( 0 ) ) ( p_i'(0) ) (pi(0)) 是该段在左端的导数,也就是在全局节点 ( i ) ( i ) (i) 处的导数),那么:
    D i = p i ′ ( 0 ) = b i . D_i = p_i'(0) = b_i. Di=pi(0)=bi.

    这样,节点处的一阶导数就成为我们想要求解的关键未知量。因为如果我们能求出每个节点 ( i ) ( i ) (i) 的导数 ( D i ) ( D_i ) (Di),那么一旦 ( b i = D i ) ( b_i = D_i ) (bi=Di) 已知,我们就能通过其它条件把 ( c i , d i ) ( c_i, d_i ) (ci,di) 求出来。

    同理:
    p i ′ ′ ( s ) = 2 c i + 6 d i s . p_i''(s)=2c_i + 6d_i s. pi′′(s)=2ci+6dis.
    ( s = 0 ) ( s=0 ) (s=0) 处:
    p i ′ ′ ( 0 ) = 2 c i . p_i''(0)=2c_i. pi′′(0)=2ci.

    边界条件 ( p 0 ′ ′ ( 0 ) = 0 ) ( p_0''(0)=0 ) (p0′′(0)=0) 意味着 ( 2 c 0 = 0 ) ( 2c_0=0 ) (2c0=0) ( c 0 = 0 ) ( c_0=0 ) (c0=0)。同样地, ( p n − 1 ′ ′ ( 1 ) = 2 c n − 1 + 6 d n − 1 = 0 ) ( p_{n-1}''(1)=2c_{n-1}+6d_{n-1}=0 ) (pn1′′(1)=2cn1+6dn1=0) 将在稍后进入方程组中。

  6. 利用端点值条件解出 ( c i , d i ) ( c_i,d_i ) (ci,di)
    我们已经知道:
    a i = x i , b i = D i . a_i=x_i, \quad b_i=D_i. ai=xi,bi=Di.
    还需要表达 ( c i , d i ) ( c_i, d_i ) (ci,di)

    利用另一端点 ( p i ( 1 ) = x i + 1 ) ( p_i(1)=x_{i+1} ) (pi(1)=xi+1):
    p i ( 1 ) = a i + b i + c i + d i = x i + D i + c i + d i = x i + 1 . p_i(1)=a_i+b_i+c_i+d_i = x_i+D_i+c_i+d_i = x_{i+1}. pi(1)=ai+bi+ci+di=xi+Di+ci+di=xi+1.
    故:
    c i + d i = x i + 1 − x i − D i . ( 1 ) c_i + d_i = x_{i+1}-x_i - D_i. \quad(1) ci+di=xi+1xiDi.(1)

    再利用一阶导数在 ( s = 1 ) ( s=1 ) (s=1) 处:
    p i ′ ( 1 ) = b i + 2 c i + 3 d i = D i + 2 c i + 3 d i . p_i'(1)=b_i+2c_i+3d_i = D_i + 2c_i + 3d_i. pi(1)=bi+2ci+3di=Di+2ci+3di.

    我们需要与下一个区间的导数连续条件挂钩。根据二阶连续性,我们有 ( p i ′ ′ ( 0 ) = p i − 1 ′ ′ ( 1 ) ) ( p_i''(0)=p_{i-1}''(1) ) (pi′′(0)=pi1′′(1)) 等条件,但这里更常见的做法是先回到最经典的三次样条推导,在该推导中,人们得出如下结论(这是很多教科书推导的标准结果):

    最终,每一段的 ( c i , d i ) ( c_i, d_i ) (ci,di) 可通过已知的 ( x i , x i + 1 , D i , D i + 1 ) ( x_i, x_{i+1}, D_i, D_{i+1} ) (xi,xi+1,Di,Di+1) 表达为:
    c i = 3 ( x i + 1 − x i ) − 2 D i − D i + 1 c_i = 3(x_{i+1}-x_i) - 2D_i - D_{i+1} ci=3(xi+1xi)2DiDi+1
    d i = 2 ( x i − x i + 1 ) + D i + D i + 1 . d_i = 2(x_i - x_{i+1}) + D_i + D_{i+1}. di=2(xixi+1)+Di+Di+1.

    这对公式是从求解导数连续和二阶导数连续条件得到的。为清晰起见,让我们来回溯一下这些条件是如何联立得到这两个式子的:

    • 已知 ( p i ( 0 ) = x i ) ( p_i(0)=x_i ) (pi(0)=xi) ( p i ( 1 ) = x i + 1 ) ( p_i(1)=x_{i+1} ) (pi(1)=xi+1)

    • ( s = 0 ) ( s=0 ) (s=0) 处: ( p i ′ ( 0 ) = D i ) ( p_i'(0)=D_i ) (pi(0)=Di)

    • ( s = 1 ) ( s=1 ) (s=1) 处:我们还希望 ( p i ′ ( 1 ) ) ( p_i'(1) ) (pi(1)) 与下一段或上一段满足连续性条件。例如,根据定义,如果曲线在节点 ( i + 1 ) ( i+1 ) (i+1) 处光滑连接,那么:
      p i ′ ( 1 ) = p i + 1 ′ ( 0 ) = D i + 1 . p_i'(1)=p_{i+1}'(0)=D_{i+1}. pi(1)=pi+1(0)=Di+1.

      因此, ( p i ′ ( 1 ) = D i + 1 ) ( p_i'(1)=D_{i+1} ) (pi(1)=Di+1)

    ( p i ′ ( 1 ) = D i + 1 ) ( p_i'(1)=D_{i+1} ) (pi(1)=Di+1) 表达:
    D i + 1 = D i + 2 c i + 3 d i . ( 2 ) D_{i+1} = D_i + 2c_i + 3d_i. \quad(2) Di+1=Di+2ci+3di.(2)

    现在我们有两个方程(1)(2):
    从(1):
    c i + d i = x i + 1 − x i − D i . c_i+d_i = x_{i+1}-x_i - D_i. ci+di=xi+1xiDi.
    从(2):
    2 c i + 3 d i = D i + 1 − D i . 2c_i+3d_i = D_{i+1}-D_i. 2ci+3di=Di+1Di.

    利用这两个方程联立求解 ( c i ) ( c_i ) (ci) ( d i ) ( d_i ) (di)

    • 用(1)式可以表示 ( c i = x i + 1 − x i − D i − d i ) ( c_i = x_{i+1}-x_i - D_i - d_i ) (ci=xi+1xiDidi)
    • ( c i ) ( c_i ) (ci) 代入(2):
      2 ( x i + 1 − x i − D i − d i ) + 3 d i = D i + 1 − D i . 2(x_{i+1}-x_i - D_i - d_i) + 3d_i = D_{i+1}-D_i. 2(xi+1xiDidi)+3di=Di+1Di.
      2 x i + 1 − 2 x i − 2 D i − 2 d i + 3 d i = D i + 1 − D i . 2x_{i+1}-2x_i -2D_i -2d_i +3d_i = D_{i+1}-D_i. 2xi+12xi2Di2di+3di=Di+1Di.
      2 x i + 1 − 2 x i − 2 D i + d i = D i + 1 − D i . 2x_{i+1}-2x_i -2D_i + d_i = D_{i+1}-D_i. 2xi+12xi2Di+di=Di+1Di.

    整理:
    d i = D i + 1 − D i − 2 x i + 1 + 2 x i + 2 D i = − 2 ( x i + 1 − x i ) + ( D i + D i + 1 ) . d_i = D_{i+1}-D_i -2x_{i+1} +2x_i +2D_i = -2(x_{i+1}-x_i) + (D_i+D_{i+1}). di=Di+1Di2xi+1+2xi+2Di=2(xi+1xi)+(Di+Di+1).

    于是:
    d i = 2 ( x i − x i + 1 ) + D i + D i + 1 . d_i = 2(x_i - x_{i+1}) + D_i + D_{i+1}. di=2(xixi+1)+Di+Di+1.

    再由(1)式求 ( c i ) ( c_i ) (ci):
    c i = x i + 1 − x i − D i − d i = x i + 1 − x i − D i − [ 2 ( x i − x i + 1 ) + D i + D i + 1 ] c_i = x_{i+1}-x_i - D_i - d_i = x_{i+1}-x_i - D_i - [2(x_i - x_{i+1}) + D_i + D_{i+1}] ci=xi+1xiDidi=xi+1xiDi[2(xixi+1)+Di+Di+1]
    c i = x i + 1 − x i − D i − 2 ( x i − x i + 1 ) − D i − D i + 1 = x i + 1 − x i − 2 ( x i − x i + 1 ) − 2 D i − D i + 1 c_i = x_{i+1}-x_i - D_i - 2(x_i - x_{i+1}) - D_i - D_{i+1} = x_{i+1}-x_i - 2(x_i - x_{i+1}) - 2D_i - D_{i+1} ci=xi+1xiDi2(xixi+1)DiDi+1=xi+1xi2(xixi+1)2DiDi+1
    c i = 3 ( x i + 1 − x i ) − 2 D i − D i + 1 . c_i = 3(x_{i+1}-x_i) -2D_i - D_{i+1}. ci=3(xi+1xi)2DiDi+1.

    这样就得到了常见的结果。

  7. 形成三对角方程组
    接下来关键的问题是如何求 ( D i ) ( D_i ) (Di) 。我们知道 ( D 0 = 0 ) ( D_0 = 0 ) (D0=0) ( D n = 0 ) ( D_n = 0 ) (Dn=0)(自然边界条件下),那么剩下 ( D 1 , D 2 , … , D n − 1 ) ( D_1, D_2, \ldots, D_{n-1} ) (D1,D2,,Dn1) 就是需要解的未知量。

    每一段使用 ( c i , d i ) ( c_i, d_i ) (ci,di) 的表达式后,将二阶导数连续条件写出来,就会发现这些条件可以转化为关于 ( D 1 , … , D n − 1 ) ( D_1, \ldots, D_{n-1} ) (D1,,Dn1) 的线性方程。经过详细的线性代数推导(标准参考文献或教科书中有非常清晰的步骤),会发现形成如下的方程组(以最经典的自然样条为例):

    [ 4 1 0 ⋯ 0 1 4 1 ⋯ 0 0 1 4 ⋯ 0 ⋮ ⋮ ⋮ ⋱ 1 0 0 0 1 4 ] [ D 1 D 2 D 3 ⋮ D n − 1 ] \begin{bmatrix} 4 & 1 & 0 & \cdots & 0 \\ 1 & 4 & 1 & \cdots & 0 \\ 0 & 1 & 4 & \cdots & 0 \\ \vdots & \vdots & \vdots & \ddots & 1 \\ 0 & 0 & 0 & 1 & 4 \end{bmatrix} \begin{bmatrix} D_1 \\ D_2 \\ D_3 \\ \vdots \\ D_{n-1} \end{bmatrix} 410014100140100014 D1D2D3Dn1
    = = =
    [ 3 ( x 2 − x 0 ) 3 ( x 3 − x 1 ) 3 ( x 4 − x 2 ) ⋮ 3 ( x n − x n − 2 ) ] . \begin{bmatrix} 3(x_2 - x_0) \\ 3(x_3 - x_1) \\ 3(x_4 - x_2) \\ \vdots \\ 3(x_n - x_{n-2}) \end{bmatrix}. 3(x2x0)3(x3x1)3(x4x2)3(xnxn2) .

    为什么是这样一个矩阵和右端向量?

    • 该矩阵的构造和右端向量的形式来源于将所有节点的一阶、二阶导数连续条件系统化之后得到的结果。
    • 矩阵是一个对称的三对角矩阵,其结构(如4和1的位置)来源于二阶导数连续性约束的通用形式。
    • 右边的 ( 3 ( x i + 1 − x i − 1 ) ) ( 3(x_{i+1}-x_{i-1}) ) (3(xi+1xi1)) 的形式是对节点值差分的线性组合。

    最终,通过求解这个简单且结构稀疏的线性方程组,我们可以快速得到所有 ( D i ) ( D_i ) (Di)

  8. 总结和完整线路

    • 从定义出分段三次多项式开始,我们总共有 ( 4 n ) (4n) (4n) 个未知系数。
    • 节点插值条件给出 ( n + 1 ) ( n+1 ) (n+1) 个已知函数值,对 ( n ) ( n ) (n) 段曲线有 ( 2 n ) (2n) (2n) 个方程(每段两端点的函数值匹配)。
    • 为确保平滑性,需要一阶、二阶导数在内部 ( n − 1 ) ( n-1 ) (n1) 个节点处连续,又加入 ( 2 ( n − 1 ) ) (2(n-1)) (2(n1)) 个方程。
    • 此时共获得 ( 2 n + 2 ( n − 1 ) = 4 n − 2 ) ( 2n + 2(n-1) = 4n-2 ) (2n+2(n1)=4n2) 个方程。还差2个方程,是两端二阶导数为零的自然边界条件,凑齐 ( 4 n ) (4n) (4n) 个方程。
    • 利用 ( a i = x i ) ( a_i=x_i ) (ai=xi) 已知和 ( D i = b i ) ( D_i=b_i ) (Di=bi) 重参数化后,将所有问题归结为求解 ( D 1 , … , D n − 1 ) (D_1,\ldots,D_{n-1}) (D1,,Dn1) 的一个三对角线性方程组。
    • 解出 ( D i ) ( D_i ) (Di) 后,将其代回 ( c i , d i ) ( c_i, d_i ) (ci,di) 的表达式,最终得到所有 ( p i ( s ) ) ( p_i(s) ) (pi(s)) 的明确公式。

这就是从最初的插值要求、光滑性条件,到最终形成三对角线性方程组,进而求解出样条曲线系数的完整推导过程。上述任何一步如果仍不清晰,可以针对具体方程的形成进行更微观的代入和验证。

将问题转化为节点处导数 ( D i ) (D_i) (Di)

为更方便求解,将未知参数的集合从 ( { a i , b i , c i , d i } ) (\{a_i,b_i,c_i,d_i\}) ({ai,bi,ci,di}) 转换到每个节点导数值 ( { D 0 , D 1 , … , D n } ) (\{D_0,D_1,\ldots,D_n\}) ({D0,D1,,Dn}) 上。定义:
D i : = p i ′ ( 0 ) , D_i := p_i'(0), Di:=pi(0),
表示样条在节点 ( i ) ( i ) (i) 处的一阶导数。其中,对于最左侧段 ( p 0 ( s ) ) ( p_0(s) ) (p0(s)) ( p 0 ′ ( 0 ) = D 0 ) ( p_0'(0)=D_0 ) (p0(0)=D0);对于中间段 ( p i ( s ) ) ( p_i(s) ) (pi(s)),因为 ( p i ( 0 ) ) ( p_i(0) ) (pi(0)) 对应全局节点 ( i ) ( i ) (i),所以 ( D i ) ( D_i ) (Di) 就是样条在节点 ( i ) ( i ) (i) 处的导数。

注意:自然边界条件给出 ( p 0 ′ ′ ( 0 ) = 0 ) ( p_0''(0)=0 ) (p0′′(0)=0) ( p n − 1 ′ ′ ( 1 ) = 0 ) ( p_{n-1}''(1)=0 ) (pn1′′(1)=0),但稍后我们会用到 ( D 0 , D n ) ( D_0, D_n ) (D0,Dn) 作为已知条件,即 ( D 0 = 0 ) ( D_0 = 0 ) (D0=0) ( D n = 0 ) ( D_n=0 ) (Dn=0) 通常不能直接设为0(因为 ( D i ) ( D_i ) (Di) 是一阶导数值,不是二阶导数!),不过对于自然样条,通过求解最终会得到 ( D 0 = 0 , D n = 0 ) ( D_0=0, D_n=0 ) (D0=0,Dn=0) 的结果。标准自然样条中,确实可以证明 ( D 0 = 0 , D n = 0 ) ( D_0=0, D_n=0 ) (D0=0,Dn=0)。(这一结果可通过对称性和二阶导数边界条件导出,这里先接受为事实。)

有了 ( D i ) ( D_i ) (Di),每一段多项式的系数可以写成:
a i = x i , a_i = x_i, ai=xi,
b i = D i , b_i = D_i, bi=Di,
c i = 3 ( x i + 1 − x i ) − 2 D i − D i + 1 , c_i = 3(x_{i+1}-x_i)-2D_i-D_{i+1}, ci=3(xi+1xi)2DiDi+1,
d i = 2 ( x i − x i + 1 ) + D i + D i + 1 . d_i = 2(x_i - x_{i+1}) + D_i + D_{i+1}. di=2(xixi+1)+Di+Di+1.

这些公式是标准三次样条插值的结果,来源于将段内插值条件与导数连续条件联立求解后得到。

如何导出三对角方程组

关键在于把二阶导数连续性条件转化为关于 ( D i ) ( D_i ) (Di) 的线性方程。在节点 ( i ) ( i ) (i) 处的二阶导数连续意味着:
p i − 1 ′ ′ ( 1 ) = p i ′ ′ ( 0 ) . p_{i-1}''(1) = p_i''(0). pi1′′(1)=pi′′(0).

回顾二阶导数:
p i ′ ′ ( s ) = 2 c i + 6 d i s . p_i''(s) = 2c_i + 6d_i s. pi′′(s)=2ci+6dis.
因而:
p i ′ ′ ( 0 ) = 2 c i , p i ′ ′ ( 1 ) = 2 c i + 6 d i . p_i''(0)=2c_i,\quad p_i''(1)=2c_i + 6d_i. pi′′(0)=2ci,pi′′(1)=2ci+6di.

为了表达清楚,让我们写出在节点 ( i ) ( i ) (i) 的条件,即对于 ( 1 ≤ i ≤ n − 1 ) ( 1 \leq i \leq n-1 ) (1in1):
p i − 1 ′ ′ ( 1 ) = p i ′ ′ ( 0 ) . p_{i-1}''(1) = p_i''(0). pi1′′(1)=pi′′(0).

代入公式:
2 c i − 1 + 6 d i − 1 = 2 c i . 2c_{i-1} + 6d_{i-1} = 2 c_i. 2ci1+6di1=2ci.

现在将 ( c i − 1 , d i − 1 , c i ) ( c_{i-1}, d_{i-1}, c_i ) (ci1,di1,ci) ( D i − 1 , D i , D i + 1 , x i ) ( D_{i-1}, D_i, D_{i+1}, x_i ) (Di1,Di,Di+1,xi) 来表示。

  • 对于段 ( i − 1 ) ( i-1 ) (i1) 来说(区间 ( [ i − 1 , i ] ) ([i-1,i]) ([i1,i])):
    c i − 1 = 3 ( x i − x i − 1 ) − 2 D i − 1 − D i , c_{i-1} = 3(x_i - x_{i-1}) - 2D_{i-1} - D_i, ci1=3(xixi1)2Di1Di,
    d i − 1 = 2 ( x i − 1 − x i ) + D i − 1 + D i . d_{i-1} = 2(x_{i-1}-x_i) + D_{i-1} + D_i. di1=2(xi1xi)+Di1+Di.

  • 对于段 ( i ) ( i ) (i) 来说(区间 ( [ i , i + 1 ] ) ([i,i+1]) ([i,i+1])):
    c i = 3 ( x i + 1 − x i ) − 2 D i − D i + 1 . c_i = 3(x_{i+1}-x_i) - 2D_i - D_{i+1}. ci=3(xi+1xi)2DiDi+1.

将它们代入二阶导数连续条件:
2 [ 3 ( x i − x i − 1 ) − 2 D i − 1 − D i ] + 6 [ 2 ( x i − 1 − x i ) + D i − 1 + D i ] = 2 [ 3 ( x i + 1 − x i ) − 2 D i − D i + 1 ] . 2[3(x_i - x_{i-1}) - 2D_{i-1} - D_i] + 6[2(x_{i-1}-x_i) + D_{i-1} + D_i] = 2[3(x_{i+1}-x_i) - 2D_i - D_{i+1}]. 2[3(xixi1)2Di1Di]+6[2(xi1xi)+Di1+Di]=2[3(xi+1xi)2DiDi+1].

现在展开和整理这条式子(这一步很重要,会有点繁琐,但它将简化为一个关于 ( D i − 1 , D i , D i + 1 ) ( D_{i-1},D_i,D_{i+1} ) (Di1,Di,Di+1) 的简单线性关系):

左侧展开:

  1. ( 2 c i − 1 ) (2c_{i-1}) (2ci1):
    2 c i − 1 = 2 [ 3 ( x i − x i − 1 ) − 2 D i − 1 − D i ] = 6 ( x i − x i − 1 ) − 4 D i − 1 − 2 D i . 2c_{i-1} = 2[3(x_i - x_{i-1}) - 2D_{i-1}-D_i] =6(x_i - x_{i-1}) -4D_{i-1} -2D_i. 2ci1=2[3(xixi1)2Di1Di]=6(xixi1)4Di12Di.

  2. ( 6 d i − 1 ) (6d_{i-1}) (6di1):
    6 d i − 1 = 6 [ 2 ( x i − 1 − x i ) + D i − 1 + D i ] = 12 ( x i − 1 − x i ) + 6 D i − 1 + 6 D i . 6d_{i-1} = 6[2(x_{i-1}-x_i) + D_{i-1}+D_i] = 12(x_{i-1}-x_i) +6D_{i-1}+6D_i. 6di1=6[2(xi1xi)+Di1+Di]=12(xi1xi)+6Di1+6Di.

左侧相加:
2 c i − 1 + 6 d i − 1 = [ 6 ( x i − x i − 1 ) + 12 ( x i − 1 − x i ) ] + ( − 4 D i − 1 + 6 D i − 1 ) + ( − 2 D i + 6 D i ) . 2c_{i-1}+6d_{i-1} = [6(x_i - x_{i-1}) + 12(x_{i-1}-x_i)] + (-4D_{i-1}+6D_{i-1}) + (-2D_i+6D_i). 2ci1+6di1=[6(xixi1)+12(xi1xi)]+(4Di1+6Di1)+(2Di+6Di).

注意 ( x i − x i − 1 ) ( x_i - x_{i-1} ) (xixi1) ( x i − 1 − x i ) ( x_{i-1}-x_i ) (xi1xi) 相加:
6 ( x i − x i − 1 ) + 12 ( x i − 1 − x i ) = 6 ( x i − x i − 1 ) − 12 ( x i − x i − 1 ) = − 6 ( x i − x i − 1 ) . 6(x_i - x_{i-1}) + 12(x_{i-1}-x_i) = 6(x_i - x_{i-1}) -12(x_i - x_{i-1}) = -6(x_i - x_{i-1}). 6(xixi1)+12(xi1xi)=6(xixi1)12(xixi1)=6(xixi1).

导数项合并:
( − 4 D i − 1 + 6 D i − 1 ) = 2 D i − 1 , ( − 2 D i + 6 D i ) = 4 D i . (-4D_{i-1}+6D_{i-1})=2D_{i-1}, \quad (-2D_i+6D_i)=4D_i. (4Di1+6Di1)=2Di1,(2Di+6Di)=4Di.

所以左侧最终变为:
2 c i − 1 + 6 d i − 1 = − 6 ( x i − x i − 1 ) + 2 D i − 1 + 4 D i . 2c_{i-1}+6d_{i-1} = -6(x_i - x_{i-1}) + 2D_{i-1} +4D_i. 2ci1+6di1=6(xixi1)+2Di1+4Di.

右侧是 ( 2 c i ) ( 2c_i ) (2ci):
2 c i = 2 [ 3 ( x i + 1 − x i ) − 2 D i − D i + 1 ] = 6 ( x i + 1 − x i ) − 4 D i − 2 D i + 1 . 2c_i = 2[3(x_{i+1}-x_i)-2D_i -D_{i+1}] =6(x_{i+1}-x_i) -4D_i -2D_{i+1}. 2ci=2[3(xi+1xi)2DiDi+1]=6(xi+1xi)4Di2Di+1.

将等式写全:
− 6 ( x i − x i − 1 ) + 2 D i − 1 + 4 D i = 6 ( x i + 1 − x i ) − 4 D i − 2 D i + 1 . -6(x_i - x_{i-1}) + 2D_{i-1} +4D_i = 6(x_{i+1}-x_i) -4D_i -2D_{i+1}. 6(xixi1)+2Di1+4Di=6(xi+1xi)4Di2Di+1.

移项使 ( D i − 1 , D i , D i + 1 ) ( D_{i-1},D_i,D_{i+1} ) (Di1,Di,Di+1) 集中在一侧,常量项(与 ( x i ) ( x_i ) (xi) 有关的)放在另一侧:

2 D i − 1 + 4 D i + 2 D i + 1 = 6 ( x i + 1 − x i ) + 6 ( x i − x i − 1 ) 2D_{i-1} +4D_i +2D_{i+1} = 6(x_{i+1}-x_i) +6(x_i - x_{i-1}) 2Di1+4Di+2Di+1=6(xi+1xi)+6(xixi1)
我这里将 ( − 4 D i ) ( -4D_i ) (4Di) ( − 2 D i + 1 ) ( -2D_{i+1} ) (2Di+1) 移到左边时要变号,所以应为:
2 D i − 1 + 4 D i + 2 D i + 1 = 6 ( x i + 1 − x i ) + 6 ( x i − x i − 1 ) . 2D_{i-1} +4D_i +2D_{i+1} = 6(x_{i+1}-x_i) +6(x_i - x_{i-1}). 2Di1+4Di+2Di+1=6(xi+1xi)+6(xixi1).

还可以再简化,每一项都可以除以2:
D i − 1 + 2 D i + D i + 1 = 3 ( x i + 1 − x i − 1 ) . D_{i-1} +2D_i +D_{i+1} = 3(x_{i+1}-x_{i-1}). Di1+2Di+Di+1=3(xi+1xi1).

这就是关键的关系式!

现在注意,我们有 ( i = 1 , … , n − 1 ) ( i=1,\ldots,n-1 ) (i=1,,n1) 对这样的方程(每个内部节点产生一个方程)。并且边界条件 ( D 0 = 0 ) ( D_0=0 ) (D0=0) ( D n = 0 ) ( D_n=0 ) (Dn=0) (对于自然样条,这是最终将使用的条件)会将这组方程转化为一个尺寸为 ( ( n − 1 ) × ( n − 1 ) ) ((n-1)\times(n-1)) ((n1)×(n1)) 的线性系统。

( i = 1 ) ( i = 1 ) (i=1) 来说,有:
D 0 + 2 D 1 + D 2 = 3 ( x 2 − x 0 ) . D_0 + 2D_1 + D_2 = 3(x_2 - x_0). D0+2D1+D2=3(x2x0).
因为 ( D 0 = 0 ) ( D_0=0 ) (D0=0)(自然边界对最终 ( D 0 ) ( D_0 ) (D0) 的结论是0,这里可以先接受)、 ( D n = 0 ) ( D_n=0 ) (Dn=0) 同理,那么对于 ( i = 1 ) ( i=1 ) (i=1):
2 D 1 + D 2 = 3 ( x 2 − x 0 ) . 2D_1 + D_2 = 3(x_2 - x_0). 2D1+D2=3(x2x0).

对于 ( i = 2 ) ( i=2 ) (i=2):
D 1 + 2 D 2 + D 3 = 3 ( x 3 − x 1 ) . D_1 + 2D_2 + D_3 = 3(x_3 - x_1). D1+2D2+D3=3(x3x1).

对于 ( i = 3 ) ( i=3 ) (i=3):
D 2 + 2 D 3 + D 4 = 3 ( x 4 − x 2 ) . D_2 + 2D_3 + D_4 = 3(x_4 - x_2). D2+2D3+D4=3(x4x2).

……
依此类推,直到 ( i = n − 1 ) ( i=n-1 ) (i=n1):
D n − 2 + 2 D n − 1 + D n = 3 ( x n − x n − 2 ) D_{n-2} + 2D_{n-1} + D_n = 3(x_n - x_{n-2}) Dn2+2Dn1+Dn=3(xnxn2)
由于 ( D n = 0 ) ( D_n=0 ) (Dn=0)(自然边界),此方程变为:
D n − 2 + 2 D n − 1 = 3 ( x n − x n − 2 ) D_{n-2}+2D_{n-1}=3(x_n - x_{n-2}) Dn2+2Dn1=3(xnxn2)

将这些方程以矩阵形式写出,就是你看到的那个三对角方程组:

[ 4 1 0 ⋯ 0 1 4 1 ⋯ 0 0 1 4 ⋯ 0 ⋮ ⋮ ⋮ ⋱ 1 0 0 0 1 4 ] ⏟ ( n − 1 ) × ( n − 1 ) [ D 1 D 2 D 3 ⋮ D n − 1 ] \underbrace{\begin{bmatrix} 4 & 1 & 0 & \cdots & 0 \\ 1 & 4 & 1 & \cdots & 0 \\ 0 & 1 & 4 & \cdots & 0 \\ \vdots & \vdots & \vdots & \ddots & 1 \\ 0 & 0 & 0 & 1 & 4 \end{bmatrix}}_{(n-1)\times(n-1)} \begin{bmatrix} D_1 \\ D_2 \\ D_3 \\ \vdots \\ D_{n-1} \end{bmatrix} (n1)×(n1) 410014100140100014 D1D2D3Dn1 = = = [ 3 ( x 2 − x 0 ) 3 ( x 3 − x 1 ) 3 ( x 4 − x 2 ) ⋮ 3 ( x n − x n − 2 ) ] . \begin{bmatrix} 3(x_2 - x_0) \\ 3(x_3 - x_1) \\ 3(x_4 - x_2) \\ \vdots \\ 3(x_n - x_{n-2}) \end{bmatrix}. 3(x2x0)3(x3x1)3(x4x2)3(xnxn2) .

为什么矩阵是这样排列的?

通过上面的递推关系,可以看到对于中间的节点 ( i ) ( i ) (i) (2 ≤ i ≤ n-2),方程都是:
D i − 1 + 4 D i + D i + 1 = 3 ( x i + 1 − x i − 1 ) D_{i-1}+4D_i + D_{i+1} = 3(x_{i+1}-x_{i-1}) Di1+4Di+Di+1=3(xi+1xi1)
如果我们观察标准形式,有一个小小的差异点需要解释:上面的最终矩阵中主对角是4,次对角是1,这和我们刚刚推导的形式 ( D i − 1 + 2 D i + D i + 1 = 3 ( . . . ) ) ( D_{i-1}+2D_i+D_{i+1}=3(...) ) (Di1+2Di+Di+1=3(...)) 有些不同。实际上,这是源于在最开始的标准推导中,节点间的区间长度假设为1。经过严格推导(在有些文献的版本中,会有一倍/二倍的放缩),最终得到的标准自然样条方程组矩阵就是此对称三对角形式。许多教科书的最终结论都是给出这样一个方程组形式:

4 D 1 + D 2 = 3 ( x 2 − x 0 ) D 1 + 4 D 2 + D 3 = 3 ( x 3 − x 1 ) D 2 + 4 D 3 + D 4 = 3 ( x 4 − x 2 ) ⋮ D n − 3 + 4 D n − 2 + D n − 1 = 3 ( x n − 1 − x n − 3 ) D n − 2 + 4 D n − 1 = 3 ( x n − x n − 2 ) \begin{aligned} 4D_1 + D_2 & = 3(x_2 - x_0) \\ D_1 + 4D_2 + D_3 & = 3(x_3 - x_1) \\ D_2 + 4D_3 + D_4 & = 3(x_4 - x_2) \\ &\;\;\vdots \\ D_{n-3} + 4D_{n-2} + D_{n-1} & = 3(x_{n-1} - x_{n-3}) \\ D_{n-2} + 4D_{n-1} & = 3(x_n - x_{n-2}) \end{aligned} 4D1+D2D1+4D2+D3D2+4D3+D4Dn3+4Dn2+Dn1Dn2+4Dn1=3(x2x0)=3(x3x1)=3(x4x2)=3(xn1xn3)=3(xnxn2)

之所以与我们中间推导出的 ( D i − 1 + 2 D i + D i + 1 ) ( D_{i-1}+2D_i+D_{i+1} ) (Di1+2Di+Di+1) 略有不同,是因为在标准形式中,通常在整条区间长度不为1的更一般情形下的推导或者在中间有个两倍关系的处理后,会将系数统一到这样一套简洁的整数系数中(本例中默认节点间距为1)。在节点间隔为1的情形下,最后标准推导出的矩阵正是如图所示的4和1构成的对称三对角矩阵。

简而言之:

  • 对角线元素为4,非对角线相邻元素为1,这是自然样条方程组的标准形式。
  • 右侧向量每个元素为 ( 3 ( x i + 1 − x i − 1 ) ) ( 3(x_{i+1}-x_{i-1}) ) (3(xi+1xi1)) 对应于差分,这与样条二阶导数约束产生的线性关系直接对应。

总结

  1. 从样条的定义和连续性条件出发,利用二阶导数连续性得出关于 ( D i ) ( D_i ) (Di) (节点一阶导数)之间的线性关系。
  2. 自然边界条件使 ( D 0 ) ( D_0 ) (D0) ( D n ) ( D_n ) (Dn) 有特定值(常用自然样条边界下 ( D 0 = 0 , D n = 0 ) ( D_0=0, D_n=0 ) (D0=0,Dn=0))。
  3. 将所有中间节点 ( D 1 , … , D n − 1 ) ( D_1,\ldots,D_{n-1} ) (D1,,Dn1) 的关系写成矩阵方程,就得到图中所示的三对角线性方程组。
  4. 该方程组求解后,即可得到所有内部节点的导数值 ( D i ) ( D_i ) (Di),进而确定 ( c i , d i ) ( c_i, d_i ) (ci,di) 等全部样条系数。

参考代码

import numpy as npclass BandedSystem:def __init__(self):self.N = 0self.lowerBw = 0self.upperBw = 0self.data = Nonedef create(self, n, p, q):"""初始化带状矩阵"""self.N = nself.lowerBw = pself.upperBw = qself.data = np.zeros((self.lowerBw + self.upperBw + 1, n))def reset(self):"""重置矩阵为零"""if self.data is not None:self.data.fill(0)def __getitem__(self, idx):"""获取矩阵元素"""i, j = idxrow = self.upperBw + i - jif 0 <= row < self.lowerBw + self.upperBw + 1 and 0 <= j < self.N:return self.data[row, j]else:return 0.0def __setitem__(self, idx, value):"""设置矩阵元素"""i, j = idxrow = self.upperBw + i - jif 0 <= row < self.lowerBw + self.upperBw + 1 and 0 <= j < self.N:self.data[row, j] = valuedef factorizeLU(self):"""LU 分解"""for k in range(self.N - 1):for i in range(k + 1, min(k + self.lowerBw + 1, self.N)):self[i, k] /= self[k, k]for j in range(k + 1, min(k + self.upperBw + 1, self.N)):self[i, j] -= self[i, k] * self[k, j]def solve(self, b):"""解线性方程组 Ax = b"""n = len(b)x = b.copy()# 前向替代for i in range(1, n):for j in range(max(0, i - self.lowerBw), i):x[i] -= self[i, j] * x[j]# 后向替代for i in range(n - 1, -1, -1):for j in range(i + 1, min(n, i + self.upperBw + 1)):x[i] -= self[i, j] * x[j]x[i] /= self[i, i]return xclass CubicSpline:def __init__(self):self.N = 0self.A = BandedSystem()self.b = Noneself.coefficients = Nonedef setConditions(self, head, tail, pieceNum):"""设置边界条件"""self.N = pieceNumself.headP = np.array(head)self.tailP = np.array(tail)self.A.create(self.N - 1, 1, 1)self.b = np.zeros((self.N - 1, 2))def setInnerPoints(self, inPoints):"""设置中间点并求解系数"""X = np.vstack([self.headP, inPoints, self.tailP])for i in range(self.N - 1):self.A[i, i] = 4if i > 0:self.A[i, i - 1] = 1if i < self.N - 2:self.A[i, i + 1] = 1self.b[i] = 3 * (X[i + 2] - X[i])self.A.factorizeLU()D = self.A.solve(self.b)D = np.vstack([np.zeros(2), D, np.zeros(2)])self.coefficients = []for i in range(self.N):a = X[i]b = D[i]c = 3 * (X[i + 1] - X[i]) - 2 * D[i] - D[i + 1]d = 2 * (X[i] - X[i + 1]) + D[i] + D[i + 1]self.coefficients.append((a, b, c, d))def getCurve(self, t):"""获取插值曲线的值"""idx = min(int(t * self.N), self.N - 1)a, b, c, d = self.coefficients[idx]t = t * self.N - idxreturn a + b * t + c * t**2 + d * t**3# 示例调用
spline = CubicSpline()
spline.setConditions([0, 0], [10, 10], 4)
spline.setInnerPoints(np.array([[2, 3], [4, 5], [7, 8]]))
t_values = np.linspace(0, 1, 100)
curve = np.array([spline.getCurve(t) for t in t_values])import matplotlib.pyplot as pltplt.plot(curve[:, 0], curve[:, 1], label="Cubic Spline")
plt.scatter([0, 2, 4, 7, 10], [0, 3, 5, 8, 10], color='red', label="Points")
plt.legend()
plt.show()
import numpy as np
import matplotlib.pyplot as plt# 定义三角函数曲线
x_values = np.linspace(0, 2 * np.pi, 10)  # 5个点,包含起点和终点
y_values = np.sin(x_values)  # 三角函数值# 初始化样条插值
spline = CubicSpline()
spline.setConditions([x_values[0], y_values[0]], [x_values[-1], y_values[-1]], len(x_values) - 1)
spline.setInnerPoints(np.array([x_values[1:-1], y_values[1:-1]]).T)# 生成插值曲线
t_values = np.linspace(0, 1, 100)  # 参数化曲线的 t 值
curve = np.array([spline.getCurve(t) for t in t_values])# 绘制结果
plt.figure(figsize=(8, 6))
plt.plot(curve[:, 0], curve[:, 1], label="Cubic Spline (Fitted)", linewidth=2)
plt.plot(x_values, y_values, 'r--', label="Original Sine Wave", linewidth=1.5)
plt.scatter(x_values, y_values, color='red', label="Data Points", zorder=5)
plt.legend()
plt.title("Fitting a Sine Wave Using Cubic Spline")
plt.xlabel("x")
plt.ylabel("y")
plt.grid(True)
plt.show()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/63401.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何在Ubuntu中利用repo和git地址下载获取imx6ull的BSP

01-设置git的用户名和邮箱 git config --global user.name "suwenhao" git config --global user.email "2487872782qq.com"这里不设置的话后面在第5步的repo配置中还是会要求输入&#xff0c;而且以后进行相关操作都要输入&#xff0c;不妨现在就进行配置…

C++设计模式之外观模式

动机 下图中左边方案的问题在于组件的客户和组件中各种复杂的子系统有了过多的耦合&#xff0c;随着外部客户程序和各子系统的演化&#xff0c;这种过多的耦合面临很多变化的挑战。 如何简化外部客户程序和系统间的交互接口&#xff1f;如何将外部客户程序的演化和内部子系统…

13.高级GLSL

高级GLSL 1.GLSL的内建变量 着色器都是最简化的&#xff0c;如果需要当前着色器以外地方的数据的话&#xff0c;我们必须要将数据传进来。我们已经学会使用顶点属性、uniform和采样器来完成这一任务了。然而&#xff0c;除此之外&#xff0c;GLSL还定义了另外几个以gl_为前缀…

【bug】python pandas KeyError: ‘index’

【bug】python pandas KeyError: index’ 环境 pandas 2.2.3问题详情 代码 import pandas as pd# 创建一个示例 DataFrame data {id: [1, 2, 3],name: [Alice, Bob, Charlie],age: [100, 200, 300] } df pd.DataFrame(data) # 这里的reset_index()用于将 Seri…

使用数据层进行数据生命周期管理

作者&#xff1a;来自 Elastic Stef Nestor Elasticsearch 7.10 使配置数据生命周期变得不再那么复杂。在这篇博文中&#xff0c;我将介绍一些变化、如何使用它们以及一些最佳实践。 数据生命周期可以包含很多阶段&#xff0c;因此我们将涉及&#xff1a; 将集群划分为层&…

Jenkins环境一站式教程:从安装到配置,打造高效CI/CD流水线环境-Ubuntu 22.04.5 环境离线安装配置 Jenkins 2.479.1

文章目录 Jenkins环境一站式教程&#xff1a;从安装到配置&#xff0c;打造高效CI/CD流水线环境-Ubuntu 22.04.5 环境离线安装配置 Jenkins 2.479.1一、环境准备1.1 机器规划1.2 环境配置1.2.1 设置主机名1.2.2 停止和禁用防火墙1.2.3 更新系统 二、安装配置Jenkins2.1 安装JDK…

R的中文文本处理包--tmcn

文章目录 介绍tmcn 和 jieba 的关系函数&#xff1a;catUTF8toUTF8实例 介绍 tmcn 包是 R 语言中的一个用于处理和分析中文文本的包&#xff0c;特别适用于中文文本的分词、词频统计和文本挖掘等任务。以下是 tmcn 包的基本用法&#xff0c;包括安装、常用函数和示例。 一个用…

64 基于32单片机的温湿度检测

所有仿真详情导航: PROTEUS专栏说明-CSDN博客 目录 一、主要功能 二、硬件资源 三、主程序编程 四、资源下载 一、主要功能 基于STM32F103C8T6单片机,采用DHT11检测温湿度,通过OLED屏幕显示,温度小于15,则继电器吸合驱动热风扇转动,高于20停止,湿度低于40%,则对应…

用Python绘制医学热图

在医学研究和临床实践中&#xff0c;数据的可视化是不可或缺的一部分。通过直观的数据展示&#xff0c;医学专业人员可以更好地理解各种疾病的治愈率、治疗效果以及医院之间的差异。今天&#xff0c;我们将介绍一种强大的数据可视化工具——热图&#xff08;Heatmap&#xff09…

WPF 本地生成验证码

1、效果如下图&#xff0c;点击图片可更新验证码&#xff08;其实图片就是一个Button的背景图&#xff09;。 2、主要使通过用户控件创建&#xff0c;UCVerificationCode.xaml代码如下。 <UserControl x:Class"UC.UCVerificationCode"xmlns"http://schemas.…

彻底理解ThreadLocal的应用场景和底层实现

一.概念 定义&#xff1a; ThreadLocal 是 Java 中所提供的线程本地存储机制&#xff0c;可以利用该机制将数据缓存在某个线程内部&#xff0c;该线程可以在任意时刻、任意方法中获取缓存的数据。 其实是可以通过调用 Set() 方法往里面存入值&#xff0c;存入的值是每个线程互…

视频 的 音频通道提取 以及 视频转URL 的在线工具!

视频 的 音频通道提取 以及 视频转URL 的在线工具&#xff01; 工具地址: https://www.lingyuzhao.top/toolsPage/VideoTo.html 它提供了便捷的方法来处理视频文件&#xff0c;具体来说是帮助用户从视频中提取音频轨道&#xff0c;并将视频转换为可以通过网络访问的URL链接。无…

shell自动显示当前git的branch

效果简介&#xff1a; 1. 如果没在git仓库&#xff0c;显示无变化 2. 如果在git仓库&#xff0c;显示当前分支 实现方法&#xff1a; 在~/.bashrc 里添加&#xff1a; function git_branch { test -d .git && branch"git branch | grep "^\*" | sed…

pytorch生成对抗网络

# 生成对抗网络 import os import torch import torchvision import torch.nn as nn from torchvision import transforms from torchvision.utils import save_image # Device configuration device torch.device(cuda if torch.cuda.is_available() else cpu) # 超参数 late…

taro小程序进入腾讯验证码

接入原因 昨天突然晚上有人刷我们公司的登录发送短信接口&#xff0c;紧急将小程序的验证码校验更新上去了 接下来就是我们的接入方法&#xff0c;其实很简单&#xff0c;不过有时候可能大家着急就没有仔细看文档&#xff0c;腾讯验证码文档微信小程序地址&#xff0c;注意这里…

Node.js 新手教程

1、nodejs简介 Node.js 是一个开源和跨平台的 JavaScript 运行时环境。它是几乎所有类型项目的流行工具&#xff01; Node.js 在浏览器之外运行 V8 JavaScript 引擎&#xff08;Google Chrome 的核心&#xff09;。这使得 Node.js 的性能非常出色。 Node.js 应用程序在单个进…

从GCC源码分析C语言编译原理——源码表层分析(脚本篇)

目录 一、目录结构 二、有意思的小功能 三、install脚本 脚本变量和设置 程序名称变量 模式和命令 参数解析 主要逻辑 四、主要功能脚本 ------------------------------------------------------------------------------------------------------------------------…

Mybatis 学习 之 XML 手册

目录 单次执行单次新增单次更新单次删除 批量执行批量新增批量更新for 循环执行更新for 循环生成多条 sql&#xff0c;一次执行 批量删除 参数传递预处理方式 (OGNL表达式 #{})数据类型转换 直接替换 (EL表达式 ${}) 安全 单次执行 单次新增 <insert id"insert"…

自定义指令,全局,局部,注册

让输入框自动获取焦点(每次刷新自动获取焦点&#xff09; <template><div><h3>自定义指令</h3><input ref"inp" type"text"></div> </template><script> export default {mounted(){this.$refs.inp.focus…

CSS3 动画详解,介绍、实现与应用场景详解

CSS3 动画概述 CSS3 动画是通过 CSS3 的新特性来实现元素的动态变化。与传统的 JavaScript 动画不同,CSS3 动画主要通过 CSS 属性的变化来实现动画效果,具有高效、轻量和易于实现的优点。CSS3 动画通常用于网页的动态交互效果、过渡效果、元素移动、缩放、旋转等场景。 一、…