免費論壇 繁體 | 簡體
Sclub交友聊天~加入聊天室當版主
分享
返回列表 发帖

将 Mathematica 画的函数图导出数据给 tikz 作图

tikz 有一种作图方式是通过读入外部文件的数据来画的,命令的格式是 plot[<local options>] file{<filename>},比如 \draw plot file {D:/0.txt}; 酱紫,被读入的 0.txt 中的数据格式为每行两个或以上数字,用空格隔开,详细见 tikz 手册 P327 Plotting Points Read From an External File。

数据文件当然应该用软件生成,本帖讲的是用 MMC 导出数据。

PS、其实我用 MMC 也不专业,所以下面所讲的东东可能会有更好的办法。

首先,最简单的一种方法是用 Table 来取点再导出。
比如,我想画一个 tikz 里没有的函数 erf(x)(这帖提到过),在 MMC 里运行:
  1. test0 = Table[{x, Erf[x]}, {x, -3, 3, 0.1}];
  2. Export["D:/0.txt", test0, "Table"]
复制代码
在 D 盘就会生成 0.txt,其内容是酱紫的:
-3.        -0.9999779095030014
-2.9        -0.9999589021219005
-2.8        -0.9999249868053346
-2.7        -0.9998656672600594
-2.6        -0.9997639655834707
......(一共61行,即共取了61个点)
然后回到 latex 中:
  1. \tikz{
  2. \draw[->](-3,0)--(3,0);\draw[->](0,-1)--(0,1);
  3. \draw plot file {D:/0.txt};
  4. }
复制代码
即得:
test0.png

这种“Table 取点法”对于较平滑的连续函数是 OK 的,而像 `\sin(1/x)` 这种函数,在原点附近震荡得很厉害,如果用此法,要显示得清楚的话,必须将 Table 的步长设得很小,让取点数变大,但这是不划算的,因为这样取的点在横坐标上是均匀分布的,然而较远处并不需要取那么多点,这就造成浪费,如果你不知道我在说什么,可以看看下图:
QQ截图20180624181521.png
这里共取了 500 个点,很明显看到,点在右边浪费太多了。将点连起来后,是酱紫:
QQ截图20180624182015.png

现在来看看直接用 Plot 的效果:
test1 = Plot[Sin[10/x], {x, 0, 5}, PlotPoints -> 20]
QQ截图20180624203842.png
原点附近明显比上面的好,但这个图的取点数仅仅比上面的多两个,可见 MMC 作图的取点方法是更好的(虽然我不知道方法具体是什么)。

这图的取点数我是怎么知道的呢?这里需要用一条命令:InputForm,效果如下:
QQ截图20180624203911.png
后面还有一大堆东西,而中间的那些数组就是画图所取的点,可以用如下命令把它们提取出来:
  1. tab1 = InputForm[test1][[1, 1, 1, 1, 3, 1, 2, 1]];
复制代码
(此处 1, 1, 1, 1, 3, 1, 2, 1 这串数字可能会与版本有关,我的是 11.2)
然后用 Length[tab1] 即得点数为 502,也可以用 ListPlot[tab1] 把它们展示出来:
QQ截图20180624204509.png
可以看到,MMC 作图时取点更精明,在弯的或者复杂的地方会取更多的点。

所以,遇到不太平滑的函数的时候,就应该 Plot 出来,再把点的数据导出,然后用 tikz 来画,也就相当于将 MMC 上的函数图象复制过去了。
接着上面,用如下命令导出:
  1. Export["D:/1.txt", tab1, "Table"]
复制代码
然后在 latex 里:
  1. \tikz[line join=round]{
  2. \draw[->](-0.5,0)--(5.5,0);\draw[->](0,-1.2)--(0,1.2);
  3. \draw plot file {D:/1.txt};
  4. }
复制代码
效果:
QQ截图20180624224925.png
与 MMC 上的就一样了。另外,这里 \tikz 后加的 line join=round 是为了不起尖。


以上讲的都是连续函数,而对于不连续的,或者分段函数,还需要讨论,时间关系,下次再说。
分享到: QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
$\href{https://kuingggg.github.io/}{\text{About Me}}$

现在开始扯不连续的。

先来个简单的例子:画反比例函数。(画这个当然直接 plot (\x,1/\x) 就行了,这里用取点法来画它纯粹是为了示范)

用 Table 取点法,需要分两段来取:
  1. fb1 = Table[{x, 1/x}, {x, -3, -1/3, 0.05}];
  2. fb2 = Table[{x, 1/x}, {x, 3, 1/3, -0.05}];
  3. fb = Union[fb1, fb2];
  4. Export["D:/fb.txt", fb, "Table"]
复制代码
然后在 latex 里
\draw plot file {D:/fb.txt};
结果你应该能猜到:
QQ截图20180625181812.png
两段的头尾被连起了,那怎么让它断开?有两种方法:

法一:打开 D 盘的 fb.txt,找到该断开的地方,插入一空行即可,如下图所示:
QQ截图20180625182118.png

法二:将上面代码中的 fb = Union[fb1, fb2]; 改成
  1. fb = Join[fb1, {{}}, fb2];
复制代码
就是在两堆数之间插入一个空的 {},导出后就会有那个空行了(你可以打开 fb.txt 来看是不是)。

显然法二更好,完成后在 latex 里再次编译即可,效果图就不贴了。


那如果碰到段数很多的,上面那样处理也挺麻烦,还是来试试 Plot 出来再导出数据吧。

这回画正切函数,画多几个周期。

先在 MMC 里画出来看看:
  1. tg = Plot[Tan[x], {x, -2 Pi, 2 Pi}, PlotRange -> {-4, 4}, PlotPoints -> 3]
复制代码
(为了方便观察,这里我设了 PlotPoints -> 3 让数据少些)
QQ截图20180625213147.png
同样地,用 InputForm 来看看数据如何:
QQ截图20180625192140.png
我们发现,由于曲线有几段,所以这里有几个 Line,故此,如果还是像 1# 那样用 InputForm[tg][[1, 1, 1, 1, 3, 1, 2, 1]] 的话只能提取到其中一段,要各段都提取出来,得先知道有多少个 Line,所以:
  1. ds = Length[InputForm[tg][[1, 1, 1, 1, 3, 1]]]
复制代码
得出结果是 6,这说明有 5 段(因为 Line 之前还有一项),与实际相符,然后:
  1. Do[tgn[n] = Append[InputForm[tg][[1, 1, 1, 1, 3, 1, n, 1]], {}], {n, 2, ds}]
复制代码
上述命令将各段的数据储存在 tgn[n] 中,并且我还在每段最后追加了一个空的 {},导出时就会有空行了。
(同样需要注意,以上几步的代码与 MMC 的版本有关。)
最后把它们合起来并导出:
  1. tgx = Flatten[Table[tgn[n], {n, 2, ds}], 1];
  2. Export["D:/tg.txt", tgx, "Table"]
复制代码
导出完成,在 latex 里:
  1. \tikz[scale=0.5]{
  2. \draw[->](-7,0)--(7,0);\draw[->](0,-4)--(0,4);
  3. \draw plot file {D:/tg.txt};
  4. }
复制代码
效果:
QQ截图20180625214559.png

别看我扯了那么多好像很麻烦的样子,其实整理起来,也就几行格式固定的代码。
比如,我现在想画 `\frac12\sec x` 在 `(0,2\pi)` 上的图,则在 MMC 里运行以下代码:
  1. tu = Plot[Sec[x]/2, {x, 0, 2 Pi}, PlotRange -> {-2, 2}, PlotPoints -> 3]
  2. ds = Length[InputForm[tu][[1, 1, 1, 1, 3, 1]]];
  3. Do[tun[n] = Append[InputForm[tu][[1, 1, 1, 1, 3, 1, n, 1]], {}], {n, 2, ds}];
  4. tux = Flatten[Table[tun[n], {n, 2, ds}], 1];
  5. Export["D:/tu.txt", tux, "Table"]
复制代码
然后在 latex 里:
  1. \tikz{
  2. \draw plot file {D:/tu.txt};
  3. \draw (0,-2) rectangle (2*pi,2);
  4. }
复制代码
效果:
QQ截图20180625215030.png

当然,实际当中 Plot 的那段代码可能要调整几次,因为通常都要控制图形的范围,才能使得 tikz 画出来比较好看,但这至少比在 latex 里调试好多了,总之,在这已经算是很方便了!
$\href{https://kuingggg.github.io/}{\text{About Me}}$

TOP

回复 4# abababa

以前我也是这样画的,前面那些方法我也是这两天才了解。
你试试加个 [xscale=5] 拉长来看看就知道在原点附近的点数其实也是不太够的。
PS、我上面画的是 sin(10/x),也是为了拉长看得清楚些。

tikz 的 plot (\x,f(\x)) 的画法实际上也是均匀取点然后连起来的,和上面的 Table 取点法本质上一样。
但 tikz 很容易会因为数值过大而报错(Dimension too large),毕竟它不是专业数学软件,只是排版的,所以有时还是得靠其他工具来辅助。

TOP

回复 3# kuing

谢谢。看来还是得根据具体的点来一个一个画,tikz不能自动推导出那些点应该在哪里。不过$\sin(\frac{1}{x})$这个,看过网上的一个画法:
\draw[domain=0.01:1,samples=500] plot (\x, {sin((1/\x)r)});
看着还行。

TOP

还是再说说 Table 取点法先。
上面说到 Table 取点法有时不够好是因为取点分布均匀,那其实我们也可以手动调整分布,像 sin(1/x) 这种既然已经知道越近原点就越需要多取点,那就先构造一个数集比如:
  1. xs = Union[Range[0.01, 0.2, 0.001], Range[0.2, 0.5, 0.002], Range[0.5, 1, 0.005], Range[1, 2.5, 0.02], Range[2.5, 5, 0.1]];
复制代码
意思就是在区间 [0.01,0.2] 内取步长为 0.001 的数,[0.2,0.5] 内步长 0.002 等等,再并在一起,这时用 Length[xs] 可知共有 541 个数,然后就可以取点了,展示一下这样取法出来的点是怎么样的:
  1. test1xx = Table[{x, Sin[10/x]}, {x, xs}];
  2. ListPlot[test1xx, PlotRange -> All]
复制代码
(这里必须加 PlotRange -> All,不然右边它不画,不知为什么,可能是取的点太疏了?)
QQ截图20180625154617.png
这样,再导出给 tikz 画,出来的效果是很好的:
QQ截图20180625154952.png
$\href{https://kuingggg.github.io/}{\text{About Me}}$

TOP

提取 InputForm[test1] 的数组时的那串数字果然与 MMC 的版本有关,在另一台电脑上的 mathematica7.0 中就应该改为
tab1 = InputForm[test1][[1, 1, 1, 3, 2, 1]]

TOP

返回列表 回复 发帖