Вычисление обратной матрицы Java

Я пытаюсь вычислить обратную матрицу в Java.

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

Это работает, когда матрица не слишком большая. Проверил, что для матриц размером до 12х12 результат выдается быстро. Однако, когда матрица больше 12x12, время, необходимое для завершения вычисления, увеличивается экспоненциально.

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

Код, который я использую:

public static double determinant(double[][] input) {
  int rows = nRows(input);        //number of rows in the matrix
  int columns = nColumns(input); //number of columns in the matrix
  double determinant = 0;

  if ((rows== 1) && (columns == 1)) return input[0][0];

  int sign = 1;     
  for (int column = 0; column < columns; column++) {
    double[][] submatrix = getSubmatrix(input, rows, columns,column);
    determinant = determinant + sign*input[0][column]*determinant(submatrix);
    sign*=-1;
  }
  return determinant;
}   

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

Спасибо


person dedalo    schedule 02.01.2010    source источник
comment
@duffymo: спасибо за ответ. Вы правы, не экспоненциально, я имею в виду, что при размере матрицы 12x12 время, необходимое для вычисления определителя, значительно увеличивается. Я пробовал Jama, но у меня не работает (я новичок в Java). Я также рассмотрю декомпозицию LU. Спасибо.   -  person dedalo    schedule 02.01.2010
comment
Нет, экспоненциально правильно, так как ваш алгоритм действительно экспоненциален, но duffymo также верен в том, что инверсия матрицы или вычисление определителя не требуют экспоненциального времени.   -  person JaakkoK    schedule 02.01.2010
comment
Спасибо всем. Я посмотрел на Jama и нашел метод «det» в классе Matrix, который быстро его вычисляет. Я также нашел методы вычисления матриц L и U (A = LU), а затем det(A) = det(L) det(U).   -  person dedalo    schedule 02.01.2010


Ответы (11)


Экспоненциально? Нет, я считаю, что инверсия матрицы - это O (N ^ 3).

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

А еще лучше, загляните в пакет, чтобы помочь вам. На ум приходит JAMA.

12x12 или 19x19 не являются большими матрицами. Обычно решают задачи с десятками или сотнями тысяч степеней свободы.

Вот рабочий пример использования JAMA. У вас должен быть JAMA JAR в вашем CLASSPATH при компиляции и запуске:

package linearalgebra;

import Jama.LUDecomposition;
import Jama.Matrix;

public class JamaDemo
{
    public static void main(String[] args)
    {
        double [][] values = {{1, 1, 2}, {2, 4, -3}, {3, 6, -5}};  // each array is a row in the matrix
        double [] rhs = { 9, 1, 0 }; // rhs vector
        double [] answer = { 1, 2, 3 }; // this is the answer that you should get.

        Matrix a = new Matrix(values);
        a.print(10, 2);
        LUDecomposition luDecomposition = new LUDecomposition(a);
        luDecomposition.getL().print(10, 2); // lower matrix
        luDecomposition.getU().print(10, 2); // upper matrix

        Matrix b = new Matrix(rhs, rhs.length);
        Matrix x = luDecomposition.solve(b); // solve Ax = b for the unknown vector x
        x.print(10, 2); // print the solution
        Matrix residual = a.times(x).minus(b); // calculate the residual error
        double rnorm = residual.normInf(); // get the max error (yes, it's very small)
        System.out.println("residual: " + rnorm);
    }
}

Вот та же проблема, решенная с помощью Apache Commons Math в соответствии с рекомендацией quant_dev:

package linearalgebra;

import org.apache.commons.math.linear.Array2DRowRealMatrix;
import org.apache.commons.math.linear.ArrayRealVector;
import org.apache.commons.math.linear.DecompositionSolver;
import org.apache.commons.math.linear.LUDecompositionImpl;
import org.apache.commons.math.linear.RealMatrix;
import org.apache.commons.math.linear.RealVector;

public class LinearAlgebraDemo
{
    public static void main(String[] args)
    {
        double [][] values = {{1, 1, 2}, {2, 4, -3}, {3, 6, -5}};
        double [] rhs = { 9, 1, 0 };

        RealMatrix a = new Array2DRowRealMatrix(values);
        System.out.println("a matrix: " + a);
        DecompositionSolver solver = new LUDecompositionImpl(a).getSolver();

        RealVector b = new ArrayRealVector(rhs);
        RealVector x = solver.solve(b);
        System.out.println("solution x: " + x);;
        RealVector residual = a.operate(x).subtract(b);
        double rnorm = residual.getLInfNorm();
        System.out.println("residual: " + rnorm);
    }
}

Адаптируйте их к вашей ситуации.

person duffymo    schedule 02.01.2010
comment
спасибо за Ваш ответ. Вы правы, не экспоненциально, я имею в виду, что при размере матрицы 12x12 время, необходимое для вычисления определителя, значительно увеличивается. Я пробовал Jama, но у меня не работает (я новичок в Java). Я также рассмотрю декомпозицию LU. Спасибо - person dedalo; 02.01.2010
comment
Лаборатория, в которой я когда-то работал, решала матрицы с миллиардами степеней свободы. Естественно, на сотнях или тысячах процессоров. - person davidtbernal; 02.01.2010
comment
Я решал одну задачу целый календарный месяц. Это было на рабочей станции Unix, до того, как многопроцессорность стала обычным явлением. Нам приходилось проверять результат раз в неделю, чтобы убедиться, что мы не потеряем слишком много времени в случае сбоя и необходимости перезапуска. - person duffymo; 02.01.2010
comment
С математикой 3.6.1 вам нужно только вызвать MatrixUtils.inverse(MatrixUtils.createRealMatrix(matrix)) - person Spenhouet; 08.02.2017
comment
Я не знаю, когда этот метод был добавлен. Этот ответ был опубликован более семи лет назад. Нельзя было ожидать, что я буду ясновидящим в отношении изменений API. То, что я написал, должно быть правильным. Оба успешно работали в тот день, когда я их опубликовал — более семи лет назад. - person duffymo; 08.02.2017

Я бы рекомендовал использовать для этого Apache Commons Math 2.0. JAMA — мертвый проект. ACM 2.0 фактически взял линейную алгебру из JAMA и развил ее дальше.

person quant_dev    schedule 02.01.2010
comment
Не знал этого, quant_dev. Спасибо за информацию. - person duffymo; 02.01.2010

Библиотека la4j (линейная алгебра для Java) поддерживает обращение матриц. Вот краткий пример:

Matrix a = new Basic2DMatrix(new double[][]{
   { 1.0, 2.0, 3.0 },
   { 4.0, 5.0, 6.0 },
   { 7.0, 8.0. 9.0 }
});

Matrix b = a.invert(Matrices.DEFAULT_INVERTOR); // uses Gaussian Elimination
person Vladimir Kostyukov    schedule 12.03.2013

Инверсия матриц требует больших вычислительных ресурсов. Как ответил duffymo, LU — хороший алгоритм, и есть и другие варианты (например, QR).

К сожалению, вы не можете избавиться от тяжелых вычислений... и, возможно, узким местом является метод getSubmatrix, если вы не используете оптимизированную библиотеку.

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

person Pablo Rodriguez    schedule 02.01.2010

Вы НИКОГДА не хотите вычислять обратную матрицу таким образом. Хорошо, вычисления самого обратного следует избегать, так как почти всегда лучше использовать факторизацию, такую ​​как LU.

Вычисление определителя с помощью рекурсивных вычислений — численно непристойное занятие. Оказывается, лучшим выбором является использование факторизации LU для вычисления определителя. Но если вы собираетесь вычислять коэффициенты LU, то зачем вам вычислять обратное? Вы уже проделали сложную работу по вычислению коэффициентов LU.

Когда у вас есть факторы LU, вы можете использовать их для выполнения обратной и прямой замены.

Поскольку матрица 19x19 большая, она даже близко не соответствует тому, что я считаю большой.

person Community    schedule 02.01.2010

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

import org.apache.commons.math3.linear.Array2DRowRealMatrix;
import org.apache.commons.math3.linear.ArrayRealVector;
import org.apache.commons.math3.linear.DecompositionSolver;
import org.apache.commons.math3.linear.LUDecomposition;
import org.apache.commons.math3.linear.RealMatrix;
import org.apache.commons.math3.linear.RealVector;

public class LinearAlgebraDemo
{
    public static void main(String[] args)
    {
        double [][] values = {{1, 1, 2}, {2, 4, -3}, {3, 6, -5}};
        double [][] rhs = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};

        // Solving AB = I for given A
        RealMatrix A = new Array2DRowRealMatrix(values);
        System.out.println("Input A: " + A);
        DecompositionSolver solver = new LUDecomposition(A).getSolver();

        RealMatrix I = new Array2DRowRealMatrix(rhs);
        RealMatrix B = solver.solve(I);
        System.out.println("Inverse B: " + B);
    }
}
person Soumya Kanti    schedule 08.09.2015
comment
Эта ошибка означает, что Exception in thread "AWT-EventQueue-0" org.apache.commons.math3.linear.SingularMatrixException: matrix is singular нужно использовать одинаковый размер матриц? - person zygimantus; 16.04.2016

Для тех, кто ищет инверсию матриц (не быстро), см. https://github.com/rchen8/Algorithms/blob/master/Matrix.java.

import java.util.Arrays;

public class Matrix {

    private static double determinant(double[][] matrix) {
        if (matrix.length != matrix[0].length)
            throw new IllegalStateException("invalid dimensions");

        if (matrix.length == 2)
            return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];

        double det = 0;
        for (int i = 0; i < matrix[0].length; i++)
            det += Math.pow(-1, i) * matrix[0][i]
                    * determinant(submatrix(matrix, 0, i));
        return det;
    }

    private static double[][] inverse(double[][] matrix) {
        double[][] inverse = new double[matrix.length][matrix.length];

        // minors and cofactors
        for (int i = 0; i < matrix.length; i++)
            for (int j = 0; j < matrix[i].length; j++)
                inverse[i][j] = Math.pow(-1, i + j)
                        * determinant(submatrix(matrix, i, j));

        // adjugate and determinant
        double det = 1.0 / determinant(matrix);
        for (int i = 0; i < inverse.length; i++) {
            for (int j = 0; j <= i; j++) {
                double temp = inverse[i][j];
                inverse[i][j] = inverse[j][i] * det;
                inverse[j][i] = temp * det;
            }
        }

        return inverse;
    }

    private static double[][] submatrix(double[][] matrix, int row, int column) {
        double[][] submatrix = new double[matrix.length - 1][matrix.length - 1];

        for (int i = 0; i < matrix.length; i++)
            for (int j = 0; i != row && j < matrix[i].length; j++)
                if (j != column)
                    submatrix[i < row ? i : i - 1][j < column ? j : j - 1] = matrix[i][j];
        return submatrix;
    }

    private static double[][] multiply(double[][] a, double[][] b) {
        if (a[0].length != b.length)
            throw new IllegalStateException("invalid dimensions");

        double[][] matrix = new double[a.length][b[0].length];
        for (int i = 0; i < a.length; i++) {
            for (int j = 0; j < b[0].length; j++) {
                double sum = 0;
                for (int k = 0; k < a[i].length; k++)
                    sum += a[i][k] * b[k][j];
                matrix[i][j] = sum;
            }
        }

        return matrix;
    }

    private static double[][] rref(double[][] matrix) {
        double[][] rref = new double[matrix.length][];
        for (int i = 0; i < matrix.length; i++)
            rref[i] = Arrays.copyOf(matrix[i], matrix[i].length);

        int r = 0;
        for (int c = 0; c < rref[0].length && r < rref.length; c++) {
            int j = r;
            for (int i = r + 1; i < rref.length; i++)
                if (Math.abs(rref[i][c]) > Math.abs(rref[j][c]))
                    j = i;
            if (Math.abs(rref[j][c]) < 0.00001)
                continue;

            double[] temp = rref[j];
            rref[j] = rref[r];
            rref[r] = temp;

            double s = 1.0 / rref[r][c];
            for (j = 0; j < rref[0].length; j++)
                rref[r][j] *= s;
            for (int i = 0; i < rref.length; i++) {
                if (i != r) {
                    double t = rref[i][c];
                    for (j = 0; j < rref[0].length; j++)
                        rref[i][j] -= t * rref[r][j];
                }
            }
            r++;
        }

        return rref;
    }

    private static double[][] transpose(double[][] matrix) {
        double[][] transpose = new double[matrix[0].length][matrix.length];

        for (int i = 0; i < matrix.length; i++)
            for (int j = 0; j < matrix[i].length; j++)
                transpose[j][i] = matrix[i][j];
        return transpose;
    }

    public static void main(String[] args) {
        // example 1 - solving a system of equations
        double[][] a = { { 1, 1, 1 }, { 0, 2, 5 }, { 2, 5, -1 } };
        double[][] b = { { 6 }, { -4 }, { 27 } };

        double[][] matrix = multiply(inverse(a), b);
        for (double[] i : matrix)
            System.out.println(Arrays.toString(i));
        System.out.println();

        // example 2 - example 1 using reduced row echelon form
        a = new double[][]{ { 1, 1, 1, 6 }, { 0, 2, 5, -4 }, { 2, 5, -1, 27 } };
        matrix = rref(a);
        for (double[] i : matrix)
            System.out.println(Arrays.toString(i));
        System.out.println();

        // example 3 - solving a normal equation for linear regression
        double[][] x = { { 2104, 5, 1, 45 }, { 1416, 3, 2, 40 },
                { 1534, 3, 2, 30 }, { 852, 2, 1, 36 } };
        double[][] y = { { 460 }, { 232 }, { 315 }, { 178 } };

        matrix = multiply(
                multiply(inverse(multiply(transpose(x), x)), transpose(x)), y);
        for (double[] i : matrix)
            System.out.println(Arrays.toString(i));
    }

}
person CoolMind    schedule 13.03.2018
comment
Я предполагаю, что метод minor должен называться submatrix? - person acala; 15.05.2021
comment
@acala, конечно, это подматрица, но я думаю, что она тоже незначительна, потому что мы удаляем строку и столбец, см. en.wikipedia.org/wiki/Минор_(линейная_алгебра). Я выучил это много лет назад и мог забыть. - person CoolMind; 15.05.2021
comment
но я думаю, что минор и подматрица - разные вещи, миноры - это определители подматриц. Пожалуйста, просмотрите страницу Википедии. - person acala; 16.05.2021
comment
@acala, согласен с тобой, спасибо. Во многих статьях миноры называются детерминантами. Итак, я должен переименовать этот метод. - person CoolMind; 16.05.2021

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

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

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

person JaakkoK    schedule 02.01.2010

Трудно победить Matlab в их игре. Они также осторожно относятся к точности. Если у вас есть опорные значения 2.0 и 2.00001 — будьте осторожны! Ваш ответ может оказаться очень неточным. Кроме того, проверьте реализацию Python (где-то она находится в numpy/scipy...)

person Hamish Grubijan    schedule 02.01.2010

У вас должно быть точное решение? Приближенный решатель (Gauss-Seidel) очень эффективен и прост в реализации. ), скорее всего, сработает для вас и очень быстро сойдется. 19х19 это совсем маленькая матрица. Я думаю, что код Гаусса-Зейделя, который я использовал, мог решить матрицу 128x128 в мгновение ока (но не цитируйте меня, это было давно).

person davidtbernal    schedule 02.01.2010

С помощью ACM вы можете сделать это без правой стороны:

RealMatrix A = new Array2DRowRealMatrix(ARR);
System.out.println(new LUDecomposition(A).getSolver().getInverse());
person mrchebik    schedule 24.05.2020