Как реализовать решатель ограничений для двумерной геометрии?

У меня есть набор металлических скользящих деталей, которые ограничены осями x и y следующим образом:

скользящие детали

Мне нужно было бы максимизировать горизонтальное расстояние между всеми частями, ограниченными одним и тем же ползунком, и вертикальное расстояние между скользящими частями и самими ползунками. Как это решить?

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

Сначала я посмотрел на некоторые очень мощные библиотеки, такие как cassowary и jsLPSolver, но у меня были некоторые проблемы с пониманием основного алгоритма и того, как ограничения проверяются на выполнимость и как затем ранжируются возможные решения.

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

РЕДАКТИРОВАТЬ:

У меня есть следующие входные данные:

maxW = 300, maxH = 320

Куски определяются следующим образом (не обязательно, принимается любое решение):

slidingPiece = [pX, pY, width, height, anchorPoint, loopDistance];

Я попытаюсь объяснить, что я имею в виду под «максимизировать».

Расстояние по горизонтали:

a0-b1, b1-b2, b2-b4, b4-b5 и b5-maxX будут одинаковыми, т.е. max X разделить на наибольшее количество вертикальных пересекающихся фигур + 1 (5). Затем b1-b3 и b3-b5 будут определяться доступным оставшимся пространством.

Расстояние по вертикали:

b1-a3, a3-a4 и a0-b5 будут одинаковыми. В идеале a0-b3,b3-b4,a2-b2,b4-a3 и b2-a4 тоже будут одинаковыми. Максимизация a1-b4 и b3-a2 аналогична максимизации b3-b4. То же самое относится к a2-b2 и b4-a3: тогда расстояние b2-b4 будет максимальным отрицательным значением.

Итак, мне нужно максимизировать расстояние между каждым скользящим элементом и его ближайшим выше или ниже Y-ограничения.

Двумерное геометрическое представление этой задачи показывает, что горизонтальное расстояние зависит от вертикального расстояния анкеров (из-за вертикального пересечения закрепленных частей), которое, в свою очередь, зависит от горизонтального положения самих частей. Подумайте, например, b2 несколько короче выше. В этом случае b1 и b2 больше не пересекаются и станут одним и тем же значением x, то есть max X, деленным на 4.

В некоторых других случаях, например, b2 намного длиннее в вышеуказанной части - и будет пересекать якорь a2, тогда он должен быть отнесен к a1. Это причина, потому что будет набор решений, некоторые из которых осуществимы, а некоторые нет, потому что, например, глобальное максимальное ограничение Y будет нарушено.


person deblocker    schedule 26.11.2016    source источник
comment
у вас есть еще данные или какие-то значения, чтобы показать, что вы хотите?   -  person Nina Scholz    schedule 26.11.2016
comment
можно было бы добавить и числовые данные, а не только картинку, на которой (для меня) не особо видно то, что нужно.   -  person Nina Scholz    schedule 26.11.2016
comment
Я думаю, что вы должны написать целевую функцию для вашей задачи. И использовать любой алгоритм оптимизации. Например, симплексный метод: en.wikipedia.org/wiki/Simplex_algorithm   -  person stdob--    schedule 26.11.2016
comment
расстояние по вертикали между скользящими деталями и самими ползунками. Не могли бы вы перефразировать это? Начнем с самого ползункаs. Что вы называете расстоянием между скользящей деталью и ползунком? Это минимальное расстояние между скользящим элементом и ползунком (т.е. кончиком элемента и другим ползунком?). Эти большие скользящие детали с длинной петлей: как определить расстояние до ползунка?   -  person Adrian Colomitchi    schedule 26.11.2016
comment
@stdob-- Весьма вероятно, что карта цели будет заполнена локальными оптимумами. Однако, учитывая размер проблемы (5 скользящих элементов по оси x и 5 ползунков по оси y), я бы попробовал сначала провести чистое исследование методом Монте-Карло, чтобы найти более узкую область в качестве начальной позиции для другого метода оптимизации.   -  person Adrian Colomitchi    schedule 26.11.2016
comment
@AdrianColomitchi Думаю, ты прав.   -  person stdob--    schedule 26.11.2016
comment
@AdrianColomitchi: спасибо, что заметили, что слово «максимизировать» очень часто используется неправильно, это относится и ко мне. Я надеюсь, что мое объяснение теперь несколько чище.   -  person deblocker    schedule 29.11.2016
comment
@NinaScholz: я добавил некоторые значения из реального мира и вспомогательную скрипку, чтобы визуализировать возможное расположение частей.   -  person deblocker    schedule 29.11.2016
comment
@deblocker, пожалуйста, добавьте пояснения к массивам данных a и b. что означают столбцы? задан ли массив feasible? или просто для получения какого-то результата?   -  person Nina Scholz    schedule 30.11.2016
comment
@NinaScholz: я добавил информацию о столбцах здесь: jsfiddle.net/0ntbkg1r/2, возможно массив был только что составлен вручную, но у меня есть много образцов данных для тестирования многих сценариев.   -  person deblocker    schedule 30.11.2016
comment
@NinaScholz: прекрасный математический ум, поскольку меня очень интересует эта тема, не могли бы вы оставить конструктивный комментарий, где вы нашли самые сложные ключевые моменты для решения этого вопроса?   -  person deblocker    schedule 07.12.2016
comment
моя проблема состоит в том, чтобы получить четкое представление о данных, которые у вас есть. я попытался реализовать структуру данных, чтобы получить всю информацию в какой-то объект для правил и ограничений, но это занимает больше времени, чем ожидалось, и у меня нет тишины, чтобы это сделать. я думал, что в структуре данных вам нужно разделить заданные точки и переменные точки, которые рассчитываются, следующим шагом будет создание некоторой функции для пригодности, что означает, что позиция взвешивается по сравнению с другими позициями и возвращает какую-то ошибку, что в лучшем случае равно нулю.   -  person Nina Scholz    schedule 07.12.2016
comment
затем я бы взял другую функцию, чтобы немного переместить всю конструкцию, чтобы минимизировать (наибольшую) ошибку, снова оценить и повторить с перемещением. но это очень сложно с зависимыми ограничениями, как у вас здесь. для начала вы можете свести все ограничения к очень простому подходу и попытаться решить его динамически. затем выполните дополнительный шаг и продолжайте, пока не достигнете полной проблемы.   -  person Nina Scholz    schedule 07.12.2016
comment
вы можете столкнуться с некоторыми основными проблемами с помощью сгенерированного решения такого типа, во-первых, решения нет, затем некоторые решения недостижимы с помощью математических алгоритмов, потому что вам нужен случайный фактор в нем, или вы с триггером или прозвонить больше позиций, что не уменьшает ошибку. если вы действительно знаете свою проблему, и она занимает много времени или не решается в этой системе, вы можете подумать о генетическом алгоритме, который может помочь, но требует дополнительной подготовки и тестирования.   -  person Nina Scholz    schedule 07.12.2016


Ответы (1)


Я бы попробовал полевой подход, аналогичный этому.

  1. Каждый ползунок отодвигает все ползунки

    с силой, масштабируемой расстоянием ^ 2, как будто все они будут иметь электрический заряд одинаковой полярности или пружины, прикрепленные друг к другу.

  2. Кроме того, добавьте трение в зависимости от скорости

    на самом деле не имеет значения, воздух v^2 или жидкость v^3

  3. внедрить кинематические ограничения

    только для горизонтального и вертикального скольжения это должно быть очень легко.

  4. Выполните физическое моделирование и подождите, пока оно не сойдется в стабильное состояние v=~0

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

[Edit4] Пример решателя C++

  1. структуры/классы для представления системы ползунков

    Чтобы облегчить последующий код, я не буду поддерживать замкнутые циклы или двойную привязку. Вот почему ползунок i1 (самый правый) ни к чему не привязан (просто обеспечивает силовое поле). Я закончил с этим определением слайдера:

    слайдер деф

    посмотрите источник class _slider для получения дополнительной информации.

  2. рендеринг

    Тире-тире означает фиксированный ползунок. Серебряные — горизонтальные, цвета морской волны — вертикальные, а желтые выбираются мышью. Возможно, позже красный цвет будет означать какую-то ошибку/зависание или что-то в целях отладки. Для решателей силового поля я иногда добавляю напряженность поля в виде красно-синей шкалы, но не уверен, буду ли я реализовывать это здесь или нет.

    Для простоты я не буду реализовывать функции масштабирования/панорамирования, поскольку ваши размеры удобны для прямого рендеринга без преобразований.

    начальные позиции

  3. выполнить первоначальную настройку

    sliders sys;
    int i0,i1,a0,a1,a2,a3,a4,b1,b2,b3,b4,b5;
    sys.slider_beg();//ia,ib,   x,    y,    a0,    a1,    b0,    b1,_horizontal
    i0=sys.slider_add(-1,-1, 25.0, 25.0,  -5.0, 405.0,   0.0,   0.0, 0);
    a0=sys.slider_add(i0,-1,  0.0,  0.0,   0.0, 400.0,   0.0,   0.0, 1);
    a1=sys.slider_add(i0,-1,  0.0,100.0,   0.0, 400.0,   0.0,   0.0, 1);
    a2=sys.slider_add(i0,-1,  0.0,200.0,   0.0, 400.0,   0.0,   0.0, 1);
    a3=sys.slider_add(i0,-1,  0.0,300.0,   0.0, 400.0,   0.0,   0.0, 1);
    a4=sys.slider_add(i0,-1,  0.0,400.0,   0.0, 400.0,   0.0,   0.0, 1);
    b1=sys.slider_add(a0,a2, 20.0,  0.0,   0.0, 125.0, 125.0, 250.0, 0);
    b2=sys.slider_add(a3,-1, 40.0,  0.0, -70.0,  30.0,   0.0,   0.0, 0);
    b3=sys.slider_add(a1,-1, 60.0,  0.0, -70.0,  30.0,   0.0,   0.0, 0);
    b4=sys.slider_add(a2,-1, 80.0,  0.0, -30.0,  70.0,   0.0,   0.0, 0);
    b5=sys.slider_add(a3,a1,100.0,  0.0,-125.0,   0.0,-125.0,-250.0, 0);
    i1=sys.slider_add(-1,-1,425.0, 25.0,  -5.0, 405.0,   0.0,   0.0, 0);
    sys.slider_end();
    

    Где ia — родительский индекс, а ib — дочерний индекс (сам класс ползунка содержит ib в качестве родителя, но это может привести к путанице при инициализации, поскольку вам нужно будет связать элемент, который еще не существует, поэтому преобразование ib обрабатывается в функции sys.add) . sys — это класс, содержащий все это, а sys.add просто добавьте к нему новый ползунок и верните его индекс, считая с нуля. x,y - это относительное положение по отношению к родителю.

    Чтобы облегчить объем кода, эта настройка не должна противоречить ограничениям. Обзор этой настройки приведен в предыдущем пункте.

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

  4. взаимодействие с мышью

    простое движение ползунка для отладки и настройки начальных значений настройки. И или обработка застрявших дел. Вам нужно обрабатывать события мыши, выберите ближайший ползунок, если он еще не редактируется. И если кнопка мыши нажата, переместите выбранный ползунок в положение мыши...

  5. физическое ограничение/взаимодействие

    Я немного упрощаю это, поэтому я просто создал функцию-предикат, которая вызывается для указанного ползунка и возвращает значение, если он или любой его дочерний элемент/якорь конфликтует с определенными ограничениями. Это намного проще кодировать и отлаживать, чем обновлять позицию в соответствии с фактическим ограничением.

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

    Это будет немного медленнее, но я слишком ленив, чтобы кодировать полное обновление ограничений (этот код может стать очень сложным...).

    Я различаю 2 взаимодействия параллельно и перпендикулярно. Параллель идет прямо. Но перпендикуляр - это взаимодействие между краем ползунка и перпендикулярными ползунками рядом с ним, не включая уже пересекающиеся ползунки (привязанные a, b или просто пересекающиеся) в исходном состоянии. Поэтому я создал список пересекающихся ползунков (ic) при запуске, которые будут игнорироваться для этого взаимодействия.

  6. физическое моделирование

    Подойдет простая физика Ньютона-Д'Аламбера для нерелятивистских скоростей. Просто на каждой итерации устанавливайте ускорения ax,ay на напряженность поля и трения.

  7. решатель полей

    Это набор правил/уравнений для установки ускорений симуляции для каждого ползунка, чтобы сходиться к решению. Я остановился на электростатической втягивающей силе F = -Q/r^2 и линейном демпфировании скорости. Также реализованы ограничители абсолютной скорости и ускорения, чтобы избежать числовых проблем.

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

Вот полный код класса C++/VCL для этого:

//---------------------------------------------------------------------------
//--- Sliders solver ver: 1.01 ----------------------------------------------
//---------------------------------------------------------------------------
#ifndef _sliders_h
#define _sliders_h
//---------------------------------------------------------------------------
#include <math.h>
#include "list.h"   // linear dynamic array template List<T> similar to std::vector
//---------------------------------------------------------------------------
const double _slider_w   =   3.00;  // [px] slider half width (for rendering)
const double _slider_gap =   4.00;  // [px] min gap between sliders (for colisions)
const double _acc_limit=   100.00;  // [px/s^2]
const double _vel_limit=   100.00;  // [px/s]
const double _friction =     0.90;  // [-]
const double _charge   =250000.00;  // [px^3/s^2]
//---------------------------------------------------------------------------
class _slider   // one slider (helper class)
    {
public:
    // properties
    double x,y;             // actual relative pos
    bool _horizontal;       // orientation
    double a0,a1;           // slider vertexes 0 is anchor point
    double b0,b1;           // anchor zone for another slider
    int ia;                 // -1 for fixed or index of parrent slider
    int ib;                 // -1 or index of parrent slider
    // computed
    List<int> ic;           // list of slider indexes to ignore for perpendicular constraints
    double a,b;             // force field affected part
    double X,Y;             // actual absolute position
    double vx,vy,ax,ay;     // actual relative vel,acc
    // temp
    int flag;               // temp flag for simulation
    double x0,x1;           // temp variables for solver
    // constructors (can ignore this)
    _slider()           {}
    _slider(_slider& a) { *this=a; }
    ~_slider()          {}
    _slider* operator = (const _slider *a) { *this=*a; return this; }
    //_slider* operator = (const _slider &a) { ...copy... return this; }
    };
//---------------------------------------------------------------------------
class sliders   // whole slider system main class
    {
public:
    List<_slider> slider;           // list of sliders

    double vel_max;                 // max abs velocity of sliders for solver precision control
    double charge;                  // actual charge of sliders for solve()
    int    mode;                    // actual solution precision control mode

    // constructors (can ignore this)
    sliders();
    sliders(sliders& a) { *this=a; }
    ~sliders()          {}
    sliders* operator = (const sliders *a) { *this=*a; return this; }
    //sliders* operator = (const sliders &a) { ...copy... return this; }

    // VCL window API variables (can ignore this)
    double mx0,my0,mx1,my1; // last and actual mouse position
    TShiftState sh0,sh1;    // last and actual mouse buttons and control keys state
    int sel;

    // API (this is important stuff)
    void slider_beg(){ slider.num=0; }  // clear slider list
    int  slider_add(int ia,int ib,double x,double y,double a0,double a1,double b0,double b1,bool _h); // add slider to list
    void slider_end();              // compute slider parameters
    bool constraints(int ix);       // return true if constraints hit
    void positions();               // recompute absolute positions
    void update(double dt);         // update physics simulation with time step dt [sec]
    void solve(bool _init=false);   // set sliders accelerations to solve this
    void stop();                    // stop all movements
    // VCL window API for interaction with GUI (can ignore this)
    void mouse(int x,int y,TShiftState sh);
    void draw(TCanvas *scr);
    };
//---------------------------------------------------------------------------
sliders::sliders()
    {
    mx0=0.0; my0=0.0;
    mx1=0.0; my1=0.0;
    sel=-1;
    }
//---------------------------------------------------------------------------
int sliders::slider_add(int ia,int ib,double x,double y,double a0,double a1,double b0,double b1,bool _h)
    {
    _slider s; double q;
    if (a0>a1) { q=a0; a0=a1; a1=q; }
    if (b0>b1) { q=b0; b0=b1; b1=q; }
    s.x=x; s.vx=0.0; s.ax=0.0;
    s.y=y; s.vy=0.0; s.ay=0.0;
    s.ia=ia; s.a0=a0; s.a1=a1;
    s.ib=-1; s.b0=b0; s.b1=b1;
    s.ic.num=0;
    if ((ib>=0)&&(ib<slider.num)) slider[ib].ib=slider.num;
    s._horizontal=_h;
    s.a=a0; // min
    if (s.a>a1) s.a=a1;
    if (s.a>b0) s.a=b0;
    if (s.a>b1) s.a=b1;
    s.b=a0; // max
    if (s.b<a1) s.b=a1;
    if (s.b<b0) s.b=b0;
    if (s.b<b1) s.b=b1;
    slider.add(s);
    return slider.num-1;
    }
//---------------------------------------------------------------------------
void sliders::slider_end()
    {
    int i,j;
    double a0,a1,b0,b1,x0,x1,w=_slider_gap;
    _slider *si,*sj;
    positions();
    // detect intersecting sliders and add them to propriet ic ignore list
    for (si=slider.dat,i=0;i<slider.num;i++,si++)
     for (sj=si+1   ,j=i+1;j<slider.num;j++,sj++)
      if (si->_horizontal!=sj->_horizontal)
        {
        if (si->_horizontal)
            {
            a0=si->X+si->a; a1=sj->X-w;
            b0=si->X+si->b; b1=sj->X+w;
            x0=si->Y;       x1=sj->Y;
            }
        else{
            a0=si->Y+si->a; a1=sj->Y-w;
            b0=si->Y+si->b; b1=sj->Y+w;
            x0=si->X;       x1=sj->X;
            }
        if (((a0<=b1)&&(b0>=a1))||((a1<=b0)&&(b1>=a0)))
         if ((x0>x1+sj->a-w)&&(x0<x1+sj->b+w))
            {
            si->ic.add(j);
            sj->ic.add(i);
            }
        }
    }
//---------------------------------------------------------------------------
bool sliders::constraints(int ix)
    {
    int i,j;
    double a0,a1,b0,b1,x0,x1,x,w=_slider_gap;
    _slider *si,*sj,*sa,*sb,*s;
    s=slider.dat+ix;
    // check parallel neighbors overlapp
    for (si=slider.dat,i=0;i<slider.num;i++,si++)
     if ((i!=ix)&&(si->_horizontal==s->_horizontal))
        {
        if (s->_horizontal)
            {
            a0=s->X+s->a; a1=si->X+si->a;
            b0=s->X+s->b; b1=si->X+si->b;
            x0=s->Y;      x1=si->Y;
            }
        else{
            a0=s->Y+s->a; a1=si->Y+si->a;
            b0=s->Y+s->b; b1=si->Y+si->b;
            x0=s->X;      x1=si->X;
            }
        if (((a0<=b1)&&(b0>=a1))||((a1<=b0)&&(b1>=a0)))
            {
            if ((i<ix)&&(x0<x1+w)) return true;
            if ((i>ix)&&(x0>x1-w)) return true;
            }
        }
    // check perpendicular neighbors overlapp
    for (si=slider.dat,i=0;i<slider.num;i++,si++)
     if ((i!=ix)&&(si->_horizontal!=s->_horizontal))
        {
        // skip ignored sliders for this
        for (j=0;j<s->ic.num;j++)
         if (s->ic[j]==i) { j=-1; break; }
          if (j<0) continue;
        if (s->_horizontal)
            {
            a0=s->X+s->a; a1=si->X-w;
            b0=s->X+s->b; b1=si->X+w;
            x0=s->Y;      x1=si->Y;
            }
        else{
            a0=s->Y+s->a; a1=si->Y-w;
            b0=s->Y+s->b; b1=si->Y+w;
            x0=s->X;      x1=si->X;
            }
        if (((a0<=b1)&&(b0>=a1))||((a1<=b0)&&(b1>=a0)))
         if ((x0>x1+si->a-w)&&(x0<x1+si->b+w))
          return true;
        }
    // conflict a anchor area of parent?
    if (s->ia>=0)
        {
        si=slider.dat+s->ia;
        if (s->_horizontal)
            {
            x0=si->Y+si->a0;
            x1=si->Y+si->a1;
            x=s->Y;
            }
        else{
            x0=si->X+si->a0;
            x1=si->X+si->a1;
            x=s->X;
            }
        if (x<x0+w) return true;
        if (x>x1-w) return true;
        }
    // conflict b anchor area of parent?
    if (s->ib>=0)
        {
        si=slider.dat+s->ib;
        if (si->_horizontal)
            {
            x0=si->X+si->b0;
            x1=si->X+si->b1;
            x=s->X;
            }
        else{
            x0=si->Y+si->b0;
            x1=si->Y+si->b1;
            x=s->Y;
            }
        if (x<x0+w) return true;
        if (x>x1-w) return true;
        }
    // conflict b anchor area with childs?
    for (si=slider.dat,i=0;i<slider.num;i++,si++)
     if ((i!=ix)&&(si->ib==ix))
        {
        if (s->_horizontal)
            {
            x0=s->X+s->b0;
            x1=s->X+s->b1;
            x=si->X;
            }
        else{
            x0=s->Y+s->b0;
            x1=s->Y+s->b1;
            x=si->Y;
            }
        if (x<x0+w) return true;
        if (x>x1-w) return true;
        }

    // check childs too
    for (si=slider.dat,i=0;i<slider.num;i++,si++)
     if ((i!=ix)&&(si->ia==ix))
      if (constraints(i)) return true;
    return false;
    }
//---------------------------------------------------------------------------
void sliders::positions()
    {
    int i,e;
    _slider *si,*sa;
    // set flag = uncomputed
    for (si=slider.dat,i=0;i<slider.num;i++,si++) si->flag=0;
    // iterate until all sliders are computed
    for (e=1;e;)
     for (e=0,si=slider.dat,i=0;i<slider.num;i++,si++)
      if (!si->flag)
        {
        // fixed
        if (si->ia<0)
            {
            si->X=si->x;
            si->Y=si->y;
            si->flag=1;
            continue;
            }
        // a anchored
        sa=slider.dat+si->ia;
        if (sa->flag)
            {
            si->X=sa->X+si->x;
            si->Y=sa->Y+si->y;
            si->flag=1;
            continue;
            }
        e=1; // not finished yet
        }
    }
//---------------------------------------------------------------------------
void sliders::update(double dt)
    {
    int i;
    _slider *si,*sa;
    double x,X;
    // D'Lamnbert integration
    for (si=slider.dat,i=0;i<slider.num;i++,si++)
     if (si->_horizontal)
        {
        x=si->y; si->vy+=si->ay*dt;     // vel = Integral(acc*dt)
                 si->vy*=_friction;     // friction k*vel
        X=si->Y; si->y +=si->vy*dt;     // pos = Integral(vel*dt)
        positions();                    // recompute childs
        if ((si->ia<0)||(constraints(i))) // if fixed or constraint hit (stop and restore original position)
            {
            si->vy=0.0;
            si->y =x;
            si->Y =X;
            positions();                // recompute childs
            }
        }
    else{
        x=si->x; si->vx+=si->ax*dt;     // vel = Integral(acc*dt)
                 si->vx*=_friction;     // friction k*vel
        X=si->X; si->x +=si->vx*dt;     // pos = Integral(vel*dt)
        positions();                    // recompute childs
        if ((si->ia<0)||(constraints(i))) // if fixed or constraint hit (stop and restore original position)
            {
            si->vx=0.0;
            si->x =x;
            si->X =X;
            positions();                // recompute childs
            }
        }
    }
//---------------------------------------------------------------------------
void sliders::solve(bool _init)
    {
    int i,j,k;
    double a0,a1,b0,b1,x0,x1;
    _slider *si,*sj,*sa;
    // init solution
    if (_init)
        {
        mode=0;
        charge=_charge;
        }
    // clear accelerations and compute actual max velocity
    vel_max=0.0;
    for (si=slider.dat,i=0;i<slider.num;i++,si++)
        {
        si->ax=0.0;
        si->ay=0.0;
        x0=fabs(si->vx); if (vel_max<x0) vel_max=x0;
        x0=fabs(si->vy); if (vel_max<x0) vel_max=x0;
        }
    // precision control of solver
    if ((mode==0)&&(vel_max>25.0)) { mode++; }                  // wait until speed raises
    if ((mode==1)&&(vel_max<10.0)) { mode++; charge*=0.10; }    // scale down forces to lower jitter
    if ((mode==2)&&(vel_max< 1.0)) { mode++; charge*=0.10; }    // scale down forces to lower jitter
    if ((mode==3)&&(vel_max< 0.1)) { mode++; charge =0.00; stop(); } // solution found
    // set x0 as 1D vector to closest parallel neighbor before and x1 after
    for (si=slider.dat,i=0;i<slider.num;i++,si++) { si->x0=0.0; si->x1=0.0; }
    for (si=slider.dat,i=0;i<slider.num;i++,si++)
     for (sj=si+1   ,j=i+1;j<slider.num;j++,sj++)
      if (si->_horizontal==sj->_horizontal)
        {
        // longer side interaction
        if (si->_horizontal)
            {
            a0=si->X+si->a; a1=sj->X+sj->a;
            b0=si->X+si->b; b1=sj->X+sj->b;
            x0=si->Y;       x1=sj->Y;
            }
        else{
            a0=si->Y+si->a; a1=sj->Y+sj->a;
            b0=si->Y+si->b; b1=sj->Y+sj->b;
            x0=si->X;       x1=sj->X;
            }
        if (((a0<=b1)&&(b0>=a1))||((a1<=b0)&&(b1>=a0)))
            {
            x0=x1-x0;
            if ((si->ia>=0)&&(x0<0.0)&&((fabs(si->x0)<_slider_gap)||(fabs(si->x0)>fabs(x0)))) si->x0=-x0;
            if ((si->ia>=0)&&(x0>0.0)&&((fabs(si->x1)<_slider_gap)||(fabs(si->x1)>fabs(x0)))) si->x1=-x0;
            if ((sj->ia>=0)&&(x0<0.0)&&((fabs(sj->x0)<_slider_gap)||(fabs(sj->x0)>fabs(x0)))) sj->x0=+x0;
            if ((sj->ia>=0)&&(x0>0.0)&&((fabs(sj->x1)<_slider_gap)||(fabs(sj->x1)>fabs(x0)))) sj->x1=+x0;
            }
        // shorter side interaction
        if (si->_horizontal)
            {
            a0=si->Y-_slider_gap; a1=sj->Y+_slider_gap;
            b0=si->Y+_slider_gap; b1=sj->Y+_slider_gap;
            x0=si->X;             x1=sj->X;
            }
        else{
            a0=si->X-_slider_gap; a1=sj->X+_slider_gap;
            b0=si->X+_slider_gap; b1=sj->X+_slider_gap;
            x0=si->Y;             x1=sj->Y;
            }
        if (((a0<=b1)&&(b0>=a1))||((a1<=b0)&&(b1>=a0)))
            {
            if (x0<x1) { x0+=si->b; x1+=sj->a; }
            else       { x0+=si->a; x1+=sj->b; }
            x0=x1-x0;
            if (si->ia>=0)
                {
                sa=slider.dat+si->ia;
                if ((sa->ia>=0)&&(x0<0.0)&&((fabs(sa->x0)<_slider_gap)||(fabs(sa->x0)>fabs(x0)))) sa->x0=-x0;
                if ((sa->ia>=0)&&(x0>0.0)&&((fabs(sa->x1)<_slider_gap)||(fabs(sa->x1)>fabs(x0)))) sa->x1=-x0;
                }
            if (sj->ia>=0)
                {
                sa=slider.dat+sj->ia;
                if ((sa->ia>=0)&&(x0<0.0)&&((fabs(sa->x0)<_slider_gap)||(fabs(sa->x0)>fabs(x0)))) sa->x0=+x0;
                if ((sa->ia>=0)&&(x0>0.0)&&((fabs(sa->x1)<_slider_gap)||(fabs(sa->x1)>fabs(x0)))) sa->x1=+x0;
                }
            }
        }
    // set x0 as 1D vector to closest perpendicular neighbor before and x1 after
    for (si=slider.dat,i=0;i<slider.num;i++,si++)
     for (sj=si+1   ,j=i+1;j<slider.num;j++,sj++)
      if (si->_horizontal!=sj->_horizontal)
        {
        // skip ignored sliders for this
        for (k=0;k<si->ic.num;k++)
         if (si->ic[k]==j) { k=-1; break; }
          if (k<0) continue;
        if (si->_horizontal)
            {
            a0=si->X+si->a; a1=sj->X-_slider_w;
            b0=si->X+si->b; b1=sj->X+_slider_w;
            x0=si->Y;
            }
        else{
            a0=si->Y+si->a; a1=sj->Y-_slider_w;
            b0=si->Y+si->b; b1=sj->Y+_slider_w;
            x0=si->X;
            }
        if (((a0<=b1)&&(b0>=a1))||((a1<=b0)&&(b1>=a0)))
            {
            if (si->_horizontal)
                {
                a1=sj->Y+sj->a;
                b1=sj->Y+sj->b;
                }
            else{
                a1=sj->X+sj->a;
                b1=sj->X+sj->b;
                }
            a1-=x0; b1-=x0;
            if (fabs(a1)<fabs(b1)) x0=-a1; else x0=-b1;
            if ((si->ia>=0)&&(x0<0.0)&&((fabs(si->x0)<_slider_gap)||(fabs(si->x0)>fabs(x0)))) si->x0=+x0;
            if ((si->ia>=0)&&(x0>0.0)&&((fabs(si->x1)<_slider_gap)||(fabs(si->x1)>fabs(x0)))) si->x1=+x0;
            if (sj->ia<0) continue;
            sa=slider.dat+sj->ia;
            if ((sa->ia>=0)&&(x0<0.0)&&((fabs(sa->x0)<_slider_gap)||(fabs(sa->x0)>fabs(x0)))) sa->x0=-x0;
            if ((sa->ia>=0)&&(x0>0.0)&&((fabs(sa->x1)<_slider_gap)||(fabs(sa->x1)>fabs(x0)))) sa->x1=-x0;
            }
        }
    // convert x0,x1 distances to acceleration
    for (si=slider.dat,i=0;i<slider.num;i++,si++)
        {
        // driving force F = ~ Q / r^2
        if (fabs(si->x0)>1e-10)  x0=charge/(si->x0*si->x0); else x0=0.0; if (si->x0<0.0) x0=-x0;
        if (fabs(si->x1)>1e-10)  x1=charge/(si->x1*si->x1); else x1=0.0; if (si->x1<0.0) x1=-x1;
        a0=x0+x1;
        // limit acc
        if (a0<-_acc_limit) a0=-_acc_limit;
        if (a0>+_acc_limit) a0=+_acc_limit;
        // store parallel acc to correct axis
        if (si->_horizontal) si->ay=a0;
         else                si->ax=a0;
        // limit vel (+/- one iteration overlap)
        if (si->_horizontal) x0=si->vy;
         else                x0=si->vx;
        if (x0<-_vel_limit)  x0=-_vel_limit;
        if (x0>+_vel_limit)  x0=+_vel_limit;
        if (si->_horizontal) si->vy=x0;
         else                si->vx=x0;
        }
    }
//---------------------------------------------------------------------------
void sliders::stop()
    {
    int i;
    _slider *si;
    for (si=slider.dat,i=0;i<slider.num;i++,si++)
        {
        si->vx=0.0;
        si->vy=0.0;
        si->ax=0.0;
        si->ay=0.0;
        }
    }
//---------------------------------------------------------------------------
void sliders::mouse(int x,int y,TShiftState sh)
    {
    int i,q0,q1;
    double d,dd;
    _slider *si;
    // update mouse state
    mx0=mx1; my0=my1; sh0=sh1;
    mx1=x;   my1=y;   sh1=sh;
    // slider movement with left mouse button
    q0=sh0.Contains(ssLeft);
    q1=sh1.Contains(ssLeft);
    if ((sel>=0)&&(q1))
        {
        si=slider.dat+sel;
        // stop simulation for selected slider
        si->vx=0.0;
        si->vy=0.0;
        si->ax=0.0;
        si->ay=0.0;
        // use mouse position instead
        if (si->ia>=0)
            {
            if (si->_horizontal){ d=si->y; dd=si->Y; si->y+=my1-si->Y; si->Y=my1; si->vy=0.0; si->ay=0.0; positions(); if (constraints(sel)) { si->y=d; si->Y=dd; positions(); }}
             else               { d=si->x; dd=si->X; si->x+=mx1-si->X; si->X=mx1; si->vx=0.0; si->ax=0.0; positions(); if (constraints(sel)) { si->x=d; si->X=dd; positions(); }}
            }
        }
    // select slider (if not left mouse button used)
    if (!q1)
     for (sel=-1,d=_slider_w+1.0,si=slider.dat,i=0;i<slider.num;i++,si++)
        {
        dd=_slider_w+1.0;
        if (si->_horizontal){ if ((mx1>=si->X+si->a)&&(mx1<=si->X+si->b)) dd=fabs(my1-si->Y); }
         else               { if ((my1>=si->Y+si->a)&&(my1<=si->Y+si->b)) dd=fabs(mx1-si->X); }
        if ((dd<d)&&(dd<=_slider_w)) { sel=i; d=dd; }
        }
    }
//---------------------------------------------------------------------------
void sliders::draw(TCanvas *scr)
    {
    int i,j,n;
    double w=_slider_w,r,x,y,a0,a1;
    AnsiString txt;
    _slider *s;
    scr->Brush->Style=bsClear;
    #define _line(aa,bb)           \
    if (s->_horizontal)            \
        {                          \
        scr->MoveTo(s->X+aa,s->Y); \
        scr->LineTo(s->X+bb,s->Y); \
        }                          \
    else{                          \
        scr->MoveTo(s->X,s->Y+aa); \
        scr->LineTo(s->X,s->Y+bb); \
        }
    scr->Pen->Color=clSilver;
    scr->Font->Color=clWhite;
    scr->TextOutA(40,40,AnsiString().sprintf("mode %i",mode));
    scr->TextOutA(40,60,AnsiString().sprintf("vel: %.3lf [px/s]",vel_max));
    scr->TextOutA(40,80,AnsiString().sprintf("  Q: %.3lf [px^3/s^2]",charge));
    scr->Font->Color=clYellow;
    for (s=slider.dat,i=0;i<slider.num;i++,s++)
        {
        if (s->_horizontal) scr->Pen->Color=clSilver;
         else               scr->Pen->Color=clAqua;
        if (i==sel)
            {
            scr->Pen->Color=clYellow;
            txt=AnsiString().sprintf(" ix:%i ia:%i ib:%i ic:",sel,s->ia,s->ib);
            for (j=0;j<=s->ic.num;j++) txt+=AnsiString().sprintf(" %i",s->ic[j]);
            scr->TextOutA(40,100,txt);
            scr->TextOutA(40,120,AnsiString().sprintf("pos: %.1lf %.1lf [px]",s->X,s->Y));
            scr->TextOutA(40,140,AnsiString().sprintf("vel: %.3lf %.3lf [px/s]",s->vx,s->vy));
            scr->TextOutA(40,160,AnsiString().sprintf("acc: %.3lf %.3lf [px/s^2]",s->ax,s->ay));
            scr->Pen->Color=clYellow;
            }
        if (s->ia<0) scr->Pen->Style=psDash;
         else        scr->Pen->Style=psSolid;
        // a anchor loop
        x=s->X;
        y=s->Y;
        if (s->ia>=0) scr->Ellipse(x-w,y-w,x+w,y+w);
        // b anchor loop
        r=0.5*fabs(s->b1-s->b0);
        if (s->_horizontal)
            {
            x=s->X+0.5*(s->b0+s->b1);
            y=s->Y;
            scr->RoundRect(x-r,y-w,x+r,y+w,w,w);
            }
        else{
            x=s->X;
            y=s->Y+0.5*(s->b0+s->b1);
            scr->RoundRect(x-w,y-r,x+w,y+r,w,w);
            }
        // a line cutted by a anchor loop
        a0=s->a0; a1=s->a1;
        if ((s->ia>=0)&&(a0<=+w)&&(a1>=-w))
            {
            if (a0<-w) _line(s->a0,-w);
            if (a1>+w) _line( w,s->a1);
            }
        else _line(s->a0,s->a1);
        }
    scr->Font->Color=clDkGray;
    scr->Pen->Style=psSolid;
    scr->Brush->Style=bsSolid;
    #undef _line
    }
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------

Вы можете игнорировать материал VCL, это просто API для взаимодействия с окном моего приложения и рендеринга. Самому солверу от него ничего не нужно. Я использовал свой шаблон динамического линейного массива List<T>, так что вот несколько пояснений:

  • List<double> xxx; совпадает с double xxx[];
  • xxx.add(5); добавляет 5 в конец списка
  • xxx[7] элемент массива доступа (безопасный)
  • xxx.dat[7] элемент массива доступа (небезопасный, но быстрый прямой доступ)
  • xxx.num - фактический используемый размер массива
  • xxx.reset() очищает массив и устанавливает xxx.num=0
  • xxx.allocate(100) предварительно выделить место для 100 элементов

Использование просто после правильной инициализации из пули #3 следующим образом:

sys.solve(true);
for (;;)
 {
 sys.solve();
 sys.update(0.040); // just time step
 if (sys.mode==4) break; // stop if solution found or stuck
 }

Вместо цикла for я вызываю это по таймеру и перерисовываю окно, чтобы увидеть анимацию:

анимация

Неравномерность возникает из-за неравномерной частоты дискретизации захвата GIF (неравномерный пропуск некоторых кадров из моделирования).

Вы можете поиграть с константами для пределов vel,acc, коэффициента демпфирования и управления режимом ifs, чтобы изменить поведение. Если вы также реализуете обработчик мыши, вы можете перемещать ползунки с помощью левой кнопки мыши, чтобы вы могли выйти из застрявших случаев...

Вот отдельная демонстрация Win32 (скомпилированная с помощью BDS2006 C++).

  • Демо нажмите на медленную загрузку под большой пурпурной кнопкой, введите 4-буквенный буквенно-цифровой код, чтобы начать загрузку без регистрации.

Для получения дополнительной информации о том, как работает вычисление силы решателя, см. связанный/последующий контроль качества:

person Spektre    schedule 27.11.2016
comment
Я ценю вашу помощь, правда. Одна из моих попыток была с кинематическими пружинами. но я понял, что точность конечного положения всех элементов зависит от количества итераций и от окончательного встряхивания, которое в большинстве случаев необходимо, чтобы избежать дрожания вокруг стабильного состояния. К сожалению, мне нужно избегать обоих. Но я согласен с вами, это может быть хорошим решением. - person deblocker; 29.11.2016
comment
Привет, Спектре, у тебя есть время, чтобы создать доказательство концепции? Может быть, это может быть лучше, чем мой... В приведенном вами примере нет реализации ограничений, т.е. node.left/right и так далее. - person deblocker; 07.12.2016
comment
что мне нужно нажать для загрузки? (кстати, я впечатлен.) - person Nina Scholz; 10.12.2016
comment
@NinaScholz, если вы хотите попробовать закодировать свой подход (из ваших комментариев), тогда все, что вам нужно, это positions() и constraints(int ix);, которые они работают как должны (проверено с помощью мыши), и для оптимизации размещения вы можете использовать что-то вроде этого Как работает приближенный поиск, но общая сложность, скорее всего, будет безумной. Надеюсь, я не забыл удалить RTL по умолчанию и использование пакетов из exe, так что ему ничего не понадобится. - person Spektre; 10.12.2016
comment
@NinaScholz обновил код, демо-ссылку и анимацию в новой версии (нашли/решили проблему взаимодействия) - person Spektre; 10.12.2016
comment
В обновленном ответе @deblocker отсутствовали более короткие боковые параллельные взаимодействия, а также забыли убрать несколько строк в перпендикулярных взаимодействиях. Теперь он ведет себя так, как и ожидалось... по крайней мере, с моей точки зрения. Надеюсь, я не удалил что-то, что не должен, поскольку я достиг предела размера ответа и был вынужден удалить материал отладки отрисовки из исходного кода непосредственно в редакторе ответов.... - person Spektre; 10.12.2016
comment
@Spektre: вот перевод вашей работы на JavaScript: Электростатический 2-D решатель Работает отлично, но, к сожалению , я не могу ждать x секунд, чтобы узнать конечное положение структуры :-( - person deblocker; 13.12.2016
comment
@deblocker каждая итерация длится всего несколько микросекунд, поэтому вы можете моделировать секунды за миллисекунды ... просто не ждите таймера или рендеринга ... Вы также можете увеличить скорость решения, настроив константы ... - person Spektre; 13.12.2016
comment
@deblocker также может помочь изменение правил. Например, я использую самую сильную 1D-силу вместо направленной 2D, поэтому решатель стабилизируется по одной оси, затем по другой и так далее, а не по всем сразу, но мне было лень кодировать это (это довольно сложно реализовать) Кстати, приятно видеть, что у тебя это работает :) +1 за это - person Spektre; 13.12.2016
comment
@deblocker только редактирование мыши не работает ... выбор в порядке, я вижу, может быть, вы не реализовали его намеренно или неправильно перевели извлечение кнопок мыши из VCL::TShiftState Также все это (я имею в виду мой код) не оптимизировано и может можно значительно улучшить, особенно position() пересчет можно значительно улучшить constraints. Вместо этого я хотел, чтобы код был максимально простым. - person Spektre; 13.12.2016
comment
@Spektre: строгий цикл примерно 600 итераций, может быть, это можно решить с меньшим количеством итераций? Кстати, друг мой, знаешь, это всегда вопрос времени.. :) Я люблю VCL. - person deblocker; 13.12.2016
comment
Вам нужно поиграть с зарядом, трением и ограничениями скорости. Во-первых, без mode изменения заряда, чтобы вы могли видеть, насколько хорошо он идет и какую скорость устанавливают эти mode условия. Чем больше заряд, тем быстрее он будет двигаться, но вам нужно ограничить скорость, чтобы он не слишком сильно мешал, а также замедлялся на достаточно небольшом расстоянии ... Ю может использовать разные ограничения для каждого mode, а не просто менять заряд, как это сделал я. много дорабатывать... - person Spektre; 13.12.2016
comment
@deblocker также, если ваша окончательная точность выражена целыми числами, вы можете остановиться намного раньше, чем я ... - person Spektre; 13.12.2016
comment
@Spektre: да, я буду приближаться только к целым числам. Не могли бы вы объяснить, что вы имеете в виду? - person deblocker; 13.12.2016
comment
@deblocker найти // precision control of solver в коде есть контроль точности, вы можете настроить скорость, даже количество режимов ... чтобы решить быстрее. Кроме того, если вы хотите настроить производительность, наибольший прирост вы получите, если добавите дочерние/родительские списки к ползункам, чтобы все поиски O(N^2) перешли к O(N) и O(1). но и сейчас ~600 действия нужно делать очень быстро, как в <10 ms - person Spektre; 13.12.2016
comment
Давайте продолжим обсуждение в чате. - person deblocker; 14.12.2016