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

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

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

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

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

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

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

Вопрос в том:

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

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

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

Вот мой код на 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()

person heltonbiker    schedule 04.07.2011    source источник
comment
Это отличный вопрос, но требует много размышлений! Тем временем я позволил себе перевести ваш код на Javascript, чтобы другие участники Stack Overflow могли поэкспериментировать с этим замечательным механизмом: garethrees.org/2011/07/04/strandbeest/strandbeest.html   -  person Gareth Rees    schedule 04.07.2011
comment
Отличная работа, @Гарет Рис! Я скоро обновлю свой вопрос, чтобы включить некоторые мысли, которые у меня были по этому вопросу. Только одно наблюдение: я думаю, что точки E и F поменялись местами в вашей модели javascript. Кроме того, рабочий механизм состоит из трех механизмов на каждую конечность животного с разницей фаз между ними в 120 градусов (анимация трех одновременных конечностей довольно жуткая!). Большое спасибо за ваш вклад!!   -  person heltonbiker    schedule 04.07.2011
comment
Надписи на моей модели такие же, как и в вашем коде (я преобразовал его довольно механически). Количество необходимых механизмов зависит от количества времени, которое нога проводит на земле. Например, связь Klann проводит половину своего времени на земле, поэтому требуется только два механизма на конечность   -  person Gareth Rees    schedule 04.07.2011


Ответы (1)


Попробуйте демо!

введите здесь описание изображения

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

  1. Оценка следа: разрывается ли связь? след имеет правильную форму? насколько он плоский? насколько плавный ход? он проводит достаточно времени в плоской части?

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

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

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


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

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

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

person Gareth Rees    schedule 04.07.2011
comment
Параметризованная версия вашего апплета великолепна! Когда я получу графику для ошибки установки, я обновлю свой код! - person heltonbiker; 05.07.2011
comment
Ты проделал отличную работу, представив оценки, Гарет! В качестве предложения я бы попытался проверить горизонтальное смещение вместо скорости, поскольку результат будет получен в тех же единицах длины, которые используются для построения связи. Кроме того, мы должны знать, что некоторые комбинации изменяют наклон дорожки контакта с землей, поэтому точки, относящиеся к этой дорожке, должны находиться в пределах допустимого расстояния от двойного касания (если дорожка имеет вогнутый участок) или минимальной кривизны. -касательная (если путь полностью выпуклый) прямой. Действительно, очень сложная проблема... - person heltonbiker; 05.07.2011
comment
Я решил, что (по крайней мере, на данный момент) буду считать, что путь контакта с землей горизонтальный, потому что для любого зверя с негоризонтальным путем контакта с землей мы можем внести соответствующие изменения в Bx и By, чтобы снова сделать его горизонтальным. Таким образом, ограничение нас горизонтальными следами не влияет на пространство животных, которое мы можем анализировать. (Это может сделать локальный поиск немного менее эффективным, но об этом можно будет подумать позже.) - person Gareth Rees; 05.07.2011
comment
Можете ли вы расширить свою точку зрения о перемещении? (Я понимаю проблему масштабирования — мы не хотим получить лучший результат, просто сжимая зверя, — но я не понимаю, что значит проверять горизонтальное смещение или почему это может помочь. Я думаю, что способ решения проблема масштабирования состоит в том, чтобы разделить показатель сопротивления на некоторую характерную длину, например, длину пятна контакта.Или это то, что вы имеете в виду?) - person Gareth Rees; 05.07.2011
comment
На самом деле скорость и смещение связаны друг с другом во времени, поэтому я подумал, что удаление фактора времени (используя смещение вместо скорости) будет, возможно, более универсальным. Кроме того, анализ зверей, контактирующих с наклонной землей, путем компенсации наклона из первых рук был бы более эффективным, поскольку можно было бы избежать присвоения низкого балла животному, у которого прямой, но наклонный путь. буду дальше думать! Спасибо за интерес! - person heltonbiker; 05.07.2011
comment
@Gareth Rees & @heltonbiker, вы, ребята, мои программные герои в этой демонстрации. Ваш код очень четко показывает, как использовать Sylvester для векторного анализа в Canvas в браузере. Можно моделировать самые разнообразные механизмы. ссылка: http://dogfeatherdesign.com/engineering-projects/mechanisms-mechanical-walker/ Большое спасибо! - person zipzit; 31.05.2015
comment
@zipzit Ваш ходок с 8 ссылками сделал мой день... Это впечатляющее исследование и то, что вы поделились, по этой увлекательной, но довольно заниженной теме механизмов! :D - person heltonbiker; 01.06.2015