OCaml Scientific Computing Tutorials

Back
Table of Contents

数学函数

从这一章开始,我们将开始探索使用 Owl 进行数字计算的世界。但让我们不要过于仓促。在进入诸如算法微分、优化、计算图等主要内容之前,让我们先尝一些开胃菜。在本章中,我们将介绍 Owl 中如何支持熟悉的数学运算。本章根据不同类型的函数进行组织,您可以随意浏览其中任何部分。请注意,本章中的函数适用于标量值。在后续章节中介绍的 N 维数组模块包含这些在 n 维数组上工作的基本函数,包括向量和矩阵。

基本函数

基本一元数学函数

许多基本数学函数将一个浮点数作为输入并返回一个浮点数。我们称它们为一元函数。您可以轻松使用这些一元函数从Maths模块中调用。例如:

Maths.sqrt 2.
>- : float = 1.41421356237309515

[@tbl:maths:basic_unary] 列出了在该模块中支持的这些一元函数。

基本一元数学函数 {#tbl:maths:basic_unary}
函数 解释
abs |x|
neg -x
reci 1/x
floor the largest integer that is smaller than x
ceil the smallest integer that is larger than x
round rounds x towards the bigger integer when on the fence
trunc integer part of x
sqr \(x^2\)
sqrt \(\sqrt{x}\)

基本二元数学函数

与一元函数不同,二元函数接受两个浮点数作为输入,并返回一个浮点数作为输出。大多数常见的算术函数属于这个类别,如[@tbl:maths:binary]所示。

二元数学函数 {#tbl:maths:binary}
函数 解释
add x + y
sub x - y
mul x * y
div x / y
fmod x % y
pow \(x^y\)
hypot \(\sqrt{x^2 + y^2}\)
atan2 返回\(\arctan(y/x)\),考虑参数的符号;这是向量\((x, y)\)相对于 x 轴的角度。

指数和对数函数

常数\(e = \sum_{n=0}^{\infty}\frac{1}{n!}\)是我们称之为自然常数的东西。之所以这样称呼它,是因为指数函数及其逆函数对数在自然界和我们的日常生活中被频繁使用:对数螺线、人口增长、碳测古物、计算银行投资等等。举个例子,在关于细菌的科学实验中,我们可以假设时间\(t\)时的细菌数量遵循指数函数\(n(t) = Ce^rt\),其中\(C\)是初始人口,\(r\)是每日增长率。通过这个模型,我们可以预测在一定时间内细菌的增长情况。

我们还有这个美妙的欧拉公式,它连接了两个最常用的常数和复数和自然数的底:

\[e^{i\pi}+ 1=0.\]

指数和对数函数的完整列表,以及一些方便的变体,都在[@tbl:maths:explog]中呈现。

指数和对数数学函数 {#tbl:maths:explog}
函数 解释
exp 指数函数\(e^x\)
exp2 \(2^x\)
exp10 \(10^x\)
expm1 返回\(\exp(x) - 1\),但对于\(x \sim 0\)更精确
log \(\log_e~x\)
log2 \(\log_2~x\)
log10 \(\log_{10}~x\)
logn \(\log_n~x\)
log1p 逆函数expm1
logabs \(\log(|x|)\)
xlogy \(x \log(y)\)
xlog1py \(x \log(y+1)\)
logit \(\log(p/(1-p))\)
expit \(1/(1+\exp(-x))\)
log1mexp \(\log(1-\exp(x))\)
log1pexp \(\log(1+\exp(x))\)

三角函数

正弦、余弦和正切函数属于三角函数,它们将两条边的比率与直角三角形的角度联系起来。它们包括常用的sincos等,以及它们的倒数,如余割、正割等。这些函数在不同领域的数值计算应用中广泛使用,如力学和几何学。这些三角函数都是一元函数,例如:

Maths.sin (Owl_const.pi /. 2.)
>- : float = 1.

它们都包含在 Owl 的数学模块中,如[@tbl:maths:triangular]所示。

三角数学函数 {#tbl:maths:triangular}
函数 解释 导数 泰勒展开
sin \(\sin(x)\) \(\cos(x)\) \(\sum_{n=1}(-1)^{n+1}\frac{x^{2n+1}}{(2n+1)!}\)
cos \(\cos(x)\) \(-\sin(x)\) \(\sum_{n=1}(-1)^n\frac{x^{2n}}{(2n)!}\)
tan \(\tan(x)\) \(1 + \tan^2(x)\) \(\sum_{n=1}\frac{4^n(4^n-1)B_n~x^{2n-1}}{(2n)!}\)
cot \(1/\tan(x)\) \(-(1 + \textrm{cot}^2(x))\) \(\sum_{n=0}\frac{E_n~x^{2n}}{(2n)!}\)
sec \(1/\cos(x)\) \(\textrm{sec}(x)\tan(x)\) \(\sum_{n=0}\frac{2(2^{2n-1})B_n~x^{2n-1}}{(2n)!}\)
csc \(1/\sin(x)\) \(-\textrm{csc}(x)\textrm{cot}(x)\) \(\frac{1}{x}-\sum_{n=1}\frac{4^n~B_n~x^{2n-1}}{(2n)!}\)

这里的\(B_n\)是第\(n\)贝努利数\(E_n\)是第\(n\)欧拉数。图[@fig:algodiff:trio]显示了这些三角函数之间的关系。这个图表受到了维基百科的一篇文章的启发。这些函数还有相应的反函数:asinacosatanacotasecacsc。例如,如果\(\sin(a) = b\),那么\(\textrm{asin}(b) = a\)

不同三角函数之间的关系
不同三角函数之间的关系

另一个相关的概念是双曲函数,如sinhcosh。这些函数是使用指数函数定义的。正如我们在[@#fig:algodiff:trio]中看到的,三角函数与圆相关。类似地,双曲函数与双曲线相关。例如,点(cosh(x), sinh(x))形成等边双曲线的右半部分,就像圆上的(cos(x), sin(x))一样。双曲函数广泛应用于数值计算中,如微分方程解、双曲几何等。

  • sinh: \(\frac{e^x - e^{-x}}{2}\), derivative is \(\cosh(x)\), and taylor expansion is \(\sum_{n=0}\frac{x^{2n+1}}{(2n+1)!}\).
  • cosh: \(\frac{e^x + e^{-x}}{2}\), derivative is \(\sinh(x)\), and taylor expansion is \(\sum_{n=0}\frac{x^{2n+1}}{(2n+1)!}\).
  • tanh: \(\frac{\sinh{x}}{\cosh{x}}\), derivative is \(1-\tanh^2(x)\), and taylor expansion is \(\sum_{n=1}\frac{4^n(4^n-1)B_{2n}~x^{2n-1}}{(2n)!}\).
  • coth: \(\frac{\cosh{x}}{\sinh{x}}\), derivative is \(1-\coth^2(x)\), and taylor expansion is \(\frac{1}{x}-\sum_{n=1}\frac{4^n~B_{2n}~x^{2n-1}}{(2n)!}\).
  • sech: \(1/\cosh(x)\), derivative is \(-\tanh(x)/\cosh(x)\), and taylor expansion is \(\sum_{n=0}\frac{E_{2n}~x^{2n}}{(2n)!}\).
  • csch:\(1/\sinh(x)\), derivative is \(-\coth(x)/\sinh(x)\), and taylor expansion is \(\frac{1}{x}+\sum_{n=1}\frac{2(1-2^{2n-1})B_{2n}~x^{2n-1}}{(2n)!}\).

同样地,每个这些函数都有对应的逆函数:asinhacoshatanhacothasechacsch。这些双曲三角函数之间的关系在[@fig:algodiff:hyper_trio]中清晰地描绘出来。

不同双曲三角函数之间的关系
不同双曲三角函数之间的关系

除了这些函数外,还有一些相关的函数。 sinc 返回 \(\sin(x)/x\) 并且在 \(x=0\) 时为 1。 logsinh 返回 \(\log(\sinh(x))\) 但处理大的 \(|x|\)logcosh 返回 \(\log(\cosh(x))\) 但处理大的 \(|x|\)sindg/cosdg/tandg/cotdg 是以度为单位给定的角度的正弦/余弦/正切/余切。

其他数学函数

还有一些在传统数学中可能不太常用的其他函数。像 sigmoidrelu 这样的函数在深度学习中经常用作神经网络中的激活函数。激活函数对于神经网络在输出结果、准确性、收敛速度等方面都至关重要。我们将在本书后面的神经网络章节中详细讨论它们。

  • sigmoid x: \(1 / (1 + \exp(-x))\)
  • signum x: 返回 x 的符号:-1、0 或 1
  • softsign x: 平滑 sign 函数
  • relu x: \(\max(0, x)\)

特殊函数

除了我们刚刚列出的,还有很多特殊函数。你可能以前没听说过它们,但它们在数学分析、物理学等不同领域都有确立的名称,对科学家和工程师很重要。在 Owl 中,这些函数的实现依赖于Cephes 数学函数库,这是一个包含对科学家和工程师感兴趣的特殊函数的 C 语言库。它们在本节的其余部分中列出。也许我们不能深入挖掘所有这些函数的数学或物理含义,但在需要时您可能会发现它们非常方便。

艾里函数

艾里函数 Ai(x) 以英国天文学家乔治·B·艾里的名字命名。它是二阶线性微分方程的解:

\[y''(x) = xy(x).\]

这个微分方程有两个线性无关的解 AiBi。Owl 提供 airy 函数来执行此操作:

val airy : float -> float * float * float * float

返回的四个数字分别是 Ai、它的导数 Ai'Bi 和它的导数 Bi'。让我们看一个例子。它绘制了两个解 AiBi[@fig:algodiff:airy]中。

let x = Mat.linspace (-15.) 5. 200

let y0 = Mat.map (fun x ->
    let ai, _, _, _ = Maths.airy x in ai
) x

let y1 = Mat.map (fun x ->
    let _, _, bi, _ = Maths.airy x in bi
) x

let _ =
  let h = Plot.create "special_airy.png" in
  Plot.(plot ~h ~spec:[ RGB (66, 133, 244); LineStyle 1; LineWidth 2. ] x y0);
  Plot.(plot ~h ~spec:[ RGB (219, 68,  55); LineStyle 2; LineWidth 2. ] x y1);
  Plot.(set_yrange h (-0.5) 1.);
  Plot.(legend_on h ~position:SouthEast [|"Ai"; "Bi"|]);
  Plot.output h
艾里方程两个解的示例
艾里方程两个解的示例

贝塞尔函数

贝塞尔函数由数学家丹尼尔·贝努利首次定义,然后由弗里德里希·贝塞尔进行了泛化,它们是贝塞尔微分方程的规范解:

\[x^2y''+xy'+(x^2 - \alpha^2)y = 0.\]

复数\(\alpha\)被称为贝塞尔函数的“阶”。贝塞尔函数在研究波传播和静电势等许多问题中非常重要,例如在圆柱波导中的电磁波。在解决柱坐标系时,通常使用整数阶或半整数阶的贝塞尔函数。

贝塞尔函数可以分为两种。两种都是贝塞尔微分方程的解,但第一种在原点处是非奇异的,而第二种在原点处是奇异的(\(x=0\))。特殊情况是当\(x\)是纯虚数时。在这种情况下,解被称为修改的贝塞尔函数,它也可以被归类为第一种和第二种。基于这些类别,Owl提供了这些函数。

贝塞尔函数 {#tbl:maths:bessel}
函数 解释
j0 x 零阶第一类贝塞尔函数
j1 x 一阶第一类贝塞尔函数
jv x y 实阶第一类贝塞尔函数
y0 x 零阶第二类贝塞尔函数
y1 x 一阶第二类贝塞尔函数
yv x y 实阶第二类贝塞尔函数
yn a x 整数阶第二类贝塞尔函数
i0 x 零阶修改贝塞尔函数
i1 x 一阶修改贝塞尔函数
iv x y 实阶修改贝塞尔函数
i0e x 零阶指数缩放修改贝塞尔函数
i1e x 一阶指数缩放修改贝塞尔函数
k0 x 零阶第二类修改贝塞尔函数
k1 一阶第二类修改贝塞尔函数
k0e 零阶指数缩放第二类修改贝塞尔函数
k1e 一阶指数缩放第二类修改贝塞尔函数

椭圆函数

Jacobi椭圆函数用于研究摆动运动。共有十二个Jacobi椭圆函数,而我们在这里包括的ellipj返回其中三个:sncndn。该函数的第四个输出phi被称为输入u的振幅。

另一方面,椭圆积分最初用于找到椭圆的周长。椭圆积分函数可以表示为:\[f(x)=\int_c^xR(t, \sqrt{P(t)})dt,\]其中\(R\)是其两个参数的有理函数,\(P\)是次数为3或4的多项式,没有重复的根,\(c\)是一个常数。椭圆积分可以分为“完全”或“不完全”。前者是一个单一参数的函数,而后者包含两个参数。每个椭圆积分都可以变换,使其包含有理函数和三个Legendre标准形式的积分,根据这些形式,椭圆可以分为第一、第二和第三类。Owl中的椭圆函数列在[@tbl:maths:elliptic]中。

椭圆函数 {#tbl:maths:elliptic}
函数 解释
ellipj u m 参数m在0和1之间,实参数u的Jacobi椭圆函数
ellipk m 完全椭圆积分的第一类
ellipkm1 p 围绕m = 1的完全椭圆积分的第一类
ellipkinc phi m 第一类不完全椭圆积分
ellipe m 完全椭圆积分的第二类
ellipeinc phi m 第二类不完全椭圆积分

我们可以使用ellipe计算椭圆的周长。通常,要计算这个需要微积分,但椭圆函数提供了一个简单的解决方案。假设一个椭圆的半长轴\(a=4\),半短轴\(b=3\)。我们可以简单地使用\(4a\textrm{ellipe}(1 - \frac{b^2}{a^2})\)来计算其周长。

let a = 4.
>val a : float = 4.
let b = 3.
>val b : float = 3.
let c = 4. *. a *. Maths.(ellipe (1. -. pow (b /. a) 2.))
>val c : float = 22.1034921607095072

伽玛函数

对于正整数n,伽玛函数是阶乘函数:

\[\Gamma(n) = (n-1)!\]

对于具有正实部的复数\(z\),伽玛函数定义为:

\[\Gamma(z) = \int_0^{\infty}x^{z-1}e^{-x}dx.\]

这里的伽玛函数是从零到无穷的积分。如果我们将其更改为从零到某个上限的积分,则称为“下不完全伽玛函数”。同样,如果是从某个下限到无穷的积分,则该积分称为“上不完全伽玛函数”。

伽玛函数在流体动力学、几何学、天体物理学等多个领域广泛应用。它特别适用于描述时间或空间中呈指数衰减的过程的普遍模式。Owl提供的伽玛函数及相关函数列在[@tbl:maths:gamma]中。

伽玛函数
函数 解释
gamma z 返回 Gamma 函数的值
rgamma z Gamma 函数的倒数
loggamma z Gamma 函数对数的主支
gammainc a x 正则化的下不完全 Gamma 函数
gammaincinv a y gammainc 的反函数
gammaincc a x 补充不完全 Gamma 积分
gammainccinv a y gammaincc 的反函数
psi z digamma 函数

这里是使用 gamma 的一个例子。

let x = Mat.linspace (-3.5) 5. 2000

let y = Mat.map Maths.gamma x

let _ =
  let h = Plot.create "example_gamma.png" in
  Plot.(plot ~h ~spec:[ RGB (66, 133, 244); LineStyle 1; LineWidth 2. ] x y);
  Plot.(set_yrange h (-10.) 20.);
  Plot.output h
Examples of Gamma function along part of the real axis
实轴部分的 Gamma 函数示例

Beta 函数

Beta 函数定义为:

\[B(x,y) = \int_0^1t^{x-1}(1-t)^{y-1}dt = \frac{\Gamma(x)\Gamma(y)}{\Gamma(x+y)}\]

这里 \(\Gamma\) 是 Gamma 函数,类似于 Gamma 函数,Beta 函数也有其“不完全”版本。不完全 Beta 函数将此定义扩展为:

\[B(x, a, b) = \int_0^xt^{a-1}(1-t)^{b-1}dt.\]

在 Owl 中,可以使用 Maths 模块中的 beta x y 函数调用 Beta 函数,其不完全版本是 betainc a b x。我们还提供了 betaincinv 函数,它是 betainc 的反函数。

Beta 函数有几个属性。例如,下面的代码显示了 beta 函数和 gamma 函数之间的关系。

let x = Maths.beta 3. 4.
>val x : float = 0.0166666666666666664
let y = Maths.((gamma 3.) *. (gamma 4.) /. (gamma (7.)))
>val y : float = 0.0166666666666666664

另一个属性是其对称性,即 \(B(x,y) = B(y, x)\)

let x = Maths.beta 3. 4.
>val x : float = 0.0166666666666666664
let y = Maths.beta 4. 3.
>val y : float = 0.0166666666666666664

斯特鲁夫函数

斯特鲁夫函数定义如下:\[H_v(x) = (z/2)^{v + 1} \sum_{n=0}^\infty \frac{(-1)^n (z/2)^{2n}}{\Gamma(n + \frac{3}{2}) \Gamma(n + v + \frac{3}{2})},\]

其中\(\Gamma\)是伽玛函数。除非\(v\)是整数,否则\(x\)必须为正数。斯特鲁夫函数广泛应用于各种物理应用,如水波问题和非定常空气动力学计算等。

Owl函数struve v x返回斯特鲁夫函数的值。参数\(v\)被称为此函数的。以下是显示阶从0到4的斯特鲁夫函数曲线的示例。

let _ =
  let h = Plot.create "example_struve.png" in
  Plot.(plot_fun ~h ~spec:[ RGB (66, 133, 244); LineStyle 1; LineWidth 2.] (Maths.struve 0.) (-12.) 12.);
  Plot.(plot_fun ~h ~spec:[ RGB (219, 68,  55); LineStyle 2; LineWidth 2.] (Maths.struve 1.) (-12.) 12.);
  Plot.(plot_fun ~h ~spec:[ RGB (244, 180,  0); LineStyle 3; LineWidth 2.] (Maths.struve 2.) (-12.) 12.);
  Plot.(plot_fun ~h ~spec:[ RGB (77,  81,  57); LineStyle 1; LineWidth 2.] (Maths.struve 3.) (-12.) 12.);
  Plot.(plot_fun ~h ~spec:[ RGB (111, 51, 129); LineStyle 2; LineWidth 2.] (Maths.struve 4.) (-12.) 12.);
  Plot.(set_yrange h (-3.) 5.);
  Plot.(legend_on h ~position:SouthEast [|"H0"; "H1"; "H2"; "H3"; "H4"|]);
  Plot.output h
不同阶数的斯特鲁夫函数示例。
不同阶数的斯特鲁夫函数示例。

ζ函数

赫维茨 zeta 函数 zeta x q定义如下:

\[\zeta(x, q) = \sum_{k=0}^{\infty}\frac{1}{(k+q)^x}.\]

\(q\)设置为1时,该函数缩减为黎曼 zeta 函数。函数zetac x返回黎曼 zeta 函数减1。ζ函数经常用于分析动态系统。此外,黎曼 zeta 函数在数论中发挥重要作用,并在量子物理、概率论和应用统计学等广泛应用。

我们可以在特定点评估ζ函数,例如:

Maths.zeta 4. 1.
>- : float = 1.08232323371113837
(Maths.pow Owl_const.pi 4.) /. 90.
>- : float = 1.08232323371113792

误差函数

这里的误差函数不是关于编程中的错误处理,而是另一类特殊函数。在数学中,误差函数定义为:\[\frac{2}{\sqrt{\pi}}\int_0^x e^{-t^2})dt.\]

误差函数经常出现在概率和统计学中。实际上,在统计学中,对于非负值xerf x是随机变量y落在范围[-x, x]的概率。这里y服从均值为0、方差为0.5的正态分布。由于它表示某种概率,补充误差函数1 - erf(x)也经常使用。Owl中的误差函数及其相关变体列在[@tbl:maths:error]中。

误差函数 {#tbl:maths:error}
函数 解释
erf x 误差函数
erfc x 补充误差函数:\(1 - \textrm{erf}(x)\)
erfcx x 缩放的补充误差函数:\(\exp(x^2) \mathrm{erfc}(x)\)
erfinv x 误差函数的反函数
erfcinv x 补充误差函数的反函数

误差函数是一个sigmoid函数。我们可以通过下面的代码观察其形状。

let _ =
  let h = Plot.create "example_erf.png" in
  Plot.(plot_fun ~h ~spec:[ RGB (66, 133, 244); LineStyle 1; LineWidth 2.] Maths.erf (-3.) 3.);
  Plot.output h
误差函数的绘图。
误差函数的绘图。

积分函数

除了我们到目前为止提到的内容,Owl还提供了几个特殊积分函数。

例如,道森函数定义为:\[D(x) = e^{-x^2}\int_0^x~e^{t^2}dt\]

菲涅尔三角函数积分返回一个包含两部分的元组:\[S(x) = \int_0^x~sin(t^2)dt, C(x) = \int_0^x~cos(t^2)dt.\]

与许多其他特殊函数一样,这两种类型的积分受到了物理研究的启发,如电磁问题。它们分别由dawsnfresnel函数在Maths模块中提供。我们可以通过绘图观察这些积分函数的函数。

let _ =
  let h = Plot.create ~m:1 ~n:2 "example_integrals.png" in
  Plot.subplot h 0 0;
  Plot.(plot_fun ~h ~spec:[ RGB (66, 133, 244); LineStyle 1; LineWidth 2.] Maths.dawsn (-5.) 5.);
  Plot.set_ylabel h "dawsn(x)";
  Plot.subplot h 0 1;
  Plot.(plot_fun ~h ~spec:[ RGB (66, 133, 244); LineStyle 1; LineWidth 2.] (fun x -> let s, _ = Maths.fresnel x in s) 0. 5.);
  Plot.(plot_fun ~h ~spec:[ RGB (219, 68,  55); LineStyle 2; LineWidth 2.] (fun x -> let _, c = Maths.fresnel x in c) 0. 5.);
  Plot.(legend_on h ~position:SouthEast [|"S(x)"; "C(x)"|]);
  Plot.set_ylabel h "fresnel(x)";
  Plot.output h
Dawson 和 Fresnel 积分函数的图表
Dawson 和 Fresnel 积分函数的图表。

除了这两个之外,还提供了其他几种特殊积分函数。完整列表显示在[@tbl:maths:integral]中。

特殊积分函数 {#tbl:maths:integral}
函数 解释
expn n x 广义指数积分 \(E_n(x) = x^{n-1}\int_x^{\infty}\frac{e^{-t}}{t^n}dt\)
shi x 双曲正弦积分:\(\int_0^x~\frac{\sinh~t}{t}dt\)
chi x 双曲余弦积分:\(\gamma + \log(x) + \int_0^x~\frac{\cosh~t -1}{t}dt\)
shichi x (shi x, chi x)
si x 正弦积分:\(\int_0^x~\frac{\sin~t}{t}dt\)
ci x 余弦积分:\(\gamma + \log(x) + \int_0^x~\frac{\cos~t -1}{t}dt\)
sici x (si x, ci x)

阶乘

在函数之后,让我们转向一个我们熟悉的概念:阶乘。阶乘的定义很简单:

\(F(n) = n! = n \times (n - 1) \times (n-2) \ldots \times 1\)

阶乘函数及其几个变体都包含在Math模块中。

阶乘函数 {#tbl:maths:factorial}
函数 解释
fact n 阶乘函数\(!n\)
log_fact n 阶乘函数的对数
doublefact n 双阶乘函数计算\(n!! = n(n-2)(n-4)\dots 2\)(或 1)
log_doublefact n 双阶乘函数的对数

阶乘函数接受整数作为输入,例如:

Maths.fact 5
>- : float = 120.

阶乘在数学的许多领域中都有应用,尤其是在组合学中。排列和组合都以阶乘的形式定义。排列函数返回有序子集的数量\(n!/(n-k)!\),长度为\(k\),从包含\(n\)个元素的集合中取出。组合函数返回集合\(n\)个元素中\(k\)个元素的子集数量,即\({n\choose k} = n!/(k!(n-k)!)\)。表[@tbl:maths:perm]提供了您可以在Math模块中使用的组合数学函数。

排列和组合函数 {#tbl:maths:perm}
函数 解释
permutation n k 排列数
permutation_float n k 类似于permutation,但处理更大范围并返回浮点数
combination n k 组合数
combination_float n k 类似于combination,但处理更大范围并返回浮点数
log_combination n k 返回\({n\choose k}\)的对数

让我们看一个简单的例子。

let x = Maths.combination 10 2
>val x : int = 45
let y = Maths.combination_float 10 2
>val y : float = 45.

插值和外推

有时我们不知道函数\(f\)的完整描述,只知道其中一些点,因此无法计算其在任意点的值。我们的目标是通过在给定数据点之间绘制平滑曲线来估计\(f(x)\)。如果\(x\)在给定数据的范围内,这个任务称为插值,否则称为外推,后者要困难得多。

Owl_maths_interpolate模块提供了一个用于插值和外推的polint函数:

val polint : float array -> float array -> float -> float * float

函数polint xs ys x对给定的数组xsys执行多项式插值。给定数组\(xs[0 \ldots (n-1)]\)\(ys[0\ldots~(n-1)]\),以及一个值x,此函数返回一个值y和一个误差估计dy

顾名思义,polint使用可能通过给定点的最低可能次数的多项式来近似复杂的曲线。我们通过一个例子展示这种插值方法的工作原理。在前一节中,我们已经说过Gamma函数实际上是整数函数\(y(x) = (n-1)!\)的插值解。因此,我们可以指定平面上由该阶乘函数生成的五个节点,并查看插值函数与Gamma函数本身相比的效果。

let x = [|2; 3; 4; 5; 6|]
>val x : int array = [|2; 3; 4; 5; 6|]
let y = Array.map (fun x -> Maths.fact (x - 1)) x
>val y : float array = [|1.; 2.; 6.; 24.; 120.|]
let x = Array.map float_of_int x
>val x : float array = [|2.; 3.; 4.; 5.; 6.|]

积分

我们已经介绍了一些特殊的积分函数,但我们仍然需要适用于任何输入函数的一般积分方法。给定一个函数\(f\),它接受一个实变量和实轴上的一个区间\([a, b]\),该函数的积分

\[\int_a^bf(x)dx\]

可以被视为笛卡尔平面上由曲线\(f\)界定、在\([a, b]\)范围内的区域的带符号面积之和。位于x轴上方的区域增加到总和中,而位于x轴下方的区域则从总面积中减去。

Owl_maths_quandrature模块中,Owl提供了几种数值例程,以帮助您进行积分。例如,我们可以使用以下代码计算\(\int_1^4x^2\)

Owl_maths_quadrature.trapz (fun x -> x ** 2.) 1. 4.
>- : float = 21.0001344681758439

我们可以使用微积分基本定理验证这个结果:

\[\int_1^4x^2 = (4^3 -1^3) / 3 = 21\]

所以您可能会想,这个trapz是什么?为什么结果不是确切的21?使用数值方法(或数值积分)进行积分可以追溯到微积分的发明甚至更早。基本思想是使用小面积的总和来近似积分的面积。存在许多算法用于数值积分,使用梯形规则是其中之一。

这个经典方法将ab划分为\(N\)个等间距的横坐标:\(x_0, x_1, \ldots, x_N\)。在\(x_i\)\(x_j\)之间的每个区域被看作一个“梯形”,并且面积公式计算为:

\[\int_{x_0}^{x_1}f(x)dx = h(\frac{f(x_0)}{2} + \frac{f(x_1)}{2}) + O(h^3f'').\]

这里的误差项\(O(h^3f'')\)表明近似误差与横坐标大小\(h\)和原始函数的二阶导数有关。函数trapz实现了这种方法。它的接口是:

val trapz : ?n:int -> ?eps:float -> (float -> float) -> float -> float -> float

trapz ~n ~eps f a b使用梯形法计算f在区间[a,b]上的积分。它通过迭代多个阶段工作,每个阶段通过添加更多的内部点来提高准确性。参数\(n\)指定最大步数,默认为20,eps是所需的分数精度阈值,默认为1e-6

其他方法在接口上类似于trapz,只是在实现上有所不同。例如,simpson使用辛普森公式:

\[\int_{x_0}^{x_2}f(x)dx = h(\frac{f(x_0)}{3} + \frac{4f(x_1)}{3} + \frac{f(x_2)}{3}) + O(h^5f(4)).\]

然后有龙贝格积分romberg)可以选择不同阶数的方法以提供良好的准确性,这些算法通常比trapzsimpson方法快得多。此外,如果横坐标可以变化,我们有自适应的固定容差高斯积分(gaussian)和固定阶数的高斯积分(gaussian_fixed)。

例如,我们可以使用数值积分方法计算上一节中的特殊正弦积分函数\(Si(x)=\int_0^x\frac{sin(t)}{t}dt\)。让我们设定\(x=4\)。我们可以看到数值方法gaussian很好地逼近了这个特殊积分函数。



实用函数

除了我们到目前为止提到的内容之外,还有一些值得一提的实用数学函数。

我们知道,质数是大于1的自然数,不能通过两个较小的自然数相乘而得到。这是信息技术中的一个关键概念,广泛应用于公钥密码学等应用。函数is_prime检查整数是否为质数。该函数对所有能由整数表示的数字都是确定性的。它是使用Miller-Rabin素性测试方法实现的。

Maths.is_prime 997
>- : bool = true

另一个与数论相关的概念是费马因子分解,它将奇整数表示为两个平方的差:\(N = a^2 - b^2\),因此N可以分解为\((a+b)(a-b)\)。函数fermat_fact对奇数N执行费马因子分解,即分解为两个大致相等的因子\(x\)\(y\),使得\(N=x\times~y\)

Maths.fermat_fact 6557
>- : int * int = (83, 79)
83 * 79
>- : int = 6557

接下来的两个函数涉及计算机中浮点数的精度。

nextafter from to返回fromto方向上的下一个可表示的双精度值。另一个函数nextafter from to返回fromto方向上的下一个可表示的单精度值。在两种情况下,如果from等于to,则返回此值本身。例如:

Maths.nextafterf 1. 2.;;
>- : float = 1.00000011920928955
Maths.nextafter 1. 2.;;
>- : float = 1.00000000000000022
Maths.nextafter 1. 0.;;
>- : float = 0.999999999999999889

总结

我们从我们熟悉的数学函数开始讨论数值计算的主题。本章介绍了 Owl 支持的数学函数,包括基本和常用函数、一些不太常用但仍然重要的特殊函数、阶乘、插值、外推、积分等。随时跳转到您感兴趣的任何部分,并使用这些函数解决手头的数学问题。您可能会发现 Owl 与任何其他数值库一样优秀的计算器。

下一步:第05章统计函数