更新:最终解决方案

更新:最终解决方案

我正在使用 TikZ 中的自动机库,我想要获得一个自动机,其节点描述椭圆内接的等边 $n$ 多边形。

我试图将答案调整为上一个问题,即使用代码

\begin{tikzpicture}
    \draw (0,0) ellipse (3 and 1);
    \node[state, accepting] at ($(0,0)+(0:3 and 1)$) {};
    \node[state] at ($(0,0)+(54:3 and 1)$) {};
    \node[state] at ($(0,0)+(75:3 and 1)$) {};
\end{tikzpicture}

但我想避免明确计算节点位置,并且我想知道是否有某种自动获取它们的方法。

最简单的方法是什么?

答案1

这个问题比看起来更难。一种简单(但错误)的方法可能是将 360 度划分为大小相等的扇区,并将节点放置在这些扇区与椭圆的交点处。这仅在椭圆是圆形时才有效。随着椭圆偏心率的增加,结果会变得更糟,如以下动画所示(七个节点):

糟糕的结果

下一个方法(仍然是错误的)是计算椭圆的周长,将其分成等长的椭圆弧,并将节点放置在计算出的点上。这很难做到,因为椭圆弧的长度没有封闭的公式。它必须通过数值近似来计算。

以下 Python 代码计算半径为 3 和 1 的椭圆周长上均匀分布的七个点的坐标。该代码是将以下伪代码直接翻译为 Python3:这个答案。它以适合复制/粘贴到 tikz 循环的格式打印结果。

from math import sin,cos,pi,sqrt
def dp(t, r1, r2):
    return sqrt((r1*sin(t))**2 + (r2*cos(t)**2))

def main(r1, r2, n):
    circ = sum([ dp(t/10000*2*pi, r1, r2) for t in range(10000) ])
    nextPoint = 0
    points = []
    run = 0.0
    for i in range(10000):
        t = i*2*pi/10000
        if n*run/circ >= nextPoint:
            points.append((r1*cos(t), r2*sin(t)))
            nextPoint += 1
        run += dp(t, r1, r2)
    return points


if __name__ == "__main__":
    pts = main(3,1,7)
    print(",".join("%f/%f"%(x,y) for x,y in pts))

运行此代码,输出如下:

3.000000/0.000000,1.423740/0.880212,-0.478610/0.987192,-2.343646/0.624261,-2.342469/-0.624752,-0.474888/-0.987392,1.427057/-0.879615

我将该序列复制粘贴到以下 tikz 代码中:

\begin{tikzpicture}    
    \draw[name path=ellipse, blue] (0,0) ellipse(3 and 1);

    \foreach \x/\y  [count=\n from 0] in {3.000000/0.000000,1.423740/0.880212,-0.478610/0.987192,-2.343646/0.624261,-2.342469/-0.624752,-0.474888/-0.987392,1.427057/-0.879615} {  
       \coordinate[state] (node-\n) at (\x,\y) {}; 
    }

    % Draw nodes and edges 
    \node[state, accepting, fill=white] at (node-0) {0};
    \foreach \n [remember=\n as \previous (initially 0)] in {1,...,6} {
       \node[state, fill=white] at (node-\n) {\n}; 
       \draw[red,thick] (node-\previous) -- (node-\n);
    }
    \draw[thick,red] (node-6) -- (node-0);
\end{tikzpicture}

结果如下:

结果

可以看出,椭圆路径上节点间距相等(蓝色),但是节点之间的距离并不相等(红色)。

更新:最终解决方案

在我最初的回答中,在声明等长椭圆弧并不意味着等距节点后,我放弃了。但后来我痴迷于这个问题,最终得以解决。请注意,没有简单的解决方案。

让我们定义一些符号,椭圆中的每个点 $(x,y)$ 都可以用实数 $t$ 表示,使用函数 $(x,y) = f(t)$,即 $f(t)=(a\cos(t), b\sin(t))$。让我们用 $|p_j-p_i|$ 表示两点 $p_i=(x_i,y_i)$ 和 $p_j=(x_j,y_j)$ 之间的距离,可以计算为 $\sqrt{(x_j-x_i)^2+(y_j-y_i)^2}$。

现在,我们的问题是找到椭圆上等距的 $N$ 个点。此问题可以表述为找到 $N$ 个实值 $t_0,\dots,t_{N-1}$,使得 $|f(t_1)-f(t_0)| = |f(t_2) - f(t_1)| = \dots = |f(t_0)-f(t_{N-1}|$。

因此,我们有 $N$ 个未知数,但只有 $N-1$ 个方程(距离等式)。这意味着有无限个解,它们对应于 $N$ 个点上的不同“旋转”,具体取决于我们将第一个点放在哪里。我们可以固定 $t_0$ 的值,然后有 $N-1$ 个未知数,这些未知数应该是可解的。

问题在于 $N-1$ 方程是非线性的,因此求解并不容易。需要一些数值优化算法。我使用了scipy.optimize.fsolve为此用过。

我编写了一个元函数,它接收椭圆半径的长度(a 和 b)、所需节点数(例如 7)和第一个节点的位置(整数 t_0,可以取从 0 到 2\pi 的任意值)作为参数。利用这些参数,元函数创建一个函数来实现需要求解的方程。然后scipy.optimize.fsolve用于寻找解决方案。

我觉得不适合在这里粘贴这个python脚本的代码,所以我把它粘贴为要旨而是。这个脚本针对不同的 $t_0$ 值求解系统,以便创建以下动画(其中黄色节点表示 $t_0$ 值的“固定”节点,蓝色节点是脚本找到的节点)。

动画片

这是脚本输出的摘录,展示了 $t_0=0$ 的情况生成的 tikz 图形:

\begin{tikzpicture}
\fill[white] (-3.5, -1.5) rectangle (3.5, 1.5);
\draw[blue] (0,0) ellipse(3 and 1);
\foreach \x/\y [count=\n from 0] in {3.000000/0.000000,1.585814/0.848868,-0.056656/0.999822,-1.696726/0.824697,-1.696726/-0.824697,-0.056656/-0.999822,1.585814/-0.8488
68} {
     \node[fill=blue,circle] (node-\n) at (\x,\y) {};
}
\foreach \n [remember=\n as \previous (initially 0)] in {1,...,6} {
   \draw[thick,red] (node-\previous.center) -- (node-\n.center);
}
\node[fill=yellow, circle] at (node-0) {};
\draw[thick,red] (node-6.center) -- (node-0.center);
\end{tikzpicture}

不幸的是,由于使用了,这些都无法在纯 Tex 中实现,甚至在 LuaTeX 中也无法实现scipy

相关内容