22

I'd like to take the modular inverse of a matrix like [[1,2],[3,4]] mod 7 in Python. I've looked at numpy (which does matrix inversion but not modular matrix inversion) and I saw a few number theory packages online, but nothing that seems to do this relatively common procedure (at least, it seems relatively common to me).

By the way, the inverse of the above matrix is [[5,1],[5,3]] (mod 7). I'd like Python to do it for me though.

Shai
  • 111,146
  • 38
  • 238
  • 371
John
  • 341
  • 1
  • 2
  • 7
  • 1
    If you end up writing your own little piece of code. Please consider sharing it here as I think a lot of us might be interested :). – bastijn Nov 27 '10 at 12:42
  • Modular matrix inversion is built into `sympy` (possibly new since this question was asked) and modular row-reduction is fairly easy, too. See http://stackoverflow.com/a/37015283/2747370 – Chris Chudzicki May 05 '16 at 16:05

5 Answers5

12

Okay...for those who care, I solved my own problem. It took me a while, but I think this works. It's probably not the most elegant, and should include some more error handling, but it works:

import numpy
import math
from numpy import matrix
from numpy import linalg

def modMatInv(A,p):       # Finds the inverse of matrix A mod p
  n=len(A)
  A=matrix(A)
  adj=numpy.zeros(shape=(n,n))
  for i in range(0,n):
    for j in range(0,n):
      adj[i][j]=((-1)**(i+j)*int(round(linalg.det(minor(A,j,i)))))%p
  return (modInv(int(round(linalg.det(A))),p)*adj)%p

def modInv(a,p):          # Finds the inverse of a mod p, if it exists
  for i in range(1,p):
    if (i*a)%p==1:
      return i
  raise ValueError(str(a)+" has no inverse mod "+str(p))

def minor(A,i,j):    # Return matrix A with the ith row and jth column deleted
  A=numpy.array(A)
  minor=numpy.zeros(shape=(len(A)-1,len(A)-1))
  p=0
  for s in range(0,len(minor)):
    if p==i:
      p=p+1
    q=0
    for t in range(0,len(minor)):
      if q==j:
        q=q+1
      minor[s][t]=A[p][q]
      q=q+1
    p=p+1
  return minor
John
  • 341
  • 1
  • 2
  • 7
  • It's not perfect yet. I just realized that int(linalg.det(A)) doesn't always give you the correct determinant. Hmm..not a big fan of numpy's determinant algorithm. For the matrices I'm dealing with (right now just small 3x3 matrices), the determinant should just be an integer! Why is numpy's det algorithm getting it wrong?? – John Nov 27 '10 at 18:49
  • I'm now using int(round(linalg.det(A))). Gross. But I think it works. – John Nov 27 '10 at 18:55
  • Thanks for sharing, saved it so I can use it in a later stadium :). – bastijn Nov 28 '10 at 12:25
12

A hackish trick which works when rounding errors aren't an issue:

  • find the regular inverse (may have non-integer entries), and the determinant (an integer), both implemented in numpy
  • multiply the inverse by the determinant, and round to integers (hacky)
  • now multiply everything by the determinant's multiplicative inverse (modulo your modulus, code below)
  • do entrywise mod by your modulus

A less hackish way is to actually implement gaussian elimination. Here's my code using Gaussian elimination, which I wrote for my own purposes (rounding errors were an issue for me). q is the modulus, which is not necessarily prime.

def generalizedEuclidianAlgorithm(a, b):
    if b > a:
        return generalizedEuclidianAlgorithm(b,a);
    elif b == 0:
        return (1, 0);
    else:
        (x, y) = generalizedEuclidianAlgorithm(b, a % b);
        return (y, x - (a / b) * y)

def inversemodp(a, p):
    a = a % p
    if (a == 0):
        print "a is 0 mod p"
        return None
    if a > 1 and p % a == 0:
        return None
    (x,y) = generalizedEuclidianAlgorithm(p, a % p);
    inv = y % p
    assert (inv * a) % p == 1
    return inv

def identitymatrix(n):
    return [[long(x == y) for x in range(0, n)] for y in range(0, n)]

def inversematrix(matrix, q):
    n = len(matrix)
    A = np.matrix([[ matrix[j, i] for i in range(0,n)] for j in range(0, n)], dtype = long)
    Ainv = np.matrix(identitymatrix(n), dtype = long)
    for i in range(0, n):
        factor = inversemodp(A[i,i], q)
        if factor is None:
             raise ValueError("TODO: deal with this case")
        A[i] = A[i] * factor % q
        Ainv[i] = Ainv[i] * factor % q
        for j in range(0, n):
            if (i != j):
                factor = A[j, i]
                A[j] = (A[j] - factor * A[i]) % q
                Ainv[j] = (Ainv[j] - factor * Ainv[i]) % q
    return Ainv

EDIT: as commenters point out, there are some cases this algorithm fails. It's slightly nontrivial to fix, and I don't have time nowadays. Back then it worked for random matrices in my case (the moduli were products of large primes). Basically, the first non-zero entry might not be relatively prime to the modulus. The prime case is easy since you can search for a different row and swap. In the non-prime case, I think it could be that all leading entries aren't relatively prime so you have to combine them

WuTheFWasThat
  • 583
  • 1
  • 5
  • 11
  • Thanks for the code. I'm long past needing the solution now (took the class last fall), but I appreciate your effort, as does the community I'm sure. I really like your suggestions--nice mathematical reasoning, especially on your first suggestion. For your Gaussian elimination solution, the code you supplied is about the same amount of work as the code I gave, but you _could_ make a case that it's more elegant (though it sounds like both of us had rounding issues). Either way, great work!! Thanks for taking the time to answer the question. – John Jul 18 '11 at 20:49
  • My code doesn't cause rounding issues at all. The first "hackish" suggestion does, though. No problem though! – WuTheFWasThat Jul 26 '11 at 03:07
  • 1
    To make this work in Python 3, replace `long` with `int` and `/` with `//` (the last one really caught me off guard). – Oleh Prypin May 24 '14 at 21:32
  • 1
    Warning: this code does not always find the inverse even if it exists. For example, the inverse matrix of [[0, 1],[1 1]] modulo 5 is [[4,1],[1,0]]. However, the code prints "a is 0 mod p", and gives an incorrect matrix [[0,0],[0,1]]. – jnalanko Jun 14 '15 at 18:43
  • 1
    Also, the `inversemodp` function may return a value **even when a has no inverse mod p**. For example, `inversemodp(2, 4)` returns 1, but `2*1 =/= 1 mod 4`. – Ponkadoodle Dec 06 '15 at 00:52
  • @jnalanko and Wallacoloo thanks for the catches, sorry I only saw this years later. I fixed inversemodp and added notes with caveats. I don't have time for it, but would be great if somebody contributed the fix :) – WuTheFWasThat Nov 20 '17 at 18:15
6

It can be calculated using Sage (www.sagemath.org) as

Matrix(IntegerModRing(7), [[1, 2], [3,4]]).inverse()

Although Sage is huge to install and you have to use the version of python that comes with it which is a pain.

Ben Reynwar
  • 1,547
  • 14
  • 21
  • Sage need not be installed. You can work online with Sage worksheets for free at cocalc.com – rwst Jan 06 '23 at 10:04
4

'sympy' package Matrix class function 'sqMatrix.inv_mod(mod)' computes modulo matrix inverse for small and arbitrarily large modulus. By combining sympy with numpy, it becomes easy to compute modulo inverse of 2-D numpy arrays (see the code snippet below):

import numpy
from sympy import Matrix

def matInvMod (vmnp, mod):
    nr = vmnp.shape[0]
    nc = vmnp.shape[1]
    if (nr!= nc):
        print "Error: Non square matrix! exiting"
        exit()
    vmsym = Matrix(vmnp)
    vmsymInv = vmsym.inv_mod(mod)
    vmnpInv = numpy.array(vmsymInv)
    print "vmnpInv: ", vmnpInv, "\n"
    k = nr
    vmtest = [[1 for i in range(k)] for j in range(k)]  # just a 2-d list
    vmtestInv = vmsym*vmsymInv
    for i in range(k):
      for j in range(k):
         #print i, j, vmtrx2[i,j] % mod
         vmtest[i][j] = vmtestInv[i,j] % mod
    print "test vmk*vkinv % mod \n:", vmtest
    return vmnpInv

if __name__ == '__main__':
    #p = 271
    p = 115792089210356248762697446949407573530086143415290314195533631308867097853951
    vm = numpy.array([[1,1,1,1], [1, 2, 4, 8], [1, 4, 16, 64], [1, 5, 25, 125]])   
    #vminv = modMatInv(vm, p)
    vminv = matInvMod(vm, p)
    print vminv
    vmtestnp = vm.dot(vminv)%p     # test mtrx inversion
    print vmtestnp
vog
  • 23,517
  • 11
  • 59
  • 75
  • 'sympy' package api 'sqMatrix.inv_mod(mod)' computes modulo matrix inverse for small and arbitrarily large modulus. By combining sympy with numpy, it becomes easy to compute modulo inverse of 2-D numpy arrays (see the code snippet below): – user1864863 May 02 '18 at 14:27
1

Unfortunately numpy does not have modular arithmetic implementations. You can always code up the proposed algorithm using row reduction or determinants as demonstrated here. A modular inverse seems to be quite useful for cryptography.

whatnick
  • 5,400
  • 3
  • 19
  • 35