近期需要绘制牛顿迭代数值计算示意图,考虑用 TikZ 实现。其关键问题是如何在函数指定点绘制切线,在 www.latexstudio.net 检索到使用 TikZ 绘制曲线的切线的方法,但不能完全符合要求。
考虑到曲线上指定点的切线可以用该点附近的线段微元表示,为此,构造两个水平差为 1pt 的两条垂线,然后分别求曲线与这两条垂线的交点,连接这两个交点就可以表示该点的切线。然后,对该线段适当进行延长,就可以得到需要的切线。
对于给定的两个路径,可以使用 TikZ 为路径提供的name intersections
参数求解这两个路径的交点。为后续使用方便,可以定义如下求解两个路径交点的命令:
% 求两个路径交点
% #1 第1路径名称
% #2 第2路径名称
% #3 交点名称,同时定义了一个全局宏 \InterNb 记录了交点总数
\newcommand{\intersec}[3]{%
\path[name intersections={of=#1 and #2, by=#3, sort by=#1,total=\t}]
\pgfextra{\xdef\internb{\t}};
}
注意,该命令只能在TikZ相关环境和命令中使用。
基于微元思想,可以定义如下求解曲线上指定点切线两个端点的命令:
% 求路径上指定点的切线的两个端点
% #1 路径名称
% #2 路径上指定点名称
% #3 端点1名称
% #4 端点2名称
% 原理,用#2点附件(1pt)的一段直线微元表示切线
\newcommand{\tanterms}[4]{
% 过#2点的垂线
\path[name path = l](#2|-current bounding box.south) -- (#2|-current bounding box.north);
% 将过#2点的垂线水平偏移1pt
\coordinate (rd) at ($(#2) + (1pt, 0)$);% 可以考虑使用sp为单位,精度更高
\path[name path = r](rd|-current bounding box.south) -- (rd|-current bounding box.north);
% 求路径#1与路径l的交战,并记为#3
\intersec{#1}{l}{#3}
% 求路径#1与路径r的交战,并记为#4
\intersec{#1}{r}{#4}
}
如果需要提高精度,可以考虑使用sp作为两个垂线的偏移量单位。
同样,该命令只能在TikZ相关环境和命令中使用。
对于一个指定曲线,通过3次迭代进行牛顿迭代求解方程根的示意图的完整代码为:
\documentclass[margin=3pt,
convert,
convert={
outext=.png,
command=\unexpanded{
pdftocairo -r 300 -png \infile % 将生成的pdf文件转换为png图像
}
}
]{standalone}
\usepackage{tikz}
\usetikzlibrary{intersections, calc}
% 求两个路径交点
% #1 第1路径名称
% #2 第2路径名称
% #3 交点名称,同时定义了一个全局宏\InterNb记录了交点总数
\newcommand{\intersec}[3]{%
\path[name intersections={of=#1 and #2, by=#3, sort by=#1,total=\t}]
\pgfextra{\xdef\internb{\t}};
}
% 求路径上指定点的切线的两个端点
% #1 路径名称
% #2 路径上指定点名称
% #3 端点1名称
% #4 端点2名称
% 原理,用#2点附件(1pt)的一段直线微元表示切线
\newcommand{\tanterms}[4]{
% 过#2点的垂线
\path[name path = l](#2|-current bounding box.south) -- (#2|-current bounding box.north);
% 将过#2点的垂线水平偏移1pt
\coordinate (rd) at ($(#2) + (1pt, 0)$);% 可以考虑使用sp为单位,精度更高
\path[name path = r](rd|-current bounding box.south) -- (rd|-current bounding box.north);
% 求路径#1与路径l的交战,并记为#3
\intersec{#1}{l}{#3}
% 求路径#1与路径r的交战,并记为#4
\intersec{#1}{r}{#4}
}
\begin{document}
\begin{tikzpicture}[
domain=1.5:4.1,
samples=101,
]
% 绘制命名曲线
\draw[blue,thick, name path=graph] plot (\x,{\x^3-7.7*\x^2+19.2*\x-15.5}) node[right] {$f(x)$};
% 绘制x轴和y轴
\draw[->, name path=x] (0,0) -- (5,0) node[right] {$x$};
\draw[->] (0,-0.5) -- (0,3.5) node[left] {$y$};
% 绘制根的真值点
\fill[fill=black, name intersections={of=graph and x}]
(intersection-3) circle[radius = 0.02cm] node[red, scale = 0.6,
left, shift={(4pt, 3pt)}, font=\tiny]{$x^*$};
% 绘制起始点
\fill[fill = black] (4, 0) coordinate (xs) circle[radius = 0.02cm] node[scale = 0.5, shift={(2pt, 0)},below, font = \tiny] {$x_{0}$};
% 通过循环绘制3次迭代结果
\foreach \X/\clr in {1/red, 2/green, 3/violet}
{
% 调整x轴点标记y方向偏移
\ifodd\X
\xdef\yoff{-3pt}
\else
\xdef\yoff{0pt}
\fi
% 过xs的垂线与曲线graph的交点
\path[name path=v](xs|-current bounding box.south) -- (xs|-current bounding box.north);
\intersec{graph}{v}{fx}
\fill[fill = black] (fx) circle[radius = 0.02cm] node[scale = 0.6, left, shift={(2pt,3pt)}, font = \tiny] {$f(x_{\X})$};
% 求切线端点
\tanterms{graph}{fx}{li}{ri}
% 定义切线路径,对得到的切线微元进行延长
% 延长倍数和方向需要手动调整(需要改进)
\path[name path=t](ri)--($(ri)!12!(li)$);
\intersec{t}{x}{tx}
\fill[fill = black] (tx) circle[radius = 0.02cm] node[scale = 0.5, shift={(2pt, \yoff)}, below, font = \tiny] {$x_{\X}$};
% 绘制切线和垂线
\draw[\clr, dashed] (fx)--(tx) (fx)--(xs);
% 下一次迭代
\coordinate (xs) at (tx);
}
\end{tikzpicture}
\end{document}
该代码的绘制结果:
Happy LaTeXing!