使用matplotlib绘制对称流线图

13

我正在尝试使用matplotlib绘制球周围磁场的流线,效果非常好。然而,生成的图像不对称,但它应该是对称的(我觉得)。 enter image description here

以下是用于生成图像的代码。请原谅代码有些冗长,但我认为这比发布无法工作的片段更好。另外,它不太符合Python风格;这是因为我将其从Matlab转换过来,但比我预期的要容易。

from __future__ import division
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle

def cart2spherical(x, y, z):
    r = np.sqrt(x**2 + y**2 + z**2)
    phi = np.arctan2(y, x)
    theta = np.arccos(z/r)
    if r == 0:
        theta = 0
    return (r, theta, phi)

def S(theta, phi):
    S = np.array([[np.sin(theta)*np.cos(phi), np.cos(theta)*np.cos(phi), -np.sin(phi)],
                  [np.sin(theta)*np.sin(phi), np.cos(theta)*np.sin(phi),  np.cos(phi)],
                  [np.cos(theta),             -np.sin(theta),             0]])
    return S

def computeB(r, theta, phi, a=1, muR=100, B0=1):
    delta = (muR - 1)/(muR + 2)
    if r > a:
        Bspherical = B0*np.array([np.cos(theta) * (1 + 2*delta*a**3 / r**3),
                                  np.sin(theta) * (delta*a**3 / r**3 - 1),
                                  0])
        B = np.dot(S(theta, phi), Bspherical)
    else:
        B = 3*B0*(muR / (muR + 2)) * np.array([0, 0, 1])
    return B

Z, X = np.mgrid[-2.5:2.5:1000j, -2.5:2.5:1000j]
Bx = np.zeros(np.shape(X))
Bz = np.zeros(np.shape(X))
Babs = np.zeros(np.shape(X))
for i in range(len(X)):
    for j in range(len(Z)):
        r, theta, phi = cart2spherical(X[0, i], 0, Z[j, 0])
        B = computeB(r, theta, phi)
        Bx[i, j], Bz[i, j] = B[0], B[2]
        Babs[i, j] = np.sqrt(B[0]**2 + B[1]**2 + B[2]**2)

fig=plt.figure()
ax=fig.add_subplot(111)

plt.streamplot(X, Z, Bx, Bz, color='k', linewidth=0.8*Babs, density=1.3,
               minlength=0.9, arrowstyle='-')
ax.add_patch(Circle((0, 0), radius=1, facecolor='none', linewidth=2))
plt.axis('equal')
plt.axis('off')
fig.savefig('streamlines.pdf', transparent=True, bbox_inches='tight', pad_inches=0)

看一下streamplot的参数,这似乎只是选择了错误的流线,但你的数据没问题。 - tacaswell
@tcaswell 我认为根本问题在于边界处的不连续性。在这种情况下,我认为将图形分成两个区域是明智的选择。 - Hooked
4个回答

8

引用自文档:

density : float or 2-tuple
    Controls the closeness of streamlines. When density = 1, 
    the domain is divided into 
    a 25x25 grid—density linearly scales this grid.
    Each cell in the grid can have, at most, one traversing streamline.
    For different densities in each direction, use [density_x, density_y].
所以,在决定流线放置的单元格和问题的对称性之间会出现混叠效应。您需要仔细选择数据的网格大小和密度。
此外,它还对边界框相对于球顶的位置非常敏感。您的球体中心是在数据网格点上还是在数据网格点之间?如果它在一个网格点上,那么包含中心点的方框将与相邻的方框不同。
我不太清楚它是如何决定绘制哪些流线的,但我可以想象它是一种贪婪算法,因此在走向高密度区域和离开密度区域时会给出不同的结果。
明确一下,您的问题不是流线“错误”,它们是有效的流线,而是您认为结果不美观。

如果我理解正确的话,这意味着如果我选择 density = 1.6,那么每个边将有20个单元格,每个单元格将有25个数据点,总共40个单元格。但是图形仍然不对称。 - Psirus
1
正如Psirus所指出的,使密度对称并不能保证图形也是对称的。我认为另一个主要问题是streamplot选择螺旋方式绘制流线的起始点,您可以通过修改_gen_starting_points来自定义这些点。流线也是数值积分的,因此需要确保网格化的数据(来自密度选择)和起始点完全对称。 - James Snyder

8

首先,出于好奇,为什么您想要绘制对称数据?只绘制一半不好吗?

话虽如此,这是一个可能的技巧。您可以像Hooked建议的那样使用掩码数组来绘制其中的一半:

mask = X>0
BX_OUT = Bx.copy()
BZ_OUT = Bz.copy()
BX_OUT[mask] = None
BZ_OUT[mask] = None
res = plt.streamplot(X, Z, BX_OUT, BZ_OUT, color='k', 
           arrowstyle='-',linewidth=1,density=2)

然后你将streamplot的结果保存在res中,提取线条并用相反的X坐标绘制它们。
lines = res.lines.get_paths()
for l in lines:
    plot(-l.vertices.T[0],l.vertices.T[1],'k')

我使用这个技巧从一个2D图中提取出流线和箭头,然后应用3D变换并使用mplot3d绘制。其中一张图片可以在我的问题这里中找到。


5
就数据完整性而言,这个解决方案真的很令人不安。 - tacaswell
我必须同意@tacaswell的观点,我宁愿绘制丑陋的原始数据并增强其视觉效果,也不愿为了创造艺术感的图形而牺牲数据的完整性。 - Willy satrio nugroho

6

使用遮罩来分离两个感兴趣的区域:

mask = np.sqrt(X**2+Z**2)<1

BX_OUT = Bx.copy()
BZ_OUT = Bz.copy()
BX_OUT[mask] = None
BZ_OUT[mask] = None
plt.streamplot(X, Z, BX_OUT, BZ_OUT, color='k', 
               arrowstyle='-', density=2)

BX_IN = Bx.copy()
BZ_IN = Bz.copy()
BX_IN[~mask] = None
BZ_IN[~mask] = None
plt.streamplot(X, Z, BX_IN, BZ_IN, color='r', 
               arrowstyle='-', density=2)

enter image description here

生成的图形不是完全对称的,但是通过给算法一个提示,它比之前更接近对称。通过meshgriddensity参数调整网格的密度,以实现您想要的效果。


2

使用物理学,而不是纯粹的计算机算法。磁场相对于 z(垂直)轴是对称的!因此,您只需要两个 streamplot

plt.streamplot(X, Z, Bx, Bz, color='k', linewidth=0.8*Babs, density=1.3, minlength=0.9, arrowstyle='-')
plt.streamplot(-X, Z, -Bx, Bz, color='k', linewidth=0.8*Babs, density=1.3, minlength=0.9, arrowstyle='-')

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接