正確な終点とゼロの終端速度を備えたレーストラックのバリアント


9

前書き

チャレンジは、ゲームレーストラックの非常に興味深いバリエーションであり、次の2つのチャレンジです。

この課題の出典はこちら(ドイツ語):c't-Racetrack

この課題は、非常に興味深いものです(上記の2つの課題とは異なります)。これは、巨大な検索スペースと、満たさなければならない正確な条件を組み合わせるためです。膨大な検索スペースのため、網羅的な検索手法は使いにくく、正確な条件のため、近似法も簡単に使用できません。このユニークな組み合わせ(および物理学からの基本的な直感)のため、問題は魅力的です(そして、レーシングカーに関連するすべてがとにかく魅力的です;-)

チャレンジ

次の競馬場(ソース)をご覧ください。

ここに画像の説明を入力してください

壁の1つに触れることなく、(120,180)正確に(320,220)(ドイツ語では "Ziel")で開始して終了する必要があります。

車は次の形式の加速度ベクトルによって制御されます(a_x,a_y)-例として:

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

最初の数値はxベクトルの加速度、2番目の数値はyベクトルの加速度です。グリッド上の整数点のみを使用できるため、整数でなければなりません。さらに、次の条件を満たす必要があります。

a_x^2 + a_y^2 <= 100,

つまり、任意の方向の加速度は以下でなければなりません10

どのように機能するかを確認するには、次の図(ソース)をご覧ください。

ここに画像の説明を入力してください

例として、x方向とy 方向に(120,180)加速します。次のステップでは、これは、次の結果の動き(ポイントへ)を取得する(物理的に正しい)加速度を追加する速度です。結果の動きは、壁の1つに触れたかどうかを調べるときに重要になります。次のステップでは次の加速度ベクトルを現在の速度に追加して、次の動きを取得します。したがって、すべてのステップで、車の位置と速度がわかります(上の図の画像では、青い矢印は速度、オレンジの矢印は加速と結果の動きの濃い赤の矢印)8-6(10,0)(146,168)

追加の条件として、(0,0)あなたが終点にいるとき、あなたは最終速度を持っている必要があります(320,220)

出力は、上記の形式の加速度ベクトルのリストでなければなりません。

勝者は、加速度ベクトルが最も少ない解を見つけるプログラムを提供する人です。

タイブレーカー
さらに、これが最適なソリューションであり、これが唯一の最適なソリューションであるかどうか、またはいくつかの最適なソリューションがあるかどうか(そしてそれらがどれであるか)を示すことができればすばらしいでしょう。

また、アルゴリズムがどのように機能するかについての一般的な概要を示し、コードにコメントを付けて、Googleが理解できるようにするとよいでしょう。

特定のソリューションが有効かどうかをチェックするプログラムがあり、フィードバックを提供します。

補遺
任意のプログラミング言語を使用できますが、誰かがRを使用した場合、私はそれを私の日常業務で頻繁に使用し、どういうわけかそれに慣れているので、特に嬉しく思います:-)

補遺II
初めて私は賞金を始めました-うまくいけば、これはボールが転がることです(またはそれ以上:車の運転です:-)


@Mego:まだ...それについて考えたことがある:少なくとも2つの理由でプログラムを追加する必要があるかどうかわからない:最初に、元の課題にも含まれていなかった。チャレンジ(例:衝突検出)なので、楽しみの一部を台無しにしてしまいます...私はそれに眠らなければなりません...
vonjd

1
プログラムは実際にパスを計算する必要がありますか、それとも事前に最適パスを計算してから、次のようなものを投稿するだけprint "(10,42)\n(62,64)..."ですか?
Loovjo、2015年

@Loovjo:いいえ、プログラムはパス自体を計算する必要があるため、出力ルーチンだけでなく、インテリジェンスもプログラムに含める必要があります。
vonjd 2015年

回答:


4

Python、24ステップ(作業中)

アイデアは、最初に継続的な問題を解決し、検索スペースを大幅に削減し、次に結果をグリッドに量子化することです(最も近いグリッドポイントに丸め、周囲の8つの正方形を検索することにより)。

パスを三角関数の合計としてパラメーター化します(多項式とは異なり、それらは発散せず、確認しやすくなります)。また、最後に0になる傾向のある重み関数を乗算するだけで境界条件を適用する方が簡単なため、加速度の代わりに速度を直接制御します。
私の目的関数は、
加速度> 10の
指数スコア-最後のポイントとターゲット間のユークリッド距離の多項式スコア-
壁との各交差の高い一定スコア、壁のエッジに向かって減少

スコアを最小化するために、すべてをNelder-Mead最適化に投入し、数秒待ちます。アルゴリズムは常に最後まで到達し、そこで停止し、最大加速度を超えないようにしますが、壁に問題があります。パスは、コーナーをテレポートしてそこに行き詰まるか、ゴールを横切って壁の横で停止します(左の画像)
ここに画像の説明を入力してください

テスト中に幸運になり、有望な方法で曲がったパスを見つけました(右の画像)。さらにパラメーターを調整した後、最適化を成功させるための最初の推測として使用できます。

量子化
パラメトリックパスを見つけたら、次に小数点を削除します。3x3近傍を見ると、検索スペースが約300 ^ Nから9 ^ Nに減少しますが、それでも実装するには大きすぎて退屈です。この道を進む前に、目的関数(コメント部分)に「グリッドにスナップ」という用語を追加してみました。更新された目標と単純な丸めを使用した最適化の100を超えるステップで、ソリューションを取得できました。

[(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 '13

1
おっと、ソリューションを更新し、24ステップにリサンプルしました。手動で微調整することは、写真を見て、一般的なケースで機能するように自動化することで簡単に行えます-それほどではありません
DenDenDo
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.