Lorenz Minder on Wed, 13 May 2009 08:52:45 +0200 |
[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]
Re: Another problem with matrix inversion |
Hi, BA writes: > On Mon, May 11, 2009 at 05:55:18AM +0200, Lorenz Minder wrote: > > Hi, > > > > There's another problem with matrix inversion that I noticed. In > > GP/PARI if you want to invert a matrix modulo some non-prime integer m, > > then things will not generally work out nicely. Example: > > > > parisize = 4000000, primelimit = 500000 > > ? A = [ Mod(9, 10), Mod(1, 10), Mod(9, 10); Mod(6, 10), Mod(9, 10), > Mod(7, 10); Mod(4, 10), Mod(0, 10), Mod(1, 10)]; > > ? 1/A > > *** impossible inverse modulo: Mod(5, 10). > > PARI only know perform polynomial and linear algebra over a field, so > it assumes that moduli are prime. Yes, of course. My point is that it would be better if it worked also if the base-ring is not a field. Since even in that case, this is a meaningful question, a useful answer would be preferable, in my opinion. I did a sketchy implementation of what I outlined in the other mail, and a patch is attached. This patch only works for small integers right now, as I have only modified Flm_gauss(), but not FpM_gauss(). It's not yet production code, only a prototype for discussion. It seems to work fine for what I need it, and so I'd like to ask if a more complete and tested patch among the same lines would be considered for inclusion in PARI. Baring any mistakes I may have made, the code should work for any modulus, and claim that a matrix is singular only if it is indeed singular. As is, the code in the patch reduces to the original algorithm if p is a prime. For composite moduli, O(log(m)) copies of the matrix are kept on the stack in the worst case. Incidentially, this patch seems to fix a nasty bug with Flm_gauss() when the right hand side is a t_COL instead of a t_MAT; but I could not find any caller using it that way in any case. (And I _might_ be misunderstanding the old code, but I think that is unlikely.) I have now also noticed that there is matsolvemod(), which seems to work fine, too. So alternatively it would be possible to implement an algorithm on top of modsolvemod(); would that be better or does it have other drawbacks such as slower performance? (Is it possible to give a bunch of right hand sides to matsolvemod() at once?) Comments? Best, --Lorenz -- Neu: GMX FreeDSL Komplettanschluss mit DSL 6.000 Flatrate + Telefonanschluss für nur 17,95 Euro/mtl.!* http://dslspecial.gmx.de/freedsl-surfflat/?ac=OM.AD.PD003K11308T4569a
diff --git a/src/basemath/alglin1.c b/src/basemath/alglin1.c --- a/src/basemath/alglin1.c +++ b/src/basemath/alglin1.c @@ -19,6 +19,9 @@ /** (first part) **/ /** **/ /********************************************************************/ + +#include <assert.h> + #include "pari.h" #include "paripriv.h" @@ -615,6 +618,8 @@ } return u; } + +#if 0 /* assume 0 <= a[i,j] < p */ static uGEN Fl_get_col_OK(GEN a, uGEN b, long li, ulong p) @@ -637,15 +642,19 @@ } return u; } +#endif + static uGEN -Fl_get_col(GEN a, uGEN b, long li, ulong p) +Fl_get_col(GEN a, uGEN b, long li, long last_row, ulong p) { uGEN u = (uGEN)cgetg(li+1,t_VECSMALL); ulong m = b[li] % p; long i,j; - u[li] = Fl_mul(m, ucoeff(a,li,li), p); - for (i=li-1; i>0; i--) + for (i=li; i>last_row; i--) { + u[i] = b[i] % p; + } + for (; i>0; i--) { m = b[i]%p; for (j = i+1; j <= li; j++) @@ -674,12 +683,14 @@ gel(b,i) = Fq_red(gel(b,i), T,p); gel(b,k) = gsub(gel(b,k), gmul(m, gel(b,i))); } +#if 0 static void /* assume m < p && SMALL_ULONG(p) && (! (b[i] & b[k] & HIGHMASK)) */ _Fl_submul_OK(uGEN b, long k, long i, ulong m, ulong p) { b[k] -= m * b[i]; if (b[k] & HIGHMASK) b[k] %= p; } +#endif static void /* assume m < p */ _Fl_submul(uGEN b, long k, long i, ulong m, ulong p) { @@ -923,40 +934,270 @@ return z; } -/* destroy a, b */ +/* Helper functions for Flm_gauss_sp */ + +static int +ulong_cmp(const void *v1, const void *v2) +{ + const ulong l1 = *(const ulong *)v1; + const ulong l2 = *(const ulong *)v2; + + if(l1 < l2) return -1; + if(l1 > l2) return 1; + return 0; +} + +/* + find_coprime_factors(a, b) + + Given a, b; find m such that m | ab and that + + m * (ab/m) + + is a coprime factorization of a * b. This fails when a and b are + powers of the same integer. Otherwise it succeeds. + + RETURN m coprime on success + a * b otherwise. + */ + +static ulong +find_coprime_factors(ulong a, ulong b) +{ + const int N = sizeof(ulong) * 8; + ulong r[N]; + + int n = 2; + r[0] = a; + r[1] = b; + + /* Build a list of factors that are either coprime or identical */ + int i; + for(i = 1; i < n; ++i) { + int j; + for(j = 0; j < i; ++j) { + if(r[i] == r[j]) + continue; + long s; + ulong v, v1; + ulong g = xgcduu(r[i], r[j], 1, &v, &v1, &s); + if(g == 1) + continue; + if(g == r[j]) { + r[i] /= g; + r[n++] = g; + --j; + continue; + } + if(g == r[i]) { + r[j] /= g; + r[n++] = g; + --j; + continue; + } + + r[i] /= g; + r[j] /= g; + r[n++] = g; + r[n++] = g; + } + } + +#if 1 + /* Ok, so this was messy, recheck. */ + for(i = 1; i < n; ++i) { + assert(r[i] != 1); + int j; + for(j = 0; j < i; ++j) { + if(r[i] == r[j]) + continue; + long s; + ulong v, v1; + ulong g = xgcduu(r[i], r[j], 1, &v, &v1, &s); + assert(g == 1); + } + } +#endif + + /* Sort */ + qsort(r, n, sizeof(ulong), ulong_cmp); + + /* Build return value */ + ulong ret = r[0]; + for(i = 1; i < n && r[i] == r[0]; ++i) + ret *= r[0]; + + return ret; +} + +/* + GEN Flm_gauss_sp(a, b, ulong p) + + Perform Gaussian elimination modulo a small integer p. a is the + matrix, b the right hand side, which can be either a matrix or a + column vector. + + This is the worker function that destroys both a and b. + */ + static GEN Flm_gauss_sp(GEN a, GEN b, ulong p) { - long i, j, k, li, bco, aco = lg(a)-1; - const int OK_ulong = 0; - int iscol; - GEN u; + /* Return Empty matrix if a is empty. */ + const long int aco = lg(a)-1; /* aco: # of columns of a */ + if (!aco) return cgetg(1,t_MAT); - if (!aco) return cgetg(1,t_MAT); - li = lg(a[1])-1; - bco = lg(b)-1; - iscol = (typ(b)!=t_MAT); + const long int li = lg(a[1])-1; /* li: # of rows of a */ + + /* If b is a column vector, + temporarily wrap it into a matrix. */ + const int iscol = (typ(b)!=t_MAT); if (iscol) b = mkmat(b); + const long int bco = lg(b)-1; /* bco: # of cols of b */ + + /* First step: Triangularize the matrix. */ + long int i, j, k; for (i=1; i<=aco; i++) { - ulong invpiv; - /* Fl_get_col wants 0 <= a[i,j] < p for all i,j */ - if (OK_ulong) for (k = 1; k < i; k++) ucoeff(a,k,i) %= p; + /* Find a pivot row in the ith column */ + int done = 0; /* Flag to cancel the rest of the triangularization + procedure */ for (k = i; k <= li; k++) { - ulong piv = ( ucoeff(a,k,i) %= p ); - if (piv) { ucoeff(a,k,i) = Fl_inv(piv, p); break; } + const ulong piv = ( ucoeff(a,k,i) %= p ); + if (piv != 0) { + + /* Compute the inverse of the pivot */ + ulong xv, xv1; + long s; + ulong g = xgcduu(p, piv, 1, &xv, &xv1, &s); + + if (g != 1ul) { + /* + * Not invertible ? + * + * If this happens, g is a nontrivial factor of p, and so we + * can split the computation into two instances that solve the + * remaining problem mod g and mod (p/g), respectively. + * Then we lift these partial solutions to one that is the + * correct solution mod p. + * + * The splitting will not work if g and pg := p/g are not + * coprime. We can always find a coprime factorization + * unless g and pg are powers of the same integer. If this is + * true for every candidate pivot, the matrix is not + * invertible. + */ + + /* Make sure p and pg have no common factors */ + g = find_coprime_factors(g, p/g); + if(g == p) /* Row can't be pivot. */ + continue; + ulong pg = p/g; + + /* Exchange the rows k and i */ + if (k != i) + { + for (j=i; j<=aco; j++) swap(gcoeff(a,i,j), gcoeff(a,k,j)); + for (j=1; j<=bco; j++) swap(gcoeff(b,i,j), gcoeff(b,k,j)); + } + + /* Save sp, so we can later reclaim scratch storage. */ + pari_sp av = avma; + + /* Build and solve Z/gZ instance */ + GEN aa = cgetg(1 + (aco - i + 1), t_MAT); + for (j = 1; j <= aco - i + 1; ++j) { + GEN v = (gel(aa, j) = cgetg(1 + (li - i + 1), t_VECSMALL)); + long int e; + for (e = 1; e <= li - i + 1; ++e) { + v[e] = gel(a, j + i - 1)[e + i - 1] % g; + } + } + + GEN bb = cgetg(bco + 1, t_MAT); + for (j = 1; j <= bco; ++j) { + GEN v = (gel(bb, j) = cgetg(1 + (li - i + 1), t_VECSMALL)); + long int e; + for (e = 1; e <= li - i + 1; ++e) { + v[e] = gel(b, j)[e + i - 1] % g; + } + } + + GEN sg = Flm_gauss_sp(aa, bb, g); + if(sg == 0) { avma = av; return NULL; } + + /* Build and solve Z/(p/g)Z instance */ + aa = cgetg(1 + (aco - i + 1), t_MAT); + for (j = 1; j <= aco - i + 1; ++j) { + GEN v = (gel(aa, j) = cgetg(1 + (li - i + 1), t_VECSMALL)); + long int e; + for (e = 1; e <= li - i + 1; ++e) { + v[e] = gel(a, j + i - 1)[e + i - 1] % pg; + } + } + + bb = cgetg(bco + 1, t_MAT); + for (j = 1; j <= bco; ++j) { + GEN v = (gel(bb, j) = cgetg(1 + (li - i + 1), t_VECSMALL)); + long int e; + for (e = 1; e <= li - i + 1; ++e) { + v[e] = gel(b, j)[e + i - 1] % pg; + } + } + + GEN spg = Flm_gauss_sp(aa, bb, pg); + if(spg == 0) { avma = av; return NULL; } + + /* Combine. + * Here, ipg is 1 mod p/g and 0 mod g, + * ig is 0 mod p/g and 1 mod g. + */ + const ulong ipg = Fl_mul(g, Fl_inv(g % pg, pg), p); + const ulong ig = Fl_mul(pg, Fl_inv(pg % g, g), p); + for (j = 1; j <= bco; ++j) { + long int e; + GEN v = gel(b, j); + GEN vsg = gel(sg, j); + GEN vspg = gel(spg, j); + for (e = 1; e <= li - i + 1; ++e) { + v[e + i - 1] = Fl_add(Fl_mul(ig, vsg[e], p), + Fl_mul(ipg, vspg[e], p), p); + } + } + + /* Done, readjust counters and go to backsubstitution */ + avma = av; + --i; + done = 1; + break; + } + + /* We store the inverse pivot value in the (k, i) position for + later use. */ + xv = xv1 % p; + if (s < 0) xv = p - xv; + ucoeff(a,k,i) = xv; + break; + } } - /* found a pivot on line k */ + + /* no pivot? matrix not invertible, abort */ if (k > li) return NULL; + if (done) break; + + /* swap rows to bring pivot to row i */ if (k != i) - { /* swap lines so that k = i */ + { for (j=i; j<=aco; j++) swap(gcoeff(a,i,j), gcoeff(a,k,j)); for (j=1; j<=bco; j++) swap(gcoeff(b,i,j), gcoeff(b,k,j)); } + + /* Last column? Done. */ if (i == aco) break; - invpiv = ucoeff(a,i,i); /* 1/piv mod p */ + /* Use the pivot row to eliminate column i in rows > i */ + const ulong invpiv = ucoeff(a,i,i); /* 1/piv mod p */ for (k=i+1; k<=li; k++) { ulong m = ( ucoeff(a,k,i) %= p ); @@ -966,20 +1207,18 @@ if (m == 1) { for (j=i+1; j<=aco; j++) _Fl_sub((uGEN)a[j],k,i, p); for (j=1; j<=bco; j++) _Fl_sub((uGEN)b[j],k,i, p); - } else if (OK_ulong) { - for (j=i+1; j<=aco; j++) _Fl_submul_OK((uGEN)a[j],k,i,m, p); - for (j=1; j<=bco; j++) _Fl_submul_OK((uGEN)b[j],k,i,m, p); } else { for (j=i+1; j<=aco; j++) _Fl_submul((uGEN)a[j],k,i,m, p); for (j=1; j<=bco; j++) _Fl_submul((uGEN)b[j],k,i,m, p); } } } - u = cgetg(bco+1,t_MAT); - if (OK_ulong) - for (j=1; j<=bco; j++) ugel(u,j) = Fl_get_col_OK(a,(uGEN)b[j], aco,p); - else - for (j=1; j<=bco; j++) ugel(u,j) = Fl_get_col(a,(uGEN)b[j], aco,p); + + /* Second Step: Backsubstitute. */ + GEN u = cgetg(bco+1,t_MAT); + for (j=1; j<=bco; j++) ugel(u,j) = Fl_get_col(a,(uGEN)b[j], aco, i, p); + + /* Done, unwrap result if necessary */ return iscol? gel(u,1): u; }