정확한 마무리 지점과 제로 터미널 속도를 가진 다양한 경마장


9

소개

도전은 게임 경마장 과 그 두 가지 도전 의 매우 흥미로운 변형입니다 .

이 도전의 근원은 여기 (독일어) : c't-Racetrack

이 문제는 거대한 검색 공간과 충족해야 할 정확한 조건이 결합되어 있기 때문에 특히 흥미 롭습니다. 방대한 검색 공간으로 인해 완전한 검색 기술을 사용하기가 어렵습니다. 정확한 조건으로 인해 대략적인 방법도 쉽게 사용할 수 없습니다. 이 독특한 조합 (물리학의 근본적인 직관)으로 인해 문제는 흥미 롭습니다 (레이싱 카와 관련된 모든 것은 어쨌든 매력적입니다.

도전

다음 경마장 ( source )을 살펴보십시오 .

여기에 이미지 설명을 입력하십시오

벽 중 하나를 건드리지 않고 (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.

작동 방식을 보려면 다음 그림 ( source )을 살펴보십시오 .

여기에 이미지 설명을 입력하십시오

예를 들어 : x 방향 및 y 방향으로 (120,180)가속합니다 . 다음 단계의 속도는 다음 결과 움직임을 얻기 위해 가속 을 추가하는 속도입니다 (점) . 결과 움직임은 벽 중 하나를 터치했는지 여부를 검사 할 때 계산됩니다. 다음 가속도 벡터를 현재 속도에 다시 추가하여 다음 움직임 등을 얻습니다. 따라서 모든 단계에서 자동차는 위치와 속도를 갖습니다. (위의 그림에서 파란색 화살표는 속도, 주황색 화살표는 가속 및 진한 빨간색 화살표가 표시됩니다.)8-6(10,0)(146,168)

추가 조건으로 (0,0)마무리 지점에있을 때 터미널 속도 를 가져야 합니다 (320,220).

출력은 위에서 언급 한 형태의 가속도 벡터 목록이어야합니다.

승자는 가장 적은 가속도 벡터를 가진 솔루션을 찾는 프로그램을 제공하는 사람입니다.

Tiebreaker
또한 이것이 최적의 솔루션임을 보여줄 수 있고 이것이 유일한 최적 솔루션인지 또는 여러 최적 솔루션이 있는지 여부를 보여줄 수 있다면 좋을 것입니다.

알고리즘의 작동 방식에 대한 일반적인 개요를 제공하고 코드를 이해할 수 있도록 주석을 달 수 있다면 좋을 것입니다.

주어진 솔루션이 유효한지 확인하는 프로그램이 있으며 피드백을 줄 것입니다.

부록
어떤 프로그래밍 언어라도 사용할 수 있지만, 누군가가 R을 하루 종일 많이 사용하고 어떤 식 으로든 익숙해 져서 R을 사용한 경우 특히 기뻐할 것입니다.

부록 II
처음으로 현상금을 시작했습니다-희망적으로 이것은 공 구르기입니다 (또는 더 좋습니다 : 자동차 운전하기 :-)


@ 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으로 줄어 들지만 여전히 구현하기에는 너무 크고 지루합니다. 이 길로 내려 가기 전에 목적 함수 (주석 부분)에 "Snap to Grid"용어를 추가하려고했습니다. 업데이트 된 목표와 단순히 반올림으로 수백 가지의 최적화 단계로 솔루션을 얻는 데 충분했습니다.

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

오 이런 : 당신이 (320220)에서 종료하지 않지만에서 (320240)이 있음을 내 프로그램 쇼 - 당신이 있는지 확인하십시오 것
vonjd

1
whoops는 솔루션을 업데이트하고 24 단계로 리샘플링했습니다. 그다지 - 손으로 미세 조정은 하찮게 쉽게 일반적인 경우와 함께 작동하도록 자동화, 그림을보고입니다
DenDenDo
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.