Как проверить, когда вектор сделал один оборот в python - PullRequest
3 голосов
/ 10 февраля 2020

Я на самом деле работаю с данными, которые представляют (примерно) шумный круг в 2-мерном пространстве. Я собираю данные по одной точке за раз, и цель - узнать, когда точки обведены.

Для этого я рассматривал каждую последующую точку только как один вектор, который немного повернулся. И чтобы узнать, когда был сделан один оборот (имеется в виду, когда окружность сформирована), я проверяю, когда координаты x и y менялись там, дважды (знак, что вектор сделал 2 * 0.5 поворота = 1 поворот). затем я жду еще один пол оборота, чтобы компенсировать ошибку при запуске. В самом деле, в зависимости от того, где он начался в той четверти пространства, в котором он находился вначале, он, возможно, не прошел весь ход.

Мне не нужно быть предельно точным. Так что это нормально для меня, но мне интересно, есть ли другой метод, который более эффективен и который показывает реальное количество ходов. Это может немного ускорить процесс, так как прибывающие точки уходят медленно (не позволяя мне ждать еще один бесполезный пол оборота)

Один важный момент заключается в том, что я могу использовать только Numpy.

РЕДАКТИРОВАТЬ : больше точности, расстояние между каждой точкой НЕ является регулярным. Сначала круг начинает формироваться медленно, а затем ускоряется. Итак, в начале точки более плотные, чем в конце. Другое дело, что точка (0,0) может даже не содержаться в круге. Наконец, я сказал, что он имеет приблизительно круглую форму, потому что он имеет форму эллипса, но не плохо сформирован, просто шумный.

И извините, но я не могу предоставить данные, по крайней мере, на данный момент. Я скажу вам, если это возможно в течение недели.

Ответы [ 3 ]

3 голосов
/ 10 февраля 2020

Вы можете отслеживать расстояние каждой точки до первой точки, и когда это расстояние достигает минимума, это означает, что круг замкнулся. Ниже показан график расстояний между точками до первой точки по кругу:

Distances

Это соответствующий код для алгоритма:

distances = []
points = [next_point()]
while True:  # break below
    points.append(next_point())
    distances.append(np.linalg.norm(points[-1] - points[0]))
    if len(distances) >= 3:
        left = distances[-2] - distances[-3]
        right = distances[-1] - distances[-2]
        if left < 0 and right > 0:  # minimum detected
            break
del points[-1], distances[-1]  # optionally delete the last point in order to leave the circle open

При тестировании набора данных, который варьирует как угловую разницу, так и радиус, получается следующий результат:

Example

Это полный пример кода:

from math import pi
import random

import matplotlib.pyplot as plt
import numpy as np


def generate():
    angle = pi/4
    angle_upper_lim = 0.002
    while True:
        angle += 2*pi * random.uniform(0.001, angle_upper_lim)
        # radius = random.uniform(0.95, 1.05)
        radius = 1
        yield np.array([3 + radius*np.cos(angle), 5 + radius*np.sin(angle)])
        angle_upper_lim *= 1.03  # make the circle fill faster


generator = generate()


def next_point(n=1):
    """n: number of points per group"""
    return sum(next(generator) for __ in range(n)) / n


distances = []
points = [next_point()]
while True:  # break below
    points.append(next_point())
    distances.append(np.linalg.norm(points[-1] - points[0]))
    if len(distances) >= 3:
        left = distances[-2] - distances[-3]
        right = distances[-1] - distances[-2]
        if left < 0 and right > 0:  # minimum detected
            break
del points[-1], distances[-1]  # optionally delete the last point in order to leave the circle open

fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(10.8, 4.8))
plt.subplots_adjust(wspace=0.11)
ax1.set_title('Data points')
ax1.scatter(*np.stack(points, axis=1), s=5, c=np.arange(len(points)))
ax1.plot(*points[ 0], 's', ms=8, label='First point', color='#2ca02c')
ax1.plot(*points[-1], '*', ms=12, label='Last point', color='#ff7f0e')
ax1.legend(loc='center')

ax2.set(title='Distance of circle points to first point', xlabel='Point #', ylabel='Distance')
ax2.yaxis.tick_right()
ax2.yaxis.set_label_position('right')
ax2.plot(distances, '-o', ms=4)
ax2.plot(len(distances)-1, distances[-1], '*', ms=10, label='circle closed')
ax2.legend()

plt.show()

Изменяющийся радиус

В случае, если радиус точек данных также изменяется, важно выбрать окно достаточного размера, которое будет группировать и усреднять последовательные точки данных для большего стабильность. Функцию next_point можно настроить, используя, например, n=5. Следующий результат получается, раскомментируя изменение радиуса в приведенном выше коде и используя размер окна n=5:

Example varying radius

1 голос
/ 10 февраля 2020

Вы можете использовать первую точку в качестве смещения, которое будет вычтено из всех точек. Это сдвигает край круга к началу координат. Теперь представьте касательную к окружности (в любой точке, но конкретно мы будем использовать начало координат), тогда окружность полностью лежит на одной стороне касательной. Сам тангенс охватывает 180 градусов, и если мы будем идти по кругу, начиная с начала координат, всегда измеряя угол между последовательными векторами, мы будем измерять в общей сложности 180 градусов, как только вернемся к началу координат (в случае, если точки на круге бесконечно малые расстояния). Это позволяет вычислить накопленную сумму углов и остановиться, когда она достигнет 180 градусов (= пи). Теперь, поскольку на самом деле точки имеют конечный интервал, мы пропустим некоторую часть 180 градусов в начале и в конце круга (относительно начала координат). Это означает, что когда мы достигнем 180 градусов, мы наберем немного больше очков, чем необходимо для замыкания круга; OP указывает, что это желаемое поведение, то есть круг должен быть закрыт (лучше, чтобы некоторые перекрывались, а не закрывались).

Это соответствующий код для алгоритма:

angle = 0
offset = next(generator)  # 'generator' produces the data points
points = [next(generator) - offset]
while angle <= pi:
    points.append(next(generator) - offset)
    angle += np.arccos(
        np.dot(points[-2], points[-1]) / 
        (np.linalg.norm(points[-2]) * np.linalg.norm(points[-1]))
    )
del points[-1]  # optionally delete the last point in order to stay below pi

Следующее На графике показан пример, где радиус каждой точки в точности совпадает, меняется только полярный угол:

Example

Это полный пример кода:

from math import pi
import random

import matplotlib.pyplot as plt
import numpy as np


def generate():
    angle = pi/4
    angle_upper_lim = 0.002
    while True:
        angle += 2*pi * random.uniform(0.001, angle_upper_lim)
        radius = 1
        yield np.array([3 + radius*np.cos(angle), 5 + radius*np.sin(angle)])
        angle_upper_lim *= 1.03  # make the circle fill faster


generator = generate()

angle = 0
offset = next(generator)  # 'generator' produces the data points
points = [next(generator) - offset]
while angle <= pi:
    points.append(next(generator) - offset)
    angle += np.arccos(
        np.dot(points[-2], points[-1]) / 
        (np.linalg.norm(points[-2]) * np.linalg.norm(points[-1]))
    )
del points[-1]  # optionally delete the last point in order to stay below pi

fig, ax = plt.subplots(figsize=(4.8, 4.8))
ax.scatter(*np.stack(points, axis=1), s=5, c=np.arange(len(points)))
ax.set_title(f'Total angle: {angle/pi:.2f} pi')
ax.plot(*points[ 0], 's', ms=8, label='First point', color='#2ca02c')
ax.plot(*points[-1], '*', ms=12, label='Last point', color='#ff7f0e')
ax.legend()

plt.show()

Изменяющийся радиус

Если, однако, точки данных также имеют изменяющийся радиус, метод страдает от этих изменений, если смотреть с начала координат. В этом случае последовательные точки данных можно сгруппировать, а затем использовать среднее значение для большей стабильности:

Example with window

Этот код использует группировку данных очки для большей стабильности:

from math import pi
import random

import matplotlib.pyplot as plt
import numpy as np


def generate():
    angle = pi/4
    angle_upper_lim = 0.002
    while True:
        angle += 2*pi * random.uniform(0.001, angle_upper_lim)
        radius = random.uniform(0.95, 1.05)
        yield np.array([3 + radius*np.cos(angle), 5 + radius*np.sin(angle)])
        angle_upper_lim *= 1.03  # make the circle fill faster


generator = generate()


def next_point(n=5):
    """n: number of points per group"""
    return sum(next(generator) for __ in range(n)) / n


angle = 0
offset = next(generator)  # 'generator' produces the data points
points = [next_point() - offset]
while angle <= pi:
    points.append(next_point() - offset)
    angle += np.arccos(
        np.dot(points[-2], points[-1]) / 
        (np.linalg.norm(points[-2]) * np.linalg.norm(points[-1]))
    )
del points[-1]  # optionally delete the last point in order to stay below pi

fig, ax = plt.subplots(figsize=(4.8, 4.8))
ax.scatter(*np.stack(points, axis=1), s=5, c=np.arange(len(points)))
ax.set_title(f'Total angle: {angle/pi:.2f} pi')
ax.plot(*points[ 0], 's', ms=8, label='First point', color='#2ca02c')
ax.plot(*points[-1], '*', ms=12, label='Last point', color='#ff7f0e')
ax.legend(loc='center')

plt.show()
1 голос
/ 10 февраля 2020

Если гарантируется, что каждая новая точка данных будет иметь больший полярный угол, чем предыдущая, т. Е. Круг формируется постепенно, без какой-либо точки «отступления» в процедуре, то для каждой пары последовательных точек можно вычислить угол между ними, и тогда вы можете остановиться, когда сумма достигает двух пи. Например:

angle = 0
points = [next(generator)]  # 'generator' produces the data points
while angle < 2*pi:
    points.append(next(generator))
    angle += np.arccos(
        np.dot(points[-2], points[-1]) / 
        (np.linalg.norm(points[-2]) * np.linalg.norm(points[-1]))
    )
del points[-1]  # optionally delete the last point in order to stay below 2 pi

Вот пример графика с использованием вышеуказанного метода:

Example

И пример кода:

from math import pi
import random

import matplotlib.pyplot as plt
import numpy as np


def generate():
    angle = 0
    angle_upper_lim = 0.002
    while True:
        angle += 2*pi * random.uniform(0.001, angle_upper_lim)
        radius = random.uniform(0.95, 1.05)
        yield radius*np.cos(angle), radius*np.sin(angle)
        angle_upper_lim *= 1.03  # make the circle fill faster


generator = generate()

angle = 0
points = [next(generator)]  # 'generator' produces the data points
while angle < 2*pi:
    points.append(next(generator))
    angle += np.arccos(
        np.dot(points[-2], points[-1]) / 
        (np.linalg.norm(points[-2]) * np.linalg.norm(points[-1]))
    )
del points[-1]  # optionally delete the last point in order to stay below 2 pi

fig, ax = plt.subplots()
ax.scatter(*np.stack(points, axis=1), s=5)
ax.set_title(f'Total angle: {angle/pi:.2f} pi')
ax.plot([0, points[ 0][0]], [0, points[ 0][1]], '--s', ms=8, label='First point', color='#2ca02c')
ax.plot([0, points[-1][0]], [0, points[-1][1]], '--*', ms=12, label='Last point', color='#ff7f0e')
ax.legend()

plt.show()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...