Задать вопрос
Nikiti4
@Nikiti4

Реализация вращения через кватернион. Где ошибка?

Пытаюсь реализовать вращение точки. Задача такая: есть молекула, для каждого атома три координаты. У этой молекулы есть группа -C-O-H. Нужно развернуть -O-H на определенный угол вокруг оси C-O. Написал реализацию на java, на простых координатах всё отлично работает, на реальных данных не работает. Помогите, пожалуйста, найти ошибку. (В main-классе закомментированны данные, которые не работают)

Main-class
public class App2
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        
//        Vector carbon = new Vector(0.864, -8.606, -0.389);
//        Vector oxygen = new Vector(-0.495, -8.775, -4.434);
//        Vector hydrogen = new Vector(-0.682, -9.652, -0.809);
        
        Vector carbon = new Vector(0, 0, 0);
        Vector oxygen = new Vector(2, 2, 2);
        Vector hydrogen = new Vector(4, 1, 1);
        
        
        double angle = Math.PI/2;
        
        // Создаем вектор, вокруг которого будем вращать точку, и нормализуем его
        Vector direction = oxygen.residual(carbon);
        direction = direction.normalize();
         // На основе направляющего вектора и угла вращения создаем кватернион и нормализуем его
        Quaternion q = new Quaternion(angle, direction);
        q = q.normalize();
        // Тут вектор, который будем вращать
        Vector rotate = hydrogen.residual(carbon);
       // Вращаем
        rotate = rotate.transform(q);
        rotate = rotate.sum(carbon);
        
        System.out.println(rotate);
        
    }
    
}


Quaternion.java
public class Quaternion {
    
    double w;
    double i;
    double j;
    double k;
            
    public Quaternion(double angle, Vector vecDir) {
        this.w = Math.cos(angle/2);
        i = vecDir.x*Math.sin(angle/2);
        j = vecDir.y*Math.sin(angle/2);
        k = vecDir.z*Math.sin(angle/2);
    }
    
    public Quaternion(double w, double i, double j, double k) {
        this.w = w;
        this.i = i;
        this.j = j;
        this.k = k;
    }
    
    public Quaternion multiply(Quaternion q) {
        double nw = w*q.w - i*q.i - j*q.j - k*q.k;
        double ni = w*q.i + i*q.w + j*q.k - k*q.j;
        double nj = w*q.j - i*q.k + j*q.w + k*q.i;
        double nk = w*q.k + i*q.j - j*q.i + k*q.w;
        return new Quaternion(nw, ni, nj, nk);
    }
    
    public Quaternion multiplyToVector(Vector v) {
        Quaternion q = new Quaternion(0, v.x, v.y, v.z);
        return multiply(q);
    }
    
    public Quaternion normalize() {
        double len = Math.sqrt(w*w + i*i + j*j + k*k);
        return new Quaternion(w/len, i/len, j/len, k/len);
    }
    
    public Quaternion invert() {
        Quaternion res = new Quaternion(w, -i, -j, -k);
        return res;
    }
    
    public Vector toVector() {
        return new Vector(i, j, k);
    }
    
    
}


Vector.java
class Vector {
    
    double x;
    double y;
    double z;
    
    public Vector(double x, double y, double z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
    
     // Действуем по формуле v' = qvq^(-1)
    public Vector transform(Quaternion q) {
        Quaternion r = q.multiplyToVector(this);
        r = r.multiply(q.invert().normalize());
        return r.toVector();
    }

    @Override
    public String toString() {
        return String.format("%2f %2f %2f", x, y, z);
    }
    
    public Vector normalize() {
        double len = Math.sqrt(x*x + y*y + z*z);
        return new Vector(x/len, y/len, z/len);
    }
    
    public Vector sum(Vector v) {
        return new Vector(v.x+x, v.y+y, v.z+z);
    }
    
    public Vector residual(Vector v) {
        return new Vector(x-v.x, y-v.y, z-v.z);
    }
    
}
  • Вопрос задан
  • 1081 просмотр
Подписаться 2 Оценить Комментировать
Пригласить эксперта
Ответы на вопрос 4
Losted
@Losted
Software Architect
Сложно сказать, что именно не работает не имея описания ошибки. Пальцем в небо - перемножаются очень мелкие double, что приводит к неправильному их округлению. В таком случае стоит использовать BigDecimal. Вот тут подобная проблема описывается применительно к деньгам.
Ответ написан
Комментировать
Mrrl
@Mrrl
Заводчик кардиганов
Получилось (-0.41,-7.31,-1.00). Почему вы считаете, что это неправильно? Простейшие независимые проверки не показывают ошибок.
Ответ написан
Nikiti4
@Nikiti4 Автор вопроса
Было:
ff5822e0122247ae88cece7b57b29f27.png
Стало:
f845eb55a75f40639b6b1a1c2b3abf6f.png
Вместе:
382b61d885df4f50aebc086b214de17b.png
Это, конечно, вращение, но не на пи/2, а на меньший. И длина, как видно, меняется
Ответ написан
Deerenaros
@Deerenaros
Программист, математик, задрот и даже чуть инженер
Ух ты ж, какая прелесть.

В общем, чтение кода было чутка затруднено его качеством (о чём ниже), однако стоит заметить, что математических ошибок обнаружено не было (но это не значит что их нет, это лишь значит что их чуть более вероятно нет). Так что всё в принципе поворачивается как надо. Должно по крайне менее, а что до возможной ошибки - есть вероятность что вы неправильно интерпретируете результаты - по всей видимости координаты вводятся в некоторый редактор и можно запросто ввести их немного неправильно. А возможно вы сделали то, чего сами не хотели - например просили повернуть вокруг оси, а хотели вокруг точки - с химией не сильно знаком, здесь сложно что-либо сказать. В любом случае, ошибка скорее всего за рамками этого кода (и этой программы похоже тоже).

Впрочем, будучи немного и математиком, и довольно много программистом - стоит заметить, что в прикладном программировании к математически ёмким сферам очень часто всё сводят к операциям на матрицах. Если не всё, то бОльшую часть - везде где это возможно в общем. Почему? Во-первых, в 99% случаях матричные формы намного, много проще. Если не для человека, то для компьютера точно - ему лучше на порядок больше чисел подробить, чем заниматься дифф. анализом =) Во-вторых, есть огромное количество эффективных алгоритмов по работе с матрицами, причём как с огромными - параллельные вычисления, так и небольшими - быстрая арифметика. И, наконец, почти всегда записи в матричной форме куда лаконичнее.

Вот... Что до кода, то с одной стороны ничего плохого, но вот названия методов повергают в тихий ужас. Multiply лично мне не нравится, в литературе довольно редкий термин, так как означает сам процесс, который никого здесь не интересует, а вот результат - это product. Тем более, что мы не меняем объект, а создаём новый. В русском это как умножение и произведение - учатся умножать, а вычисляют произведение. Как-то так. И не надо toVector - java и без вас знает какие аргументы подавать, а для вас это явно лишняя информация - следовало бы реализовать произведение кватернионов и кватерниона на константу, java бы сама уже вызывала нужный метод в зависимости от подаваемых параметров. И invert был бы не нужен.

А соседство sum и residual... Ну это уже совсем печаль. Если метод изменяет объект (не наш случай) - то add и reduce - то есть процесс, если нет - sum и difference. Но residual... Битый час гадал при чём здесь GMRES, ведь обычная разность...

В остальном... Более менее.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы
18 дек. 2024, в 12:37
10000 руб./за проект
18 дек. 2024, в 12:22
5000 руб./за проект
18 дек. 2024, в 11:57
500 руб./в час