Решая LCP - Автор XperienS
Данная статья является обобщением знаний, полученных из работ по этой теме ( список работ представлени в конце статьи ); это выборка основных моментов, нужных для понимания основных принципов, почти без математики.
Решая LCP
LCP - Linear Complementarity Problem ( проблема линейного дополнения ) возникает тогда, когда нам нужно решить систему следующего вида:
b = Ax + q,
где b, x, q - векторы n x 1, A - матрица n x n;
действуют также так называемые дополнительные ограничения ( complementarity constraints ).
LCP нужно решать например если в симуляторе динамики твердого тела есть так называемые односторонние ограничения ( unilateral constraints ), такие как контактное соединение ( contact joint ) - сила должна действовать только тогда, когда тела нужно раздвинуть, но не должна их приближать. Для решения подобных систем существует два вида алгоритмов:
1. Прямые методы решения LCP;
Lemke: [ Michael Cline ]
Dantzig: [ Davig Baraff ]
2. Итеративные методы решения LCP;
Jacobi: [ Kenny Erleben ]
Gauss-Seidel: [ Erin Catto, Kenny Erleben ]
В этой заметке я остановлюсь лишь на итеративных методах так как за последнее время я удостоверился, что прямые методы лучше не использовать для игровых приложений. Они точнее, но и гораздо нестабильнее.
Итеративные методы решения LCP
Суть итеративных методов состоит в том, что ему дается некое начальное значение искомой переменной ( initial guess ), и после каждой итерации искомая переменная приближается от начального предположения к действительному решению. Очевидное преимущество - быстрота такого подхода по сравнению с прямыми методами решения, хотя и не такая высокая точность. Также можно ускорить работу, сделав так называемые тесты на сходимость ( convergence testing ), так же называемые критериями остановки ( stopping criteria ). Это позволит завершить работу солвера если требуемая точность достигнута. Этот способ будет описан позже.
Сначала я рассмотрю методы решения линейных систем вида Ax = b, без всяких дополнительных условий. Затем я покажу как один из методов решения линейных систем развить до метода решения MLCP ( Mixed LCP - присутствуют как ограниченные строки, так и неограниченные ). Итак, простейший итеративный метод решения линейных систем - Jacobi.
Математический вид данного метода:
x'i = (bi - S(Li,j*xj)j=0,i-1 - S(Ui,j*xj)j=i+1,n-1) / Ai,i;
где S - сумма по переменной указанной мельче в пределах, указанных после равно; x' - переменная на текущем шаге; x - переменная на предыдущем шаге.
Код:
(
VecN - вектор с варьирующейся размерностью;
MatMN - матрица с изменяемой размерностью, причем m - кол-во строк, а n - кол-во столбцов.
)
VecN Solve_Jacobi(MatMN &A, VecN &b, int kMax = 5) { if (A.m != b.n) return b; int i, j; int n = A.m; // Number of A's rows ( also size of vector b ) VecN x = b; // Initial Guess VecN y(n); float delta; // Jacobi Solver for (int k = 0; k < kMax; ++k) { for (i = 0; i < n; ++i) { delta = 0.0f; for (j = 0; j < i; ++j) delta += A[i][j]*x[j]; for (j = i+1; j < n; ++j) delta += A[i][j]*x[j]; y[i] = (b[i] - delta) / A[i][i]; } x = y; } return x; }
Вышеуказанный метод является "параллельным"( это видно из-за того, что при вычислениях не используется x', а так же использование доп. вектора y: все переменные могут обновляться одновременно, потому что вычисления зависят только от результатов вычислений на предыдущем шаге ), в то время как метод Gauss-Seidel является "последовательным"( вычисления зависят как от результатов на текущем, так и на предыдущем шагах ):
x'i = (bi - S(Li,j*x'j)j=0,i-1 - S(Ui,j*xj)j=i+1,n-1) / Ai,i;
Код, реализующий Gauss-Seidel solver:
VecN Solve_GaussSeidel(MatMN &A, VecN &b, int kMax = 5) { if (A.m != b.n) return b; int i, j, n = A.m; VecN x = b; float delta; // Gauss-Seidel Solver for (int k = 0; k < kMax; ++k) { for (i = 0; i < n; ++i) { delta = 0.0f; for (j = 0; j < i; ++j) delta += A[i][j]*x[j]; for (j = i+1; j < n; ++j) delta += A[i][j]*x[j]; x[i] = (b[i] - delta) / A[i][i]; } } return x; }
Несмотря на то, что метод Gauss-Seidel - последовательный, он более предпочтителен, нежели метод Jacobi. На практике он показывает более высокую скорость вычислений и стабильность.
SOR
Так же для увеличения стабильности данного метода используется так называемая последовательная верхняя релаксация ( successive overrelaxation - SOR ). Для того, чтобы понять, как она работает, нужно определить остаточный вектор( residual vector ) r:
остаточный вектор для x в данной системе: r = Ax - b;
тогда получим, что
Ai,i*xi + r'i,i = Ai,i*x'i;
переставив местами:
x'i = xi + r'i,i/Ai,i;
для более быстрой сходимости метода вводят новую переменную w ( некоторые ее значения будут обеспечивать более быструю сходимость ), называемую параметром релаксации ( в данном случае для метода Gauss-Seidel ):
x'i = xi + w * r'i,i/Ai,i;
теперь метод преобразовался в SOR Gauss-Seidel, параметр w должен быть 1 + 0.2 ( когда 0 < w < 1 это нижняя релаксация - under-relaxation, а w > 1 это верхняя релаксация; у меня в данный момент w = 0.9 ) и код для решения линейной системы методом SOR-GS:
VecN Solve_SOR(MatMN &A, VecN &b, float w = 0.9f, int kMax = 5) { if (A.m != b.n) return b; int i, j, n = A.m; VecN x = b; float delta; // Gauss-Seidel with Successive OverRelaxation Solver for (int k = 0; k < kMax; ++k) { for (i = 0; i < n; ++i) { delta = 0.0f; for (j = 0; j < i; ++j) delta += A[i][j]*x[j]; for (j = i+1; j < n; ++j) delta += A[i][j]*x[j]; delta = (b[i] - delta) / A[i][i]; x[i] += w * (delta - x[i]); } } return x; }
Тест на сходимость или Критерий остановки
Вообще-то по-хорошему надо, чтобы итеративные метода продолжали решать, пока тест на сходимость не пройден. Но в действительности этот тест может быть использован в купе с максимальным количеством итераций, чтобы ограничить количество итераций ( а следовательно, ускорить работу алгоритма ). Для того, чтобы провести тест на сходимость, нужно ввести некую нужную точность CONV_EPS, тогда тест на сходимость будет выглядеть примерно так:
пусть x' - решение на текущем шаге, x - решение на предыдущем и определена точность CONV_EPS > 0
||x' - x|| < CONV_EPS;
где || || - векторная норма.
Один из способов проверки на сходимость это просто проверка величины delta. Если delta < CONV_EPS для всех i на данной итерации, то тест на сходимость пройден и можно завершать решение системы. У меня пока этот тест не реализован, но сделать его, особенно по второму методу не составит особого труда.
#dynamics, #LCP, #физика, #rigid body
15 апреля 2006 (Обновление: 21 янв 2010)
Комментарии [2]