Подтвердить что ты не робот

Эволюционный алгоритм механизма ходьбы Теоса Янсена

Существует голландский художник/инженер, который создал очень сложный механизм ходьбы. Принцип работы можно увидеть здесь:

http://www.strandbeest.com/beests_leg.php

Любопытная часть состоит в том, что он использовал самодельный эволюционный алгоритм для расчета идеальных длин ссылок, которые описаны в нижней части страницы.

Я создал Python script, чтобы визуально проанализировать контактную часть цикла, которая должна иметь два реквизита:

  • Будьте как можно более прямыми, чтобы не качаться вверх и вниз;
  • Быть максимально возможной, чтобы не перетащить одну ногу на другую;

Эти два критерия приведут к влиянию "колеса", когда машина движется линейно вперед, не теряя кинетической энергии.

Возникает вопрос:

"Есть ли у вас предложение простой эволюционной итеративной формулы для оптимизации длины ног (путем вставки правильных мутаций в код ниже), чтобы улучшить траекторию ходьбы, учитывая два вышеуказанных критерия?"

РЕДАКТИРОВАТЬ: некоторые предложения о "правиле подбора" для кандидатов генома:

  • Возьмите "нижнюю часть" (контакт земли) цикла, учитывая, что она соответствует одной трети оборота кривошипа (ум нижней части может иметь не горизонтальный наклон и все еще быть линейным);
  • Применить линейную регрессию на точечные позиции этой части "контакта с землей";
  • Рассчитать вертикальное отклонение от линейной регрессии (наименьшие квадраты?)
  • Рассчитать изменение скорости по разности между одной точкой и предыдущей, параллельно линии регрессии;
  • (необязательно) графические графики этих "функций ошибок", возможно, позволяющие визуально отображать мутанты (boooring...; o).

Вот мой код в Python + GTK, который дает некоторое визуальное представление о проблеме: (EDIT: теперь с параметризованными магическими номерами, подверженными мутации значениями mut)

# coding: utf-8

import pygtk
pygtk.require('2.0')
import gtk, cairo
from math import *

class Mechanism():
    def __init__(s):
        pass

    def assemble(s, angle):

        # magic numbers (unmutated)
        mu = [38, 7.8, 15, 50, 41.5, 39.3, 61.9, 55.8, 40.1, 39.4, 36.7, 65.7, 49]

        # mutations
        mut = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

        # mutated
        mn = [mu[n]+mut[n] for n in range(13)]

        s.A = Point(0,0)
        s.B = Point(-mn[0], -mn[1])

        s.C = fromPoint(s.A, mn[2], angle)
        s.ac = Line(s.A, s.C)

        s.D = linkage(s.C, mn[3], s.B, mn[4])
        s.cd = Line(s.C, s.D)
        s.bd = Line(s.B, s.D)

        s.E = linkage(s.B, mn[5], s.C, mn[6])
        s.be = Line(s.B, s.E)
        s.ce = Line(s.C, s.E)

        s.F = linkage(s.D, mn[7], s.B, mn[8])
        s.df = Line(s.D, s.F)
        s.bf = Line(s.B, s.F)

        s.G = linkage(s.F, mn[9], s.E, mn[10])
        s.fg = Line(s.F, s.G)
        s.eg = Line(s.E, s.G)

        s.H = linkage(s.G, mn[11], s.E, mn[12])
        s.gh = Line(s.G, s.H)
        s.EH = Line(s.E, s.H)


        return s.H


class Point:
    def __init__(self, x, y):
        self.x, self.y = float(x), float(y)
    def __str__(self):
        return "(%.2f, %.2f)" % (self.x, self.y)

class Line:
    def __init__(self, p1, p2):
        self.p1, self.p2 = p1, p2
    def length(self):
        return sqrt((p1.x-p2.x)**2 + (p1.y-p2.y)**2)

def fromPoint(point, distance, angle):
    angle = radians(angle)
    return Point(point.x + distance * cos(angle),
        point.y + distance * sin(angle))

def distance(p1, p2):
    return sqrt( (p1.x - p2.x)**2 + (p1.y - p2.y)**2 )

def ccw(p1, p2, px):
    """ Test if px is at the right side of the line p1p2 """
    ax, ay, bx, by = p1.x, p1.y, p2.x, p2.y
    cx, cy = px.x, px.y
    return (bx-ax)*(cy-ay)-(by-ay)*(cx-ax) < 0

def linkage(p1, l1, p2, l2):
    l1 = float(l1)
    l2 = float(l2)
    dx,dy = p2.x-p1.x, p2.y-p1.y
    d = sqrt(dx**2 + dy**2)                             # distance between the centers
    a = (l1**2 - l2**2 + d**2) / (2*d)                  # distance from first center to the radical line
    M = Point(p1.x + (dx * a/d), p1.y + (dy * a/d))     # intersection of centerline with radical line
    h = sqrt(l1**2 - a**2)                              # distance from the midline to any of the points
    rx,ry = -dy*(h/d), dx*(h/d)
    # There are two results, but only one (the correct side of the line) must be chosen
    R1 = Point(M.x + rx, M.y + ry)
    R2 = Point(M.x - rx, M.y - ry)
    test1 = ccw(p1, p2, R1)
    test2 = ccw(p1, p2, R2)
    if test1:
        return R1
    else:
        return R2




###############################33

mec = Mechanism()
stepcurve = [mec.assemble(p) for p in xrange(360)]

window=gtk.Window()
panel = gtk.VBox()
window.add(panel)
toppanel = gtk.HBox()
panel.pack_start(toppanel)

class Canvas(gtk.DrawingArea):
    def __init__(self):
        gtk.DrawingArea.__init__(self)
        self.connect("expose_event", self.expose)

    def expose(self, widget, event):
        cr = widget.window.cairo_create()
        rect = self.get_allocation()
        w = rect.width
        h = rect.height
        cr.translate(w*0.85, h*0.3)
        scale = 1
        cr.scale(scale, -scale)
        cr.set_line_width(1)

        def paintpoint(p):
            cr.arc(p.x, p.y, 1.2, 0, 2*pi)
            cr.set_source_rgb(1,1,1)
            cr.fill_preserve()
            cr.set_source_rgb(0,0,0)
            cr.stroke()

        def paintline(l):
            cr.move_to(l.p1.x, l.p1.y)
            cr.line_to(l.p2.x, l.p2.y)
            cr.stroke()

        for i in mec.__dict__:
            if mec.__dict__[i].__class__.__name__ == 'Line':
                paintline(mec.__dict__[i])

        for i in mec.__dict__:
            if mec.__dict__[i].__class__.__name__ == 'Point':
                paintpoint(mec.__dict__[i])

        cr.move_to(stepcurve[0].x, stepcurve[0].y)
        for p in stepcurve[1:]:
            cr.line_to(p.x, p.y)
        cr.close_path()
        cr.set_source_rgb(1,0,0)
        cr.set_line_join(cairo.LINE_JOIN_ROUND)
        cr.stroke()

class FootPath(gtk.DrawingArea):
    def __init__(self):
        gtk.DrawingArea.__init__(self)
        self.connect("expose_event", self.expose)

    def expose(self, widget, event):
        cr = widget.window.cairo_create()
        rect = self.get_allocation()
        w = rect.width
        h = rect.height

        cr.save()
        cr.translate(w/2, h/2)

        scale = 20
        cr.scale(scale, -scale)

        cr.translate(40,92)

        twocurves = stepcurve.extend(stepcurve)

        cstart = 305
        cr.set_source_rgb(0,0.5,0)
        for p in stepcurve[cstart:cstart+121]:
            cr.arc(p.x, p.y, 0.1, 0, 2*pi)
            cr.fill()

        cr.move_to(stepcurve[cstart].x, stepcurve[cstart].y)
        for p in stepcurve[cstart+1:cstart+121]:
            cr.line_to(p.x, p.y)
        cr.set_line_join(cairo.LINE_JOIN_ROUND)
        cr.restore()
        cr.set_source_rgb(1,0,0)
        cr.set_line_width(1)
        cr.stroke()




        cr.save()
        cr.translate(w/2, h/2)
        scale = 20
        cr.scale(scale, -scale)
        cr.translate(40,92)

        cr.move_to(stepcurve[cstart+120].x, stepcurve[cstart+120].y)
        for p in stepcurve[cstart+120+1:cstart+360+1]:
            cr.line_to(p.x, p.y)
        cr.restore()
        cr.set_source_rgb(0,0,1)
        cr.set_line_width(1)
        cr.stroke()



canvas = Canvas()
canvas.set_size_request(140,150)
toppanel.pack_start(canvas, False, False)

toppanel.pack_start(gtk.VSeparator(), False, False)

footpath = FootPath()
footpath.set_size_request(1000,-1)
toppanel.pack_start(footpath, True, True)


def changeangle(par):
    mec.assemble(par.get_value()-60)
    canvas.queue_draw()
angleadjust = gtk.Adjustment(value=0, lower=0, upper=360, step_incr=1)
angleScale = gtk.HScale(adjustment=angleadjust)
angleScale.set_value_pos(gtk.POS_LEFT)
angleScale.connect("value-changed", changeangle)
panel.pack_start(angleScale, False, False)


window.set_position(gtk.WIN_POS_CENTER)
window.show_all()
gtk.main()
4b9b3361

Ответ 1

Попробуйте демонстрацию!

enter image description here

Это интересный вопрос, хотя я думаю, что это несколько выходит за рамки Stack Overflow: это не то, что будет решено за несколько минут, поэтому я буду придерживаться схемы и обновить его, если я сделаю какой-то прогресс. Для любого подхода будет три части:

  • Подсчет следа: происходит ли разрыв связи? имеет ли след вид правильной формы? насколько он плоский? насколько гладко движение? он проводит достаточно времени в плоской части?

  • Поиск хороших значений магических чисел. Непонятно, что это должен быть эволюционный алгоритм (хотя я могу понять, почему идея такого алгоритма обратится к Тео Янсену, как он соответствует животной метафоре в его искусстве); возможно, будут полезны другие подходы, такие как локальный поиск (восхождение на холм) или имитированный отжиг.

  • Поиск хороших конфигураций оружия. Вот почему эволюционный подход кажется наиболее эффективным.

В моей демонстрации Javascript/canvas вы можете попробовать разные магические числа, чтобы посмотреть, какие движения вы можете получить (например, CD = 55.4). Там существует целая математическая теория связей, кстати, которая связывает конфигурационные пространства связей с топологическими многообразиями.


Я добавил несколько простых результатов в демо. Оценка земли - это часть цикла, которую нога проводит на земле, и я беру все точки, y-координата которых находится в пределах некоторого допуска нижней точки. Оценка сопротивления - самая большая разница между любыми двумя горизонтальными скоростями, в то время как нога находится на земле. (Он всегда отрицательный, так что более высокие значения = небольшие различия в скоростях = лучше.)

Но здесь, где возникает трудность. Чтобы запрограммировать любой поиск, мне нужно уметь комбинировать эти оценки. Но как мне сбалансировать их друг против друга? Магические числа Янсена дают мне основание: 0,520; dragScore: -0.285. Если я устанавливаю AC = 10, GH = 65, EH = 50, я получаю groundScore: 0,688; dragScore: -0.661. Почти в 70% случаев нога находится на земле. Но взлет - перетаскивание. Это лучше или хуже, чем у Янсена?

Я думаю, что получить реальную инженерную обратную связь для того, чтобы определить хороший результат, будет большая проблема здесь, а не фактический поиск.