Варіант бігової доріжки з точною кінцевою точкою та нульовою кінцевою швидкістю


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
Вперше я розпочав щедрість - сподіваємось, що цей м'яч котиться (а ще краще: приїжджайте за кермом машини :-)


@Mego: Але ... подумавши про це: я не впевнений, чи варто мені додавати програму хоча б з двох причин: По-перше, в оригінальний виклик вона також не була включена, по-друге, вона, наприклад, містить підпрограми, що входять до складу виклик (наприклад, виявлення зіткнення), щоб це зіпсувало частину задоволення ... Мені доведеться спати на ньому ...
vonjd

1
Чи дійсно програмі потрібно обчислити шлях, чи я можу заздалегідь просто обчислити оптимальний шлях, а потім опублікувати щось подібне print "(10,42)\n(62,64)..."?
Ловджо

@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()

Для виконання: GUI, який дозволяє намалювати початковий шлях, щоб отримати орієнтовне відчуття напрямку. Все краще, ніж випадковий вибірки з 14-мірного простору


Молодці! Здається, що 17 кроків - це мінімум - як би ви змінили свою програму, щоб знайти рішення з цією додатковою інформацією?
фондж

О, дорогі: Моя програма показує, що ви не закінчуєтесь на (320,220), але на (320,240) - перевірте, будь ласка,
від

1
Уопс, оновив рішення, а також перекомпонував його до 24 етапів. Тонка настройка вручну тривіально проста, дивлячись на картинку, автоматизуючи її для роботи із загальним випадком - не так вже й багато
DenDenDo
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.