如何计算数据点在交叉点之外继续的方式?

时间:2014-07-03 08:56:16

标签: python statistics scipy physics interpolation

假设你有两个来自计算的数据值数组,你可以用连续的,可微分的函数建模。两条"线"数据点在(至少)一点处相交,现在的问题是这些数据集背后的函数是否实际交叉或反交叉。

下图显示了我所知道的情况(从其背后的物理学知识),在上面的两个"接触点"黄色和绿色的线条实际上应该"切换颜色",而在较低的一个方面,两个功能都是彼此不同的方式:

Intersecting data crosses and anti-crosses

提供更容易的玩具套装"数据,例如:

import matplotlib.pyplot as plt
import numpy as np

x=np.arange(-10,10,.5)
y1=[np.absolute(i**3)+100*np.absolute(i) for i in x]
y2=[-np.absolute(i**3)-100*np.absolute(i) for i in x][::-1]

plt.scatter(x,y1)
plt.scatter(x,y2,color='r')

plt.show()

哪个应产生以下图像:

Two intersecting datasets

现在我怎么能推断数据背后的趋势是否正在交叉(所以 lower 左边的数据继续向 upper 右边)或反交叉(如用上面的颜色表示, lower 左边的数据继续到 lower 右边)?

到目前为止,我能够找到"联络点"通过查看它们之间差异的导数,在这些数据集之间,大致如下:

closePoints=np.where(np.diff(np.diff(array_A - array_B) > 0))[0] + 1 

(用scipy&c; cKDTree这样的东西评估可能会更快。)

我是否应继续(可能效率非常低)检查交叉路口两侧的导数?或者我可以以某种方式检查左侧数据的推断是否更适合穿越或反交叉?

2 个答案:

答案 0 :(得分:3)

我将问题理解为:

  • 您在2D平面中有两个点序列。
  • 真实曲线可以通过序列连续点之间的直线近似。
  • 您想知道两条曲线相交的频率和位置(不仅相互接触,而且相互交叉)(多边形交叉点)。

潜在的解决方案是:

  • 您可以查看一条曲线的线段与另一条曲线的线段的每个组合。
  • 线段边界框重叠的组合可能包含交叉点。
  • 您解决线性方程系统以计算两条线之间的交叉点是否发生
  • 如果方程式系统没有解决方案,则线条是平行的但不重叠,忽略这种情况
  • 如果一个解决方案检查它是否真正在段内,如果是,则记录此交叉点
  • 在无限多个交叉点的情况下,线条是相同的。这也不是真正的交叉,可以被驳回。
  • 对所有线段组合执行此操作并消除双重情况,即两条曲线在段开始或结束处相交的位置

让我提供一些详细信息

如何检查片段的两个边界框(矩形)是否重叠,以使这些片段可能相交?

一个矩形的最小x / y值必须小于另一个矩形的最大x / y值。这必须适用于两者。

如果你有两个片段,你如何解决交叉点?

假设A段有两个点(x1,y1)和(x2,y2),段B有两个点(x2,y3)和(x4,y4)。

然后你只需要设置两个参数化的线方程:

(x1,y1)+ t *(x2 - x1,y2 - y1)=(x3,y3)+ q *(x4 - x3,y4 - y3)

你需要找到[0,1]中t或q的所有解。相应的线性方程系统可能是排名不足或根本无法解决,最好是使用一次性完成所有事情的通用求解器(我选择numpy.linalg.lstsq)。

共享共同点的曲线

令人惊讶的是,在两条曲线的分割中,一个点很常见。然而,困难在于真正的交叉点与接触点的正确决定。解决方案是计算公共点周围两条曲线的两个相邻段的角度(给出4个角度),并查看角度的顺序。如果两条曲线在相等的点周围交替出现,那么它就是一个交点,否则它就不是。

Example of intersection vs. contact point

根据您的数据代码示例

import math
import matplotlib.pyplot as plt
import numpy as np

def intersect_curves(x1, y1, x2, y2):
    """
        x1, y1 data vector for curve 1
        x2, y2 data vector for curve 2
    """

    # number of points in each curve, number of segments is one less, need at least one segment in each curve
    N1 = x1.shape[0]
    N2 = x2.shape[0]

    # get segment presentation (xi, xi+1; xi+1, xi+2; ..)
    xs1 = np.vstack((x1[:-1], x1[1:]))
    ys1 = np.vstack((y1[:-1], y1[1:]))
    xs2 = np.vstack((x2[:-1], x2[1:]))
    ys2 = np.vstack((y2[:-1], y2[1:]))

    # test if bounding-boxes of segments overlap
    mix1 = np.tile(np.amin(xs1, axis=0), (N2-1,1))
    max1 = np.tile(np.amax(xs1, axis=0), (N2-1,1))
    miy1 = np.tile(np.amin(ys1, axis=0), (N2-1,1))
    may1 = np.tile(np.amax(ys1, axis=0), (N2-1,1))
    mix2 = np.transpose(np.tile(np.amin(xs2, axis=0), (N1-1,1)))
    max2 = np.transpose(np.tile(np.amax(xs2, axis=0), (N1-1,1)))
    miy2 = np.transpose(np.tile(np.amin(ys2, axis=0), (N1-1,1)))
    may2 = np.transpose(np.tile(np.amax(ys2, axis=0), (N1-1,1)))
    idx = np.where((mix2 <= max1) & (max2 >= mix1) & (miy2 <= may1) & (may2 >= miy1)) # overlapping segment combinations

    # going through all the possible segments
    x0 = []
    y0 = []
    for (i, j) in zip(idx[0], idx[1]):
        # get segment coordinates
        xa = xs1[:, j]
        ya = ys1[:, j]
        xb = xs2[:, i]
        yb = ys2[:, i]
        # ax=b, prepare matrices a and b
        a = np.array([[xa[1] - xa[0], xb[0] - xb[1]], [ya[1] - ya[0], yb[0]- yb[1]]])
        b = np.array([xb[0] - xa[0], yb[0] - ya[0]])
        r, residuals, rank, s = np.linalg.lstsq(a, b)
        # if this is not a
        if rank == 2 and not residuals and r[0] >= 0 and r[0] < 1 and r[1] >= 0 and r[1] < 1:
            if r[0] == 0 and r[1] == 0 and i > 0 and j > 0:
                # super special case of one segment point (not the first) in common, need to differentiate between crossing or contact
                angle_a1 = math.atan2(ya[1] - ya[0], xa[1] - xa[0])
                angle_b1 = math.atan2(yb[1] - yb[0], xb[1] - xb[0])

                # get previous segment
                xa2 = xs1[:, j-1]
                ya2 = ys1[:, j-1]
                xb2 = xs2[:, i-1]
                yb2 = ys2[:, i-1]
                angle_a2 = math.atan2(ya2[0] - ya2[1], xa2[0] - xa2[1])
                angle_b2 = math.atan2(yb2[0] - yb2[1], xb2[0] - xb2[1])

                # determine in which order the 4 angle are
                if angle_a2 < angle_a1:
                    h = angle_a1
                    angle_a1 = angle_a2
                    angle_a2 = h
                if (angle_b1 > angle_a1 and angle_b1 < angle_a2 and (angle_b2 < angle_a1 or angle_b2 > angle_a2)) or\
                        ((angle_b1 < angle_a1 or angle_b1 > angle_a2) and angle_b2 > angle_a1 and angle_b2 < angle_a2):
                    # both in or both out, just a contact point
                    x0.append(xa[0])
                    y0.append(ya[0])
            else:
                x0.append(xa[0] + r[0] * (xa[1] - xa[0]))
                y0.append(ya[0] + r[0] * (ya[1] - ya[0]))

    return (x0, y0)

# create data

def data_A():
    # data from question (does not intersect)
    x1 = np.arange(-10, 10, .5)
    x2 = x1
    y1 = [np.absolute(x**3)+100*np.absolute(x) for x in x1]
    y2 = [-np.absolute(x**3)-100*np.absolute(x) for x in x2][::-1]
    return (x1, y1, x2, y2)

def data_B():
    # sine, cosine, should have some intersection points
    x1 = np.arange(-10, 10, .5)
    x2 = x1
    y1 = np.sin(x1)
    y2 = np.cos(x2)
    return (x1, y1, x2, y2)

def data_C():
    # a spiral and a diagonal line, showing the more general case
    t = np.arange(0, 10, .2)
    x1 = np.sin(t * 2) * t
    y1 = np.cos(t * 2) * t
    x2 = np.arange(-10, 10, .5)
    y2 = x2
    return (x1, y1, x2, y2)

def data_D():
    # parallel and overlapping, should give no intersection point
    x1 = np.array([0, 1])
    y1 = np.array([0, 0])
    x2 = np.array([-1, 3])
    y2 = np.array([0, 0])
    return (x1, y1, x2, y2)

def data_E():
    # crossing at a segment point, should give exactly one intersection point
    x1 = np.array([-1,0,1])
    y1 = np.array([0,0,0])
    x2 = np.array([0,0,0])
    y2 = np.array([-1,0,1])
    return (x1, y1, x2, y2)

def data_F():
    # contacting at one segment point, should give no intersection point
    x1 = np.array([-1,0,-1])
    y1 = np.array([-1,0,1])
    x2 = np.array([1,0,1])
    y2 = np.array([-1,0,1])
    return (x1, y1, x2, y2)

x1, y1, x2, y2 = data_F() # select the data you like here

# show example data
plt.plot(x1, y1, 'b-o')
plt.plot(x2, y2, 'r-o')

# call to intersection computation
x0, y0 = intersect_curves(x1, y1, x2, y2)
print('{} intersection points'.format(len(x0)))

# display intersection points in green
plt.plot(x0, y0, 'go')
plt.show() # zoom in to see that the algorithm is correct

我对它进行了广泛的测试,并且应该使大多数(所有)边界情况正确(请参阅代码中的data_A-F)。一些例子:

Example, intersection of a spirale and a straight line

Example, sine and cosine

一些评论

  • 关于线近似的假设是至关重要的。大多数真实曲线可能仅在某种程度上与本地线条近似。由于这两个曲线接近但不与曲线连续采样点距离的距离相交的位置 - 您可能会获得误报或漏报。然后解决方案是使用更多的点或使用关于真实曲线的附加知识。样条曲线可能会提供更低的错误率,但也需要更多的计算,因此更好的曲线采样将是优选的。
  • 当两次相同的曲线并让它们相交时,通常会包含自相交
  • 此解决方案的另一个优点是,它不限于y=f(x)形式的曲线,但它适用于2D中的任意曲线。

答案 1 :(得分:2)

您可以对差函数g(x) = y1(x) - y(2)使用样条插值。找到平方g(x)**2的最小值将是接触点或交叉点。查看一阶和二阶导数,您可以决定它是否为联络点(g(x)具有最小值,g'(x)==0g''(x) != 0)或交叉点(g(x)是固定的点,g'(x)==0g''(x)==0)。

以下代码在约束区间内搜索最小g(x)**2,然后绘制导数。约束区间的使用是通过排除先前点所在的区间来连续找到多个点。

import matplotlib.pyplot as plt
import numpy as np
import scipy.optimize as sopt
import scipy.interpolate as sip

# test functions:
nocrossingTest = True
if nocrossingTest:
    f1 = lambda x: +np.absolute(x**3)+100*np.absolute(x)
    f2 = lambda x: -np.absolute(x**3)-100*np.absolute(x)
else:
    f1 = lambda x: +np.absolute(x**3)+100*x
    f2 = lambda x: -np.absolute(x**3)-100*x

xp = np.arange(-10,10,.5)
y1p, y2p = f1(xp), f2(xp) # test array


# Do Interpolation of y1-y2 to find crossing point:
g12 = sip.InterpolatedUnivariateSpline(xp, y1p - y2p) # Spline Interpolator of Difference
dg12 = g12.derivative() # spline derivative
ddg12 = dg12.derivative() # spline derivative

# Bounded least square fit to find minimal distance
gg = lambda x: g12(x)*g12(x)
rr = sopt.minimize_scalar(gg, bounds=[-1,1]) # search minium in Interval [-1,1]
x_c = rr['x'] # x value with minimum distance
print("Crossing point is at x = {} (Distance: {})".format(x_c, g12(x_c)))



fg = plt.figure(1)
fg.clf()
fg,ax = plt.subplots(1, 1,num=1)
ax.set_title("Function Values $y$")
ax.plot(xp, np.vstack([y1p,y2p]).T, 'x',)
xx = np.linspace(xp[0], xp[-1], 1000)
ax.plot(xx, np.vstack([f1(xx), f2(xx)]).T, '-', alpha=0.5)
ax.grid(True)
ax.legend(loc="best")
fg.canvas.draw()

fg = plt.figure(2)
fg.clf()
fg,axx = plt.subplots(3, 1,num=2)
axx[0].set_title("$g(x) = y_1(x) - y_2(x)$")
axx[1].set_title("$dg(x)/dx$")
axx[2].set_title("$d^2g(x)/dx^2$")
for ax,g in zip(axx, [g12, dg12, ddg12]):
    ax.plot(xx, g(xx))
    ax.plot(x_c, g(x_c), 'ro', alpha=.5)
    ax.grid(True)

fg.tight_layout()
plt.show()

差异函数显示差异不平滑: Interpolation of difference