[goffice] Pseudo-inverse: improve algorithm.



commit 7ebe4d77bc7d582d9fc27480a015fabb12c521b2
Author: Morten Welinder <terra gnome org>
Date:   Fri May 3 20:37:50 2013 -0400

    Pseudo-inverse: improve algorithm.

 ChangeLog                    |    5 +
 goffice/math/go-regression.c |  292 ++++++++++++++++++++++++++++++------------
 2 files changed, 213 insertions(+), 84 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index 5b534f9..0fb5089 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2013-05-03  Morten Welinder  <terra gnome org>
+
+       * goffice/math/go-regression.c (go_matrix_pseudo_inverse): Improve
+       algorithm.
+
 2013-05-03  Jean Brefort  <jean brefort normalesup org>
 
        * goffice/Makefile.am: add new UI file.
diff --git a/goffice/math/go-regression.c b/goffice/math/go-regression.c
index ed681d7..dec0e36 100644
--- a/goffice/math/go-regression.c
+++ b/goffice/math/go-regression.c
@@ -221,6 +221,19 @@ go_regression_statl_get_type (void)
               (dst)[_i] = (src)[_i];           \
   } while (0)
 
+#define PRINT_MATRIX(var,dim1,dim2)                            \
+  do {                                                         \
+       int _i, _j, _d1, _d2;                                   \
+       _d1 = (dim1);                                           \
+       _d2 = (dim2);                                           \
+       for (_i = 0; _i < _d1; _i++) {                          \
+         for (_j = 0; _j < _d2; _j++) {                        \
+                 g_printerr (" %12.8" FORMAT_g, (var)[_i][_j]); \
+         }                                                     \
+         g_printerr ("\n");                                    \
+       }                                                       \
+  } while (0)
+
 #define PRINT_QMATRIX(var,dim1,dim2)                           \
   do {                                                         \
        int _i, _j, _d1, _d2;                                   \
@@ -308,7 +321,7 @@ copy_stats (go_regression_stat_t *s1,
  * V is a pre-allocated output m-times-n matrix.  V will contrain
  * n vectors of different lengths: n, n-1, ..., 1.  These are the
  * Householder vectors (or null for the degenerate case).  The
- * matrix Q of size m-times-n is implied from V.
+ * matrix Q of size m-times-m is implied from V.
  *
  * Rfinal is a pre-allocated output matrix of size n-times-n.  (To
  * get the m-times-n version of R, simply add m-n null rows.)
@@ -326,6 +339,7 @@ SUFFIX(QRH) (CONSTMATRIX A, gboolean qAT,
        int i, j, k;
        QUAD *tmp = g_new (QUAD, n);
        gboolean ok = TRUE;
+       gboolean debug = FALSE;
 
        if (m < n || n < 1) {
                ok = FALSE;
@@ -370,6 +384,8 @@ SUFFIX(QRH) (CONSTMATRIX A, gboolean qAT,
                SUFFIX(go_quad_add)(&L2p, &L2p, &s);
                SUFFIX(go_quad_sqrt)(&L, &L2p);
                if (SUFFIX(go_quad_value)(&L) == 0) {
+                       if (debug)
+                               g_printerr ("L=0 in round %d:\n", k);
                        /* This will be an identity so no determinant sign */
                        continue;
                }
@@ -404,15 +420,15 @@ SUFFIX(QRH) (CONSTMATRIX A, gboolean qAT,
                for (i = k + 1; i < m; i++)
                        SUFFIX(go_quad_init) (&R[i][k], 0);
 
-#if 0
-               g_printerr ("After round %d:\n", k);
-               g_printerr ("R:\n");
-               PRINT_QMATRIX(R, m ,n );
-               g_printerr ("\n");
-               g_printerr ("V:\n");
-               PRINT_QMATRIX(V, m ,n );
-               g_printerr ("\n\n");
-#endif
+               if (debug) {
+                       g_printerr ("After round %d:\n", k);
+                       g_printerr ("R:\n");
+                       PRINT_QMATRIX(R, m, n);
+                       g_printerr ("\n");
+                       g_printerr ("V:\n");
+                       PRINT_QMATRIX(V, m, n);
+                       g_printerr ("\n\n");
+               }
        }
 
        for (i = 0; i < n /* Only n */; i++)
@@ -449,6 +465,31 @@ SUFFIX(multiply_Qt) (QUAD *x, CONSTQMATRIX V, int m, int n)
        }
 }
 
+static void
+SUFFIX(print_Q) (CONSTQMATRIX V, int m, int n)
+{
+       QMATRIX Q;
+       int i, j;
+       QUAD *x;
+
+       ALLOC_MATRIX2 (Q, m, m, QUAD);
+       x = g_new (QUAD, m);
+
+       for (j = 0; j < m; j++) {
+               for (i = 0; i < m; i++)
+                       SUFFIX(go_quad_init)(&x[i], i == j ? 1 : 0);
+
+               SUFFIX(multiply_Qt)(x, V, m, n);
+               for (i = 0; i < m; i++)
+                       Q[j][i] = x[i];
+       }
+
+       PRINT_QMATRIX (Q, m, m);
+
+       FREE_MATRIX (Q, m, m);
+       g_free (x);
+}
+
 /*
  * Solve Rx=b.
  *
@@ -511,10 +552,6 @@ SUFFIX(regres_from_condition) (CONSTQMATRIX R, int n)
        DOUBLE c, lc;
        int i;
 
-       /*
-        * R is triangular, so all the eigenvalues are in the diagonal.
-        * We need the absolute largest and smallest.
-        */
        for (i = 0; i < n; i++) {
                DOUBLE e = SUFFIX(fabs)(SUFFIX(go_quad_value)(&R[i][i]));
                if (e < emin) emin = e;
@@ -2137,6 +2174,30 @@ SUFFIX(go_linear_regression_leverage) (MATRIX A, DOUBLE *d, int m, int n)
        return regres;
 }
 
+static void
+SUFFIX(qmatrix_mul) (QMATRIX C, QMATRIX A, QMATRIX B,
+                    int C_m, int C_n, int A_n)
+{
+       int i, j, k;
+
+       for (i = 0; i < C_n; i++) {
+               for (j = 0; j < C_m; j++) {
+                       QUAD acc;
+
+                       SUFFIX(go_quad_init) (&acc, 0);
+                       for (k = 0; k < A_n; k++) {
+                               QUAD p;
+
+                               SUFFIX(go_quad_mul) (&p, &A[i][k], &B[k][j]);
+                               SUFFIX(go_quad_add) (&acc, &acc, &p);
+                       }
+
+                       C[i][j] = acc;
+               }
+       }
+}
+
+
 /*
  * Compute the pseudo-inverse of a matrix, see
  *     http://en.wikipedia.org/wiki/Moore%E2%80%93Penrose_pseudoinverse
@@ -2161,25 +2222,63 @@ SUFFIX(go_matrix_pseudo_inverse) (CONSTMATRIX A, int m, int n,
 {
        QMATRIX V;
        QMATRIX R;
-       QMATRIX R2 = NULL;
-       QMATRIX R2inv = NULL;
-       gboolean *null_dim;
-       QUAD *x = NULL;
+       QMATRIX Bi = NULL;
+       MATRIX RTR = NULL;
        gboolean has_result;    
-       void *state = SUFFIX(go_quad_start) ();
-       int i, j, i2, j2, rank;
-       DOUBLE emax;
+       void *state;
+       int i, j;
+       DOUBLE emin, emax, delta;
+       QUAD *x;
+       int steps;
+       gboolean full_rank;
        gboolean debug = FALSE;
 
+       if (debug) {
+               g_printerr ("A:\n");
+               PRINT_MATRIX (A, m, n);
+       }
+
+       if (m < n) {
+               MATRIX AT;
+               MATRIX BT;
+
+               if (debug)
+                       g_printerr ("Pseudo-inverse via transpose\n");
+
+               /*
+                * The code below assumes m >= n.  Luckily, taking the
+                * pseudo-inverse commutes with transposition.
+                */
+
+               ALLOC_MATRIX (AT, n, m);
+               ALLOC_MATRIX (BT, m, n);
+               for (i = 0; i < m; i++)
+                       for (j = 0; j < n; j++)
+                               AT[j][i] = A[i][j];
+               SUFFIX(go_matrix_pseudo_inverse)(AT, n, m, threshold, BT);
+               for (i = 0; i < m; i++)
+                       for (j = 0; j < n; j++)
+                               B[j][i] = BT[i][j];
+               FREE_MATRIX (AT, n, m);
+               FREE_MATRIX (BT, m, n);
+               return;
+       }
+
+       state = SUFFIX(go_quad_start) ();
+
        ALLOC_MATRIX2 (V, m, n, QUAD);
        ALLOC_MATRIX2 (R, n, n, QUAD);
-       null_dim = g_new0 (gboolean, n);
 
        has_result = SUFFIX(QRH)(A, FALSE, V, R, m, n, NULL);
-       if (!has_result)
+       if (!has_result) {
+               if (debug)
+                       g_printerr ("QRH failed\n");
                goto null_result;
+       }
 
        if (debug) {
+               g_printerr ("Q:\n");
+               SUFFIX(print_Q) (V, m, n);
                g_printerr ("R:\n");
                PRINT_QMATRIX (R, n, n);
        }
@@ -2189,81 +2288,109 @@ SUFFIX(go_matrix_pseudo_inverse) (CONSTMATRIX A, int m, int n,
                DOUBLE abs_e = SUFFIX(fabs) (SUFFIX(go_quad_value)(&R[i][i]));
                emax = MAX (emax, abs_e);
        }
+       if (emax == 0)
+               goto null_result;
 
-       rank = 0;
+       emin = emax;
+       full_rank = TRUE;
        for (i = 0; i < n; i++) {
                DOUBLE abs_e = SUFFIX(fabs) (SUFFIX(go_quad_value)(&R[i][i]));
-               null_dim[i] = abs_e <= emax * threshold;
-               if (!null_dim[i])
-                       rank++;
+               if (abs_e <= emax * threshold) {
+                       full_rank = FALSE;
+                       SUFFIX(go_quad_init)(&R[i][i], 0);
+               } else
+                       emin = MIN (emin, abs_e);
        }
-       if (rank == 0)
-               goto null_result;
+
+       delta = full_rank ? 0 : emax * threshold;
 
        /*
-        * R2 is R after removing columns and rows that were deemed
-        * null dimensions.
+        * Starting point for the iteration:
+        *
+        *    Bi := (RT R + delta I)^-1 * RT
+        *
+        * (RT R) is positive semi-definite and (delta I) is positive definite,
+        * so the sum is positive definite and invertible.
         */
-       ALLOC_MATRIX2 (R2, rank, rank, QUAD);
-       for (i = i2 = 0; i < n; i++) {
-               if (null_dim[i])
-                       continue;
-               for (j = j2 = 0; j < n; j++) {
-                       if (null_dim[j])
-                               continue;
-                       R2[i2][j2] = R[i][j];
-                       j2++;
+       ALLOC_MATRIX (RTR, n, n);
+       ALLOC_MATRIX2 (Bi, n, n, QUAD);
+       for (i = 0; i < n; i++) {
+               for (j = 0; j < n; j++) {
+                       QUAD acc;
+                       int k;
+
+                       SUFFIX(go_quad_init) (&acc, i == j ? delta : 0.0);
+                       for (k = 0; k <= i && k <= j; k++) {
+                               QUAD p;
+
+                               SUFFIX(go_quad_mul) (&p, &R[k][i], &R[k][j]);
+                               SUFFIX(go_quad_add) (&acc, &acc, &p);
+                       }
+
+                       RTR[i][j] = SUFFIX(go_quad_value) (&acc);
                }
-               i2++;
        }
        if (debug) {
-               g_printerr ("R2:\n");
-               PRINT_QMATRIX (R2, rank, rank);
+               g_printerr ("RTR:\n");
+               PRINT_MATRIX (RTR, n, n);
        }
+       if (!SUFFIX(go_matrix_invert)(RTR, n))
+               goto null_result;
+       if (debug) {
+               g_printerr ("RTR inverse:\n");
+               PRINT_MATRIX (RTR, n, n);
+       }
+       for (i = 0; i < n; i++) {
+               for (j = 0; j < n; j++) {
+                       QUAD acc;
+                       int k;
 
-       /* R2inv := R2^-1 */
-       ALLOC_MATRIX2 (R2inv, rank, rank, QUAD);
-       x = g_new (QUAD, rank);
-       for (j = 0; j < rank; j++) {
-               for (i = 0; i < rank; i++)
-                       SUFFIX(go_quad_init)(&x[i], i == j ? 1 : 0);
+                       SUFFIX(go_quad_init) (&acc, 0.0);
+                       for (k = 0; k < n; k++) {
+                               QUAD p, a;
 
-               if (SUFFIX(back_solve) (R2, x, x, rank))
-                       goto cannot_happen;
+                               SUFFIX(go_quad_init) (&a, RTR[i][k]);
+                               SUFFIX(go_quad_mul) (&p, &a, &R[j][k]);
+                               SUFFIX(go_quad_add) (&acc, &acc, &p);
+                       }
 
-               for (i = 0; i < rank; i++)
-                       R2inv[i][j] = x[i];
+                       Bi[i][j] = acc;
+               }
        }
-       g_free (x);
-       x = NULL;
        if (debug) {
-               g_printerr ("R2inv:\n");
-               PRINT_QMATRIX (R2inv, rank, rank);
+               g_printerr ("B0:\n");
+               PRINT_QMATRIX (Bi, n, n);
        }
 
-       /* R := R2inv with null columns/rows inserted back in */
-       for (i = i2 = 0; i < n; i++) {
-               if (null_dim[i]) {
+       /* B_{i+1} = (2 I - B_i R) B_i */
+       for (steps = 0; steps < 10; steps++) {
+               QMATRIX W;
+               QMATRIX Bip1;
+               QUAD two;
+
+               SUFFIX(go_quad_init)(&two, 2);
+
+               ALLOC_MATRIX2 (W, n, n, QUAD);
+               ALLOC_MATRIX2 (Bip1, n, n, QUAD);
+
+               SUFFIX(qmatrix_mul) (W, Bi, R, n, n, n);
+               for (i = 0; i < n; i++)
                        for (j = 0; j < n; j++)
-                               SUFFIX(go_quad_init)(&R[i][j], 0.0);
-               } else {
-                       for (j = j2 = 0; j < n; j++) {
-                               if (null_dim[j])
-                                       SUFFIX(go_quad_init)(&R[i][j], 0.0);
-                               else {
-                                       R[i][j] = R2inv[i2][j2];
-                                       j2++;
-                               }
-                       }
-                       i2++;
+                               SUFFIX(go_quad_sub) (&W[i][j],
+                                                    i == j ? &two : &SUFFIX(go_quad_zero),
+                                                    &W[i][j]);
+               SUFFIX(qmatrix_mul) (Bip1, W, Bi, n, n, n);
+               COPY_MATRIX (Bi, Bip1, n, n);
+               if (debug) {
+                       g_printerr ("B%d:\n", steps + 1);
+                       PRINT_QMATRIX (Bi, n, n);
                }
-       }
-       if (debug) {
-               g_printerr ("R2-inv-expand:\n");
-               PRINT_QMATRIX (R, n, n);
+
+               FREE_MATRIX (W, n, n);
+               FREE_MATRIX (Bip1, n, n);
        }
 
-       /* B := R Q^T */
+       /* B := (Bi|O) Q^T */
        x = g_new (QUAD, m);
        for (j = 0; j < m; j++) {
                QUAD acc;
@@ -2281,29 +2408,26 @@ SUFFIX(go_matrix_pseudo_inverse) (CONSTMATRIX A, int m, int n,
                        SUFFIX(go_quad_init) (&acc, 0);
                        for (k = 0; k < n /* Only n */; k++) {
                                QUAD p;
-                               SUFFIX(go_quad_mul) (&p, &R[i][k], &x[k]);
+                               SUFFIX(go_quad_mul) (&p, &Bi[i][k], &x[k]);
                                SUFFIX(go_quad_add) (&acc, &acc, &p);
                        }
 
                        B[i][j] = SUFFIX(go_quad_value)(&acc);
                }
        }
+       g_free (x);
 
 out:
        FREE_MATRIX (V, m, n);
        FREE_MATRIX (R, n, n);
-       if (R2) FREE_MATRIX (R2, rank, rank);
-       if (R2inv) FREE_MATRIX (R2inv, rank, rank);
-       g_free (null_dim);
-       g_free (x);
+       if (RTR) FREE_MATRIX (RTR, n, n);
+       if (Bi) FREE_MATRIX (Bi, n, n);
 
        SUFFIX(go_quad_end) (state);
        return;
 
-cannot_happen:
-       g_printerr ("This cannot happen\n");
-
 null_result:
+       if (debug) g_printerr ("Null result\n");
        for (i = 0; i < n; i++)
                for (j = 0; j < m; j++)
                        B[i][j] = 0;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]