Вариант ипподрома с точной финишной точкой и нулевой конечной скоростью

9

Введение

Задача - очень интересный вариант игрового ипподрома и эти две задачи:

Источник этой задачи здесь (на немецком языке): c't-Racetrack

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

Вызов

Посмотрите на следующую трассу ( источник ):

введите описание изображения здесь

Вы должны начать (120,180)и закончить точно в (320,220)(«Ziel» на немецком языке), не касаясь одной из стен.

Автомобиль контролируется векторами ускорения вида (a_x,a_y)- в качестве примера:

(8,-6)
(10,0)
(1,9)

Первое число - ускорение для вектора x, второе - для вектора y. Они должны быть целыми числами, потому что вам разрешено использовать только целые точки на сетке. Дополнительно должно быть выполнено следующее условие:

a_x^2 + a_y^2 <= 100,

Это означает, что ускорение в любом направлении должно быть ниже или равно 10.

Чтобы увидеть, как это работает, взгляните на следующую картинку ( источник ):

введите описание изображения здесь

В качестве примера: начиная с (120,180)ускорения по 8оси X и по -6оси Y. Для следующего шага это ваша скорость, где вы добавляете свое ускорение, (10,0)чтобы получить (физически правильное) ваше следующее результирующее движение (к точке (146,168). Результирующее движение - это то, что имеет значение, когда дело доходит до того, касались ли вы одной из стен. На следующем шаге. Вы снова добавляете свой следующий вектор ускорения к текущей скорости, чтобы получить следующее движение и т. д. Таким образом, на каждом шаге у вашего автомобиля есть положение и скорость. (На иллюстративном рисунке выше синие стрелки обозначают скорость, оранжевые стрелки). для ускорения и темно-красные стрелки для результирующего движения.)

В качестве дополнительного условия у вас должна быть (0,0)конечная скорость, когда вы находитесь на финишной точке (320,220).

Выходными данными должен быть список векторов ускорения в вышеупомянутой форме.

Победителем становится тот, кто предоставляет программу, которая находит решение с наименьшим вектором ускорения.

Tiebreaker
Кроме того, было бы здорово показать, что это оптимальное решение и является ли это единственным оптимальным решением или существует несколько оптимальных решений (и какими они являются).

Также было бы хорошо, если бы вы могли дать общее представление о том, как работает ваш алгоритм, и прокомментировать код, чтобы мы могли его понять.

У меня есть программа, которая проверяет, является ли данное решение действительным, и я буду давать отзывы.

Приложение
Вы можете использовать любой язык программирования, но я был бы особенно рад, если бы кто-то использовал R, потому что я часто использую его в своей повседневной работе и как-то привык к нему :-)

Приложение II
Впервые я учредил награду - надеюсь, что этот мяч зашкаливает (или лучше: за рулем автомобиля :-)

vonjd
источник
@Mego: Тем не менее ... подумав об этом: я не уверен, стоит ли мне добавлять программу по крайней мере по двум причинам: во-первых, в первоначальный вызов она также не была включена, во-вторых, она, например, содержит подпрограммы, являющиеся частью вызов (например, обнаружение столкновений), так что это испортит часть веселья ... Мне придется спать на нем ...
vonjd
1
Нужно ли программе на самом деле вычислять путь, или я мог бы просто заранее рассчитать оптимальный путь, а затем опубликовать что-то вроде print "(10,42)\n(62,64)..."?
Loovjo
@Loovjo: Нет, программа должна рассчитать сам путь, поэтому в программу должен быть включен интеллект, а не просто процедура вывода.
vonjd

Ответы:

4

Python, 24 шага (работа в процессе)

Идея состояла в том, чтобы сначала решить непрерывную задачу, значительно сократив пространство поиска, а затем квантовать результат в сетке (просто округляя до ближайшей точки сетки и просматривая окружающие 8 квадратов)

Я параметризирую путь как сумму тригонометрических функций (в отличие от полиномов, они не расходятся и их легче контролировать). Я также управляю скоростью напрямую, а не ускорением, потому что легче выполнить граничное условие, просто умножив весовую функцию, которая стремится к 0 в конце.
Моя целевая функция состоит из
-экспоненциальной оценки для ускорения> 10-
полиномиальной оценки для евклидова расстояния между последней точкой и целью
-высокой постоянной оценки для каждого пересечения со стеной, уменьшающейся к краям стены

Чтобы минимизировать счет, я добавляю все это в оптимизацию Nelder-Mead и жду несколько секунд. Алгоритму всегда удается дойти до конца, остановиться на нем и не превысить максимальное ускорение, но у него есть проблемы со стенами. Тропа либо телепортируется через углы и застревает там, либо останавливается рядом со стеной с целью прямо напротив (левое изображение)
введите описание изображения здесь

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

Квантование
После нахождения параметрического пути пришло время удалить десятичные точки. Просмотр окрестности 3x3 уменьшает пространство поиска примерно с 300 ^ N до 9 ^ N, но все еще слишком велик и скучен для реализации. Прежде чем идти по этому пути, я попытался добавить термин «Привязать к сетке» к целевой функции (закомментированные части). Чтобы получить решение, потребовалось еще сто шагов оптимизации с обновленной целью и простым округлением.

[(9, -1), (4, 0), (1, 1), (2, 2), (-1, 2), (-3, 4), (-3, 3), (-2 , 3), (-2, 2), (-1, 1), (0, 0), (1, -2), (2, -3), (2, -2), (3, -5 ), (2, -4), (1, -5), (-2, -3), (-2, -4), (-3, -9), (-4, -4), (- 5, 8), (-4, 8), (5, 8)]

Количество шагов было фиксированным и не являлось частью оптимизации, но поскольку у нас есть аналитическое описание пути (и поскольку максимальное ускорение значительно ниже 10), мы можем использовать его в качестве отправной точки для дальнейшей оптимизации с меньшим числом временные шаги

from numpy import *
from scipy.optimize import fmin
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection as LC

walls = array([[[0,0],[500,0]],   # [[x0,y0],[x1,y1]]
        [[500,0],[500,400]],
        [[500,400],[0,400]],
        [[0,400],[0,0]],

        [[200,200],[100,200]],
        [[100,200],[100,100]],
        [[100,100],[200,100]],

        [[250,300],[250,200]],

        [[300,300],[300,100]],
        [[300,200],[400,200]],
        [[300,100],[400,100]],

        [[100,180],[120, 200]], #debug walls
        [[100,120],[120, 100]],
        [[300,220],[320, 200]],
        #[[320,100],[300, 120]],
])

start = array([120,180])
goal = array([320,220])

###################################
# Boring stuff below, scroll down #
###################################
def weightedintersection2D(L1, L2):
    # http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
    p = L1[0]
    q = L2[0]
    r = L1[1]-L1[0]
    s = L2[1]-L2[0]
    d = cross(r,s)
    if d==0: # parallel
        if cross(q-p,r)==0: return 1 # overlap
    else:
        t = cross(q-p,s)*1.0/d
        u = cross(q-p,r)*1.0/d
        if 0<=t<=1 and 0<=u<=1: return 1-0*abs(t-.5)-1*abs(u-.5) # intersect at p+tr=q+us
    return 0

def sinsum(coeff, tt):
    '''input: list of length 2(2k+1), 
    first half for X-movement, second for Y-movement.
    Of each, the first k elements are sin-coefficients
    the next k+1 elements are cos-coefficients'''
    N = len(coeff)/2
    XS = [0]+list(coeff[:N][:N/2])
    XC =     coeff[:N][N/2:]
    YS = [0]+list(coeff[N:][:N/2])
    YC =     coeff[N:][N/2:]
    VX = sum([XS[i]*sin(tt*ww[i]) + XC[i]*cos(tt*ww[i]) for i in range(N/2+1)], 0)
    VY = sum([YS[i]*sin(tt*ww[i]) + YC[i]*cos(tt*ww[i]) for i in range(N/2+1)], 0)
    return VX*weightfunc, VY*weightfunc

def makepath(vx, vy):
    # turn coordinates into line segments, to check for intersections
    xx = cumsum(vx)+start[0]
    yy = cumsum(vy)+start[1]
    path = []
    for i in range(1,len(xx)):
        path.append([[xx[i-1], yy[i-1]],[xx[i], yy[i]]])
    return path

def checkpath(path):
    intersections = 0
    for line1 in path[:-1]: # last two elements are equal, and thus wrongly intersect each wall
        for line2 in walls:
            intersections += weightedintersection2D(array(line1), array(line2))
    return intersections

def eval_score(coeff):
    # tweak everything for better convergence
    vx, vy = sinsum(coeff, tt)
    path = makepath(vx, vy)
    score_int = checkpath(path)
    dist = hypot(*(path[-1][1]-goal))
    score_pos = abs(dist)**3
    acc = hypot(diff(vx), diff(vy))
    score_acc = sum(exp(clip(3*(acc-10), -10,20)))
    #score_snap = sum(abs(diff(vx)-diff(vx).round())) + sum(abs(diff(vy)-diff(vy).round()))
    print score_int, score_pos, score_acc#, score_snap
    return score_int*100 + score_pos*.5 + score_acc #+ score_snap

######################################
# Boring stuff above, scroll to here #
######################################
Nw = 4 # <3: paths not squiggly enough, >6: too many dimensions, slow
ww = [1*pi*k for k in range(Nw)]
Nt = 30 # find a solution with tis many steps
tt = linspace(0,1,Nt)
weightfunc = tanh(tt*30)*tanh(30*(1-tt)) # makes sure end velocity is 0

guess = random.random(4*Nw-2)*10-5
guess = array([ 5.72255365, -0.02720178,  8.09631272,  1.88852287, -2.28175362,
        2.915817  ,  8.29529905,  8.46535503,  5.32069444, -1.7422171 ,
       -3.87486437,  1.35836498, -1.28681144,  2.20784655])  # this is a good start...
array([ 10.50877078,  -0.1177561 ,   4.63897574,  -0.79066986,
         3.08680958,  -0.66848585,   4.34140494,   6.80129358,
         5.13853914,  -7.02747384,  -1.80208349,   1.91870184,
        -4.21784737,   0.17727804]) # ...and it returns this solution      

optimsettings = dict(
    xtol = 1e-6,
    ftol = 1e-6,
    disp = 1,
    maxiter = 1000, # better restart if not even close after 300
    full_output = 1,
    retall = 1)

plt.ion()
plt.axes().add_collection(LC(walls))
plt.xlim(-10,510)
plt.ylim(-10,410)
path = makepath(*sinsum(guess, tt))
plt.axes().add_collection(LC(path, color='red'))
plt.plot(*start, marker='o')
plt.plot(*goal, marker='o')
plt.show()

optres = fmin(eval_score, guess, **optimsettings)
optcoeff = optres[0]    

#for c in optres[-1][::optimsettings['maxiter']/10]:
for c in array(optres[-1])[logspace(1,log10(optimsettings['maxiter']-1), 10).astype(int)]:
    vx, vy = sinsum(c, tt)
    path = makepath(vx,vy)
    plt.axes().add_collection(LC(path, color='green'))
    plt.show()

To Do: GUI, который позволяет вам нарисовать начальный путь, чтобы получить грубое чувство направления. Все лучше, чем случайная выборка из 14-мерного пространства

DenDenDo
источник
Отлично сработано! Кажется, что 17 шагов - это минимум. Как бы вы изменили свою программу, чтобы найти решение с этой дополнительной информацией?
vonjd
О, дорогой: моя программа показывает, что вы не заканчиваете в (320,220), но в (320,240) - пожалуйста, проверьте это
Vonjd
1
Whoops, обновил решение, также обновил его до 24 шагов. Ручная подстройка тривиально проста, если посмотреть на картинку, автоматизировав ее для работы с общим делом - не так уж и много
DenDenDo