Biến thể của đường đua với điểm kết thúc chính xác và vận tốc đầu cuối bằng không


9

Giới thiệu

Thử thách là một biến thể rất thú vị của đường đua trò chơi và hai thử thách đó:

Nguồn gốc của thử thách này là ở đây (bằng tiếng Đức): c't-Racetrack

Thử thách này đặc biệt thú vị (và khác với hai thử thách nêu trên) bởi vì nó kết hợp một không gian tìm kiếm khổng lồ với một số điều kiện chính xác phải được đáp ứng. Bởi vì các kỹ thuật tìm kiếm toàn diện không gian tìm kiếm khổng lồ rất khó sử dụng, vì các điều kiện chính xác, các phương pháp gần đúng cũng không dễ sử dụng. Do sự kết hợp độc đáo này (cộng với trực giác cơ bản từ vật lý), vấn đề rất hấp dẫn (và mọi thứ liên quan đến xe đua đều hấp dẫn ;-)

Thử thách

Có một cái nhìn về đường đua sau đây ( nguồn ):

nhập mô tả hình ảnh ở đây

Bạn phải bắt đầu (120,180)và kết thúc chính xác tại (320,220)("Ziel" bằng tiếng Đức) mà không chạm vào một trong các bức tường.

Chiếc xe được điều khiển bởi các vectơ gia tốc có dạng (a_x,a_y)- như một ví dụ:

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

Số đầu tiên là gia tốc cho vectơ x, số thứ hai cho vectơ y. Chúng phải là số nguyên vì bạn chỉ được phép sử dụng các điểm nguyên trên lưới. Ngoài ra, các điều kiện sau đây phải được đáp ứng:

a_x^2 + a_y^2 <= 100,

có nghĩa là gia tốc theo bất kỳ hướng nào phải ở dưới hoặc bằng 10.

Để xem nó hoạt động như thế nào, hãy xem hình ảnh sau đây ( nguồn ):

nhập mô tả hình ảnh ở đây

Ví dụ: Bắt đầu từ (120,180)bạn tăng tốc theo 8hướng x và theo hướng -6y. Đối với bước tiếp theo, đây là vận tốc của bạn, nơi bạn thêm gia tốc của mình (10,0)để có được (chính xác về mặt vật lý) chuyển động kết quả tiếp theo của bạn (đến điểm (146,168). Chuyển động kết quả là điều quan trọng khi kiểm tra xem bạn có chạm vào một trong các bức tường hay không. bạn lại thêm vectơ gia tốc tiếp theo vào vận tốc hiện tại của mình để có được chuyển động tiếp theo, v.v. Vì vậy, ở mỗi bước xe của bạn có một vị trí và vận tốc. (Trong hình minh họa phía trên mũi tên màu xanh là cho vận tốc, mũi tên màu cam để tăng tốc và mũi tên đỏ sẫm cho chuyển động kết quả.)

Như một điều kiện bổ sung, bạn phải có (0,0)vận tốc đầu cuối khi bạn đang ở điểm kết thúc (320,220).

Đầu ra phải là một danh sách các vectơ gia tốc ở dạng đã nói ở trên.

Người chiến thắng là người cung cấp một chương trình tìm ra giải pháp có ít vectơ gia tốc nhất.

Tiebreaker
Ngoài ra, sẽ thật tuyệt nếu bạn có thể chỉ ra rằng đây là một giải pháp tối ưu và liệu đây có phải là giải pháp tối ưu duy nhất hay có một số giải pháp tối ưu (và đó là những giải pháp tối ưu).

Sẽ tốt hơn nếu bạn có thể đưa ra một phác thảo chung về cách thuật toán của bạn hoạt động và nhận xét mã để chúng tôi có thể hiểu nó.

Tôi có một chương trình kiểm tra xem bất kỳ giải pháp cụ thể nào là hợp lệ và tôi sẽ đưa ra phản hồi.

Phụ lục
Bạn có thể sử dụng bất kỳ ngôn ngữ lập trình nào nhưng tôi sẽ rất vui mừng nếu ai đó sử dụng R vì tôi sử dụng nó rất nhiều trong công việc hàng ngày và bằng cách nào đó đã quen với nó :-)

Phụ lục II
Lần đầu tiên tôi bắt đầu tiền thưởng - hy vọng điều này sẽ khiến trái bóng lăn (hoặc tốt hơn: có được lái xe ô tô :-)


@Mego: Tuy nhiên ... đã nghĩ về nó: Tôi không chắc liệu tôi có nên thêm chương trình vì ít nhất hai lý do hay không: Thứ nhất, trong thử thách ban đầu, nó không được bao gồm, thứ hai, ví dụ, nó chứa các thói quen là một phần của thử thách (ví dụ như phát hiện va chạm) vì vậy nó sẽ phá hỏng một phần của cuộc vui ... Tôi sẽ phải ngủ trên đó ...
vonjd

1
Chương trình có thực sự cần tính toán đường dẫn hay tôi chỉ có thể tính toán đường dẫn tối ưu trước, sau đó đăng một cái gì đó như thế print "(10,42)\n(62,64)..."nào?
Loovjo

@Loovjo: Không, chương trình phải tự tính toán đường dẫn, vì vậy trí thông minh phải được đưa vào chương trình, không chỉ là thói quen đầu ra.
vonjd

Câu trả lời:


4

Python, 24 bước (đang tiến hành)

Ý tưởng là giải quyết vấn đề liên tục trước tiên, giảm đáng kể không gian tìm kiếm và sau đó định lượng kết quả vào lưới (bằng cách làm tròn đến điểm lưới gần nhất và tìm kiếm 8 ô vuông xung quanh)

Tôi tham số đường dẫn dưới dạng tổng của các hàm lượng giác (không giống như đa thức, chúng không phân kỳ và dễ kiểm tra hơn). Tôi cũng điều khiển vận tốc trực tiếp thay vì gia tốc, bởi vì việc thực thi điều kiện biên dễ dàng hơn bằng cách đơn giản là nhân một hàm trọng số có xu hướng về 0 ở cuối.
Hàm mục tiêu của tôi bao gồm điểm
-exponential để tăng tốc> 10
điểm đa thức cho khoảng cách euclide giữa điểm cuối và điểm
không đổi cao nhất của mục tiêu cho mỗi giao lộ với một bức tường, giảm dần về các cạnh của bức tường

Để giảm thiểu điểm số, tôi ném tất cả vào tối ưu hóa Nelder-Mead và đợi vài giây. Thuật toán luôn thành công trong việc đi đến cuối cùng, dừng lại ở đó và không vượt quá gia tốc tối đa, nhưng nó có vấn đề với các bức tường. Con đường hoặc dịch chuyển qua các góc và bị kẹt ở đó, hoặc dừng lại bên cạnh một bức tường với mục tiêu ngay bên kia (ảnh trái)
nhập mô tả hình ảnh ở đây

Trong quá trình thử nghiệm, tôi đã may mắn và tìm thấy một con đường bị vặn vẹo theo một cách đầy hứa hẹn (ảnh phải) và sau khi điều chỉnh các tham số, tôi có thể sử dụng nó như một dự đoán bắt đầu để tối ưu hóa thành công.

Lượng tử hóa
Sau khi tìm thấy một đường dẫn tham số, đã đến lúc xóa các dấu thập phân. Nhìn vào vùng lân cận 3x3 làm giảm không gian tìm kiếm từ khoảng 300 ^ N xuống còn 9 ^ N, nhưng nó vẫn quá lớn và nhàm chán để thực hiện. Trước khi tôi đi trên con đường này, tôi đã thử thêm thuật ngữ "Snap to Grid" vào hàm mục tiêu (các phần được nhận xét). Hơn một trăm bước tối ưu hóa với mục tiêu được cập nhật và làm tròn đơn giản là đủ để có được giải pháp.

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

Số lượng các bước đã được cố định và không phải là một phần của tối ưu hóa, nhưng vì chúng tôi có một mô tả phân tích về đường dẫn, (và vì gia tốc tối đa thấp hơn 10), chúng tôi có thể sử dụng lại làm điểm bắt đầu để tối ưu hóa thêm với số lượng nhỏ hơn dấu thời gian

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

Việc cần làm: GUI cho phép bạn vẽ một đường dẫn ban đầu để có được cảm nhận rõ ràng về hướng. Bất cứ điều gì tốt hơn là lấy mẫu ngẫu nhiên từ không gian 14 chiều


Làm tốt! Dường như 17 bước là tối thiểu - bạn sẽ thay đổi chương trình của mình như thế nào để tìm giải pháp với thông tin bổ sung này?
vonjd

Ôi trời: Chương trình của tôi cho thấy bạn không kết thúc vào lúc (320.220) nhưng vào lúc (320.240) - bạn có vui lòng kiểm tra xem không
vonjd

1
Rất tiếc, đã cập nhật giải pháp, cũng chia nhỏ lại thành 24 bước. Tinh chỉnh bằng tay rất dễ dàng bằng cách nhìn vào hình ảnh, tự động hóa nó để hoạt động với một trường hợp chung - không quá nhiều
DenDenDo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.