采用微元思想在TikZ中绘制曲线指定点的切线 - registor

发布于 2021-03-26 17:29:21

近期需要绘制牛顿迭代数值计算示意图,考虑用 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}

该代码的绘制结果:
image.png
Happy LaTeXing!

0 条评论

发布
问题