Comments 19
Если просто уравнения Эйлера интегрировать, метод четвертого порядка заметно лучше метода второго. Подозреваю, что все-таки у вас где-то есть ошибка
https://gist.github.com/uranix/7c137c2c3a0fb22579f54d96721e69be
Думаю, что ошибка в update - вы сначала обновляете omega, а затем этой обновленной omega доворачиваете углы поворота. Правильно же применять метод интегрирования к совместной системе для углов и скоростей
Думаю, что ошибка в update
К сожалению да, в примере ошибка, которую в коде я давно исправил, а там забыл. В коде сейчас вроде бы корректно.
На момент построения графиков было так:
class State3d(val transform: Transform3d, val velocity: Velocity3d):
def updated(s: State3dDerivative, dt: Double): State3d =
State3d(
transform.copy().update(s.velocity, dt),
velocity.copy().update(s.acceleration, dt)
)
Я явно копирую transform и velocity, state3d не меняется, создаётся новая копия.
class Transform3d(val position: Vector3d, val rotation: Quaternion):
def update(velocity: Velocity3d, dt: Double): Transform3d =
position.madd(velocity.linear, dt)
val dRot = Quaternion().setFromExp(velocity.angular * (0.5 * dt))
dRot *> rotation
rotation.normalize()
this
class Velocity3d(val linear: Vector3d, val angular: Vector3d):
def update(acceleration: Acceleration3d, dt: Double): Velocity3d =
linear.madd(acceleration.linear, dt)
angular.madd(acceleration.angular, dt)
this
Я посмотрел Ваш пример, но в нём производятся вычисления в локальной системе отсчёта и вообще не затрагивается вопрос ориентации тела в пространстве. Попробую его воспроизвести и прям по шагам сравнить вычисления со своими, но не уверен, что быстро найду ошибку.
Сделал в инерциальной системе отсчета
https://gist.github.com/uranix/b101ca6ce8dcc2b616502dd8e9a2f9e9
Тут, конечно, нужно следить за единичностью кватерниона в процессе, иначе она плывет (для Эйлера критично, для остальных терпимо). Но порядок погрешности первых интегралов тот же.
Огромное спасибо, а то я уже отчаялся.
К Вашему коду можно добавить
L0Vec = I0 * w0
def LVec(I0, xs):
ws = xs[:, :3]
Rs = R(xs)
I = np.einsum('m,tim,tjm->tij', I0, Rs, Rs)
Lvec = np.einsum('tij,tj->ti', I, ws)
return Lvec
и построить график разницы между начальным L и конечным.
plt.plot(ts4, np.linalg.norm((L4Vec - L0Vec[np.newaxis, :]), axis=1) / L0)
И это то самое место, где мой солвер почему-то теряет точность, а Ваш — нет!
Я смог воспроизвести Ваш способ!
Идея брать производную от кватерниона даже не приходила мне в голову. Что для меня странно — на курсе теормеха кватернионов вообще не было, я их как-то сам потом освоил.
Вместо производной я просто на основе w делал кватернион поворота на приращение угла вокруг оси и домножал на него. Что интересно, на методах второго порядка точности это работает немного лучше или так же.
А вот на Рунге-Кутте четвёртого порядка, видимо, влияет на порядок.
Я добавил новые солверы в код и в графики в ноутбуке — они там с именем типа RungeKutta4Alt.
Точность намного выше, порядка 10^-9 для шага 0.01 и 10^-13 для шага 0.001
А ещё правильней использовать симплектический метод интегрирования (схема Верле, полу-неявный метод Ньютона: у него много названий). Этот метод консервативен (т.е. сохраняет Гамильтониан) по построению.
Большое спасибо за пример!
В Вашем примере в качестве ошибки берётся (|L| — |L0|) / |L0|. Если в моём коде считать ошибку так, то она тоже получется порядка 10^-8.
Но если считать ошибку как |L — L0|/|L0|, то получается, что L отклоняется в сторону и получается ошибка порядка 10^-4.
Я повторил Ваши рассчёты в локальной СО для скорости и ускорений. Они работают с такой же точностью: 10^8 при dt =0.01. И для E, и для модуля L.
После этого я попробовал добавить обновление ориентации. Рассчёты в локальной СО от неё никак не зависят, но станет можно повернуть L из локальной СО в глобальную. В глобальной СО L не должен отклоняться. К сожалению, он отклоняется, и итоговая ошибка тоже получается порядка 10^-4 (как и у меня). По крайней мере, у меня лучше не получилось :(
Раз уж проводится сравнение численных методов интегрирования ДУ, то,вероятно, следует сказать о том, что для немонотонных решений использование постоянного шага - не самая лучшая идея. Есть версии Рунге-Кутта с оценкой ошибки на шаге (версия Мерсона, например). Более естественным является использование многошаговых методов типа Адамса-Башфорта, где оценка ошибки на шаге получается естественным образом.
Лучше использовать роторы из алгебры Клиффорда вместо кватернионов.
А подробнее можно? Чем лучше? Очень интересно. Что такое кватернион я понимаю, а вот ротор из алгербы Клиффорда...
У double мантисса 52 бита, это примерно 16 значащих цифр. Я думаю, такой точности с запасом хватит почти для всего, кроме каких-то спецефических случаев.
Уместить в одно число и галактические масштабы и сантиметры — это очень спецефическая задача.
Например, расстояние от Солнца до реальной Земли 150 млн км, это 1.5 * 10^8 км = 1.5 * 10^14 мм. Даже в таких масштабах double что-то может.
Если же ограничиться только Землёй c радиусом 6400 км, то это 6.4 * 10^9 мм или 6.4 * 10^15 нм.
KSP внутри сделаны сложнее, чем кажется на первый взгляд: https://www.youtube.com/watch?v=mXTxQko-JH0
Формулы для операций с векторами, матрицами и численным решением диффуров не зависят от того, как представлены числа. Там можно использовать float, double или что Вашей душе угодно.
Интересно, спасибо! А про гайку тут как-то был целый цикл https://habr.com/ru/post/264381/ с расчётом периода смены оси вращения. Ещё материалы на https://bivector.net рекомендую посмотреть.
Довольно интересно, спасибо за труд!
Огромное спасибо за труд ! Сейчас как раз изучаю scala, и одновременно очень активно пишу на scala-js (срочно требуется веб-обёртка для некоей моей приблудины). А тут на scala описана вполне интересная и содержательная тема ! Будет что почитать vscode !
Физика вращения 3д тел