具有精确终点和零终端速度的赛道变化


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方向和-6y方向加速。对于下一步,这是您的速度,您可以在其中添加加速度(10,0)以获得(物理上正确的)下一个合成运动(指向)(146,168)。合成运动是检查您是否触摸过其中一堵墙的关键。再次将下一个加速度矢量添加到当前速度以得到下一个运动,依此类推。因此,您的汽车在每一步都有一个位置和一个速度。(在上面的说明图中,蓝色箭头表示速度,橙色箭头表示表示加速,深红色箭头表示最终的运动。)

作为附加条件,在(0,0)终点时必须具有最终速度(320,220)

输出必须是上述形式的加速度矢量列表。

获奖者是提供程序的人,该程序可以找到加速度矢量最少的解决方案。

Tiebreaker
另外,如果您可以证明这是一个最佳解决方案,以及这是否是唯一的最佳解决方案或是否有多个最佳解决方案(以及它们是哪种),那将是非常不错的。

如果您可以大致概述算法的工作原理并注释代码,以使我们能够理解它,那也很好。

我有一个程序可以检查任何给定的解决方案是否有效,我会给出反馈。

附录
您可以使用任何编程语言,但是如果有人使用R,我会特别高兴,因为我在日常工作中经常使用R,并且以某种方式习惯了它:-)

附录II
我第一次开始赏金计划-希望这可以使球滚动(或者更好:让汽车行驶:-)


@Mego:但是……经过深思熟虑:我不确定是否由于至少两个原因而添加该程序:首先,在最初的挑战中也没有包含该程序,其次,例如,它包含了属于挑战(例如碰撞检测),这样会破坏一部分乐趣...我将不得不睡觉...
vonjd 2015年

1
该程序实际上是否需要计算路径,或者我可以事先计算出最佳路径,然后发布类似内容print "(10,42)\n(62,64)..."
Loovjo 2015年

@Loovjo:不,程序必须自己计算路径,因此必须在程序中包括智能,而不仅仅是输出例程。
2015年

Answers:


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个步骤-您将如何更改程序以使用此附加信息找到解决方案?
vonjd 2015年

哦,亲爱的:我的程序显示,您的结尾不是(320,220),而是(320,240)-请您检查一下
vonjd 2015年

1
糟糕,更新了解决方案,并将其重新采样到24个步骤。通过查看图片,手动微调非常容易,可以自动将其与一般情况一起使用-并非如此
-DenDenDo 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.