The main coding theory problem#


Table of Contents#


Programming Environment#

Hide code cell source
# %load imports.py
import numpy  as np
import pandas as pd

import matplotlib        as mpl
from   matplotlib.patches import Rectangle
import matplotlib.pyplot as plt
plt.style.use('ggplot');
plt.rcParams.update({'text.usetex' : True});
%matplotlib inline

from   collections import Counter,defaultdict
from   itertools   import combinations,product
import itertools

from typing import Set

from IPython.display import display, Math

from   datetime import datetime as d
import locale                   as l
import platform                 as p
import sys                      as s

pad = 20
print(f"{'Executed'.upper():<{pad}}: {d.now()}")
print()
print(f"{'Platform'   :<{pad}}: "
      f"{p.mac_ver()[0]} | "
      f"{p.system()} | "
      f"{p.release()} | "
      f"{p.machine()}")
print(f"{''           :<{pad}}: {l.getpreferredencoding()}")
print()
print(f"{'Python'     :<{pad}}: {s.version}")
print(f"{''           :<{pad}}: {s.version_info}")
print(f"{''           :<{pad}}: {p.python_implementation()}")
print()
print(f"{'Matplotlib' :<{pad}}: {mpl.__version__}")
print(f"{'NumPy'      :<{pad}}: {np .__version__}")

#==================================================

def dec_to_bin_repr (a : int,
                     n : int) -> str:
  """Convert a decimal number [int] to a binary number of length n [str].
  
  Parameters
  ==========
  a : decimal number
  n : number of places

  Returns
  =======
  return the binary representation of an integer as a string to n positions
  """
  return format(a,f'0{n}b')
#print(dec_to_bin_repr(17,10))

def bin_repr_inv (a : int,
                  n : int) -> str:
  """Convert a decimal number [int] to the inverse of a binary number of length n [str].
  
  Parameters
  ==========
  a : decimal number
  n : number of places

  Returns
  =======
  return the inverse of the binary representation of an integer as a string to n positions
  """
  return format(a^(2**n-1),f'0{n}b')
#print(bin_repr_inv(17,10))

def rc (q : int,
        n : int) -> Set[str]:
  """Repetition Code
  Generate a q-ary repetition block code of length n.
  """
  S=set()
  for i in range(q):
    S.add(str(i)*n)
  return S

def fqn (q : int,
         n : int,
         g : int = 0) -> Set[str]:
  """Construct a linear space of dimension n over a finite field of order q.
  
  Parameters
  ==========
  g : If the space is very large, opt for the first g elements of a generator object.
  """
  if bool(g):
    f=itertools.product(range(q),repeat=n)
    return set(''.join(str(i) for i in next(f)) for _ in range(g))
  else:
    return {''.join(str(bit) for bit in word) for word in itertools.product(range(q),repeat=n)}

def qarycode_to_nbitstring (code={'3121','2101'},k=4):
  """Convert a q-ary code """
  for n in code:
    print(' '.join(format(int(i),f'0{k}b') for i in n))

def hd (a : str = '1001',
        b : str = '0101') -> int:
  """HAMMING DISTANCE
  
  Parameters
  ==========
  x : str
  y : str

  Return
  ======
  int
  """
  assert len(a) == len(b), 'x and y must have the same length'
  return sum(x!=y for x,y in zip(a,b))

def nbfmd (c  : Set[str],
           pr : bool = False) -> np.float16:
  """NAIVE BRUTE FORCE MINIMUM DISTANCE d(C)

  Computes the pairwise Hamming distance for all codewords and returns the minimum value.

  This is a naive (i.e., non vectorized) implementation using nested for loops.
  
  Parameters
  ==========
  c  : code
  pr : Print intermediate steps.

  Returns
  =======
  d(C)
  """

  # convert a set of string vectors to a 2D NumPy array of integers
  c=np.array([list(codeword) for codeword in c],dtype=np.float16)

  # intialize empty hamming distance matrix
  hamming = np.empty([c.shape[0]]*2,dtype=np.float16)
  for i,x in enumerate(c):
    for j,y in enumerate(c):
      hamming[i,j]=(x!=y).sum()
  # the diagonal represents the Hamming distance of a codeword with itself, which is always 0.
  np.fill_diagonal(hamming,np.inf)

  if pr == True:
    print(hamming)

  return hamming.min().astype(np.int8)

def one_error_detecting (q    : int,
                         code : Set[str],
                         p    : bool = False) -> bool:
  """Verify that a code is one-error detecting.
  No one-bit error equals a codeword.
  """
  flag=True
  alphabet=set(str(i) for i in range(q))
  for codeword in code:
    if p:
      print()
      print(f"{'orig cw : ':10}{codeword}")
    for i in range(len(codeword)):
      a,b,c=codeword[:i],codeword[i],codeword[i+1:]
      symbols=alphabet-set(codeword[i])
      for symbol in symbols:
        cw=codeword[:i]+symbol+codeword[i+1:] # SINGLE ERROR
        if cw in code:
          flag=False
          if p:
            print(f"{'ERROR':10}{cw}")
        else:
          if p:
            print(f"{'':10}{cw}")
  return flag

# set(''.join(l for l in i) for i in itertools.product('10',repeat=3))
# set(''.join(l for l in i) for i in itertools.combinations_with_replacement('012',r=3))
# set(''.join(l for l in i) for i in itertools.combinations('01',r=2))

def vadd (vecs : set[str],
          q    : int) -> str:
  """VECTOR SUM
  Takes a set of q-ary vectors and computes their sum mod q.
  """
  return ''.join((np.array(list(list(vec) for vec in vecs),dtype=np.int8).sum(axis=0)%q).astype(str))

def vmul (vecs : set[str],
          q    : int) -> str:
  """VECTOR PRODUCT
  Takes a set of q-ary vectors and computes their product mod q.
  """
  return ''.join((np.array(list(list(vec) for vec in vecs),dtype=np.int8).prod(axis=0)%q).astype(str))

def vw (v : str) -> int:
  """VECTOR WEIGHT"""
  c=Counter(v)
  if '0' in c.keys():
    c.pop('0')
  return c.total()

def overall_parity_check (code : set[str],
                          q    : int,
                          arr  : int = 0) -> np.ndarray:
  code=np.array(list(list(codeword) for codeword in sorted(code)),dtype=np.int8)
  code_as_array=np.concatenate([code,(code.sum(axis=1)%q).reshape((code.shape[0],1))],axis=1)
  if arr:
    return code_as_array
  else:
    return {''.join(codeword) for codeword in code_as_array.astype(str)}
EXECUTED            : 2024-05-21 15:46:17.642449

Platform            : 14.4.1 | Darwin | 23.4.0 | arm64
                    : UTF-8

Python              : 3.11.9 | packaged by conda-forge | (main, Apr 19 2024, 18:34:54) [Clang 16.0.6 ]
                    : sys.version_info(major=3, minor=11, micro=9, releaselevel='final', serial=0)
                    : CPython

Matplotlib          : 3.8.4
NumPy               : 1.26.4

The main coding theory problem#

A good \((n,M,d)\)-code has

(i) small \(n\) for fast transmission of messages

(ii) large \(M\) to enable transmission of a wide variety of messages

(iii) large \(d\) to correct many errors

The main coding theory problem is to optimize one of the parameters \(n,M,d\) for given values of the other two.

Usually, we want to find the largest code of a given length \(n\) and given minimum distance \(d\).

\(A_q(n,d)\) is the largest value of \(M\) such that there exists a \(q\)-ary \((n,M,d)\)-code.

\(A_q(n,d)=\max M\ni\exists\,q\text{-ary}\,(n,M,d)\text{-code}\)

Theorem#

(i) \(A_q(n,1)=q^n\)

(ii) \(A_q(n,n)=q\)

PROOF (i)

If \(d(C)\ge1\) then the codewords must be distinct.

The largest \(q\)-ary \((n,M,d)\)-code is just \((F_q)^n\), and \(M=q^n=|(F_q)^n|\).

\(\blacksquare\)

PROOF (ii)

Suppose \(C\) is a \(q\)-ary \((n,M,n)\)-code.

Then any two distinct codewords differ in all \(n\) positions (columns).

And so the symbols appearing in any fixed position (column) in the \(M\) codewords must be distinct.

Therefore, \(M\le q\). [There can be no more than \(q\) codewords.]

And \(A_q(n,n)\le q\). [The largest value of \(M\) such that… is bounded above by \(q\).]

We already know that the \(q\)-ary repetition code of length \(n\) is an \((n,q,n)\)-code.

Therefore, \(A_q(n,n)=q\).

\(\blacksquare\)

\(q\le A_q(n,d)\le q^n\) for \(1\le d\le n\)


Equivalence of Codes#

Permutation#

[PERMUTATION]

A permutation \(f\) of a set \(S=\{x_1,x_2,...,x_n\}\) is a one-to-one correspondence (or mapping) from \(S\) to itself.

\( \left( \begin{matrix} x_1 & x_2 & \dots & x_n \\ \downarrow & \downarrow & & \downarrow \\ f(x_1) & f(x_2) & \dots & f(x_n) \\ \end{matrix} \right) \)

Equivalence#

Two \(q\)-ary codes are called equivalent if one can be obtained from the other by a combination of operations of the following types:

(A) permutation of the positions (columns) of the code

(B) permutation of the symbols appearing in a fixed position (column)

If a code is displayed as an \(M\times n\) matrix whose rows are the codewords, then an operation of type (A) corresponds to a permutation or rearrangement of the columns of the matrix, while an operation of type (B) corresponds to a relabelling of the symbols appearing in a given column.

The distances between codewords are unchanged by such operations, and so equivalent codes have the same parameters \((n,M,d)\) and will correct the same number of errors.

Under the assumptions of a \(q\)-ary symmetric communication channel, the performances of equivalent codes will be identical in terms of probabilities of error correction.

Lemma#

Any \(q\)-ary \((n,M,d)\)-code over an alphabet \(\{0,1,...,q-1\}\) is equivalent to an \((n,M,d)\)-code which contains the zero vector \(\mathbf{0}=\underbrace{00...0}_{n}\).

PROOF

Choose any codeword \(\mathbf{x}=x_1x_2...x_n\) and for each \(x_i\ne0\) apply the permutation

\( \left( \begin{matrix} 0 & x_i & j \\ \downarrow & \downarrow & \downarrow \\ x_i & 0 & j \\ \end{matrix} \right) \forall j\ne0,x_i \)

to the symbols in position \(i\).

\(\blacksquare\)


Example#

What is \(A_2(5,3)\)? In other words, what is the largest value of \(M\) such that there exists a binary \((5,M,3)\)-code?

We are already familiar with a binary \((5,4,3)\)-code:

\( \begin{aligned} C= \begin{cases} 00000\\ 01101\\ 10110\\ 11011\\ \end{cases} \end{aligned} \)

So \(A_2(5,3)\ge4\).

Is there a binary \((5,5,3)\)-code?

Brute-force method: consider all subsets of order \(5\) in \((F_2)^5\) (over 200,000 of them) and find the minimum distance of each.

q=2
n=5
print(f"{q**n:,}")
f=fqn(q,n)
f
32
{'00000',
 '00001',
 '00010',
 '00011',
 '00100',
 '00101',
 '00110',
 '00111',
 '01000',
 '01001',
 '01010',
 '01011',
 '01100',
 '01101',
 '01110',
 '01111',
 '10000',
 '10001',
 '10010',
 '10011',
 '10100',
 '10101',
 '10110',
 '10111',
 '11000',
 '11001',
 '11010',
 '11011',
 '11100',
 '11101',
 '11110',
 '11111'}
len(list(itertools.combinations(f,r=5)))
201376
max(list(nbfmd(c) for c in itertools.combinations(f,r=5)))
2

Let’s show that a binary \((5,M,3)\)-code must have \(M\le4\) and that the \((5,4,3)\)-code is unique, up to equivalence.

Suppose \(C\) is a \((5,M,3)\)-code with \(M\ge4\).

By the lemma we can assume that \(\mathbf{0}\in C\) or an equivalent code.

[

\(00000\)

]

\(C\) contains at most one codeword having four or five 1s. For if there were even two such codewords \(\mathbf{x}\) and \(\mathbf{y}\) then they would have at least three 1s in common positions and \(d(\mathbf{x,y})\le2\), contradicting \(d(C)=3\).

[

No more than one of

\(11111,01111,10111,11011,11101,11110\)

]

Since \(\mathbf{0}\in C\), there can be no codewords containing just one or two 1s

[

None of

\(10000,01000,00100,00010,00001,11000,10100,10010,10001,01100,01010,01001,00110,00101,00011\)

]

and so there must be two codewords containing exactly three 1s

[

At least two of

\(11100,11010,11001,01110,01101,00111\)

]

So far, our code looks like this

\( C= \begin{cases} 00000\\ 11100\\ 00111\\ \end{cases} \)

By trial and error, there is no other codeword of three 1s that is three away from both the other codewords with three 1s and there is only one codeword with four or five 1s that is.

\( C= \begin{cases} 00000\\ 11100\\ 00111\\ 11011\\ \end{cases} \)

Therefore, \(A_2(5,3)=4\) and the code which achieves this value is unique, up to equivalence.


Vector operations modulo 2#

Let \(\mathbf{x,y}\in(F_2)^n\) where \(\mathbf{x}=x_1x_2...x_n\) and \(\mathbf{y}=y_1y_2...y_n\).

x='11100'
y='00111'

Sum#

\(\mathbf{x+y}\in(F_2)^n\) where \(\mathbf{x+y}=(x_1+y_1\mod2,x_2+y_2\mod2,...,x_n+y_n\mod2)\)

vadd({x,y},2)
'11011'

Product#

\(\mathbf{x\cap y}\in(F_2)^n\) where \(\mathbf{x\cap y}=(x_1y_1\mod2,x_2y_2\mod2,...,x_ny_n\mod2)\)

vmul({x,y},2)
'00100'

Weight#

The weight \(w(\mathbf{x})\) of a vector \(\mathbf{x}\in(F_2)^n\) is the number of 1s in \(\mathbf{x}\).

vw(x)
3

Lemma 2.5#

[LEMMA 2.5]

If \(\mathbf{x,y}\in(F_2)^n\) then \(d(\mathbf{x,y})=w(\mathbf{x+y})\).

[The distance between two binary vectors is just the weight of their modular sum.]

PROOF

\(\mathbf{x+y}\) has a \(1\) where \(\mathbf{x}\) and \(\mathbf{y}\) differ and a \(0\) where \(\mathbf{x}\) and \(\mathbf{y}\) agree.

\(\blacksquare\)

Lemma 2.6#

[LEMMA 2.6]

If \(\mathbf{x,y}\in(F_2)^n\) then \(d(\mathbf{x,y})=w(\mathbf{x})+w(\mathbf{y})-2w(\mathbf{x\cap y})\).

PROOF

\(d(\mathbf{x,y})=w(\mathbf{x+y})=\) the number of 1s in \(\mathbf{x}-\) the number of 1s in \(\mathbf{y}-2\) the number of positions where both \(\mathbf{x}\) and \(\mathbf{y}\) have a 1

[We multiply the third term by 2 to subtract 1 from and account for each of the two vectors who share the 1 in a common position.]

\(\blacksquare\)

Theorem 2.7#

[THEOREM 2.7]

Suppose \(d\) is odd.

Then a binary \((n,M,d)\)-code exists iff a binary \((n+1,M,d+1)\)-code exists.

PROOF [only if]

If a binary \((n,M,d)\)-code exists then a binary \((n+1,M,d+1)\)-code exists.

Suppose \(C\) is a binary \((n,M,d)\)-code where \(d\) is odd.

Let \(\hat{C}\) be the code of length \(n+1\) obtained from \(C\) by extending each codeword \(\mathbf{x}\) of \(C\) according to the rule

ADDING AN OVERALL PARITY CHECK TO THE CODE C

\( \begin{aligned} \mathbf{x}&=x_1x_2...x_n \\ \mathbf{\hat{x}}&= \begin{cases} x_1x_2...x_n0 & \text{if}\,w(\mathbf{x})\,\text{is even}\\ x_1x_2...x_n1 & \text{if}\,w(\mathbf{x})\,\text{is odd}\\ \end{cases} \\ &=x_1x_2...x_nx_{n+1} \,\,\,\text{where}\,\,\, x_{n+1}=\left(\sum_{i=1}^nx_i\right)\mod2 \end{aligned} \)

Since \(w(\mathbf{\hat{x}})\) is even for every codeword \(\mathbf{\hat{x}}\in\hat{C}\) it follows from lemma 2.6 that \(d(\mathbf{\hat{x},\hat{y}})\) is even for all \(\mathbf{\hat{x},\hat{y}}\in\hat{C}\).

Therefore, \(d(\hat{C})\) is even.

\(d\le d(\hat{C})\le d+1\)

Since \(d\) is odd, \(d(\hat{C})=d+1\).

Therefore, \(\hat{C}\) is a \((n+1,M,d+1)\)-code.

PROOF [if]

If a binary \((n+1,M,d+1)\)-code exists then a binary \((n,M,d)\)-code exists.

Suppose \(D\) is a binary \((n+1,M,d+1)\)-code where \(d\) is odd.

Choose codewords \(\mathbf{x,y}\in D\ni d(\mathbf{x,y})=d+1\).

Choose a position in which \(\mathbf{x}\) and \(\mathbf{y}\) differ and delete this from all codewords.

The result is a \((n,M,d)\)-code.

\(\blacksquare\)

Corollary 2.8#

[COROLLARY 2.8]

If \(d\) is odd, then \(A_2(n+1,d+1)=A_2(n,d)\).

Equivalently, if \(d\) is even, then \(A_2(n,d)=A_2(n-1,d-1)\).


Known values of \(A_2(n,d)\)#

  • Sloane 1982 pp. 156

  • MacWilliams and Sloane 1977 pp. 674

\(n\)

\(d=3\)

\(d=5\)

\(d=7\)

5

4

2

6

8

2

7

16

2

2

8

20

4

2

9

40

6

2

10

72-79

12

2

11

144-158

24

4

12

256

32

4

13

512

64

8

14

1024

128

16

15

2048

256

32

16

2560-3276

256-340

36-37

\( \begin{aligned} A_2(5,3)&=4=A_2(6,4)\\ A_2(5,5)&=2=A_2(6,6)\\ A_2(6,3)&=8=A_2(7,4)\\ A_2(6,5)&=2=A_2(7,6)\\ ... \end{aligned} \)


Examples of constructing codes via adding an overall parity check#

\( \text{binary}\,\)(5,4,3)\(\text{-code}\, C= \begin{cases} 00000\\ 01101\\ 10110\\ 11011\\ \end{cases} \)

c={'00000','01101','10110','11011'}
c
{'00000', '01101', '10110', '11011'}
overall_parity_check(c,2)
{'000000', '011011', '101101', '110110'}
nbfmd(overall_parity_check(c,2))
4

Binomial Coefficients#

If \(n\) and \(m\) are integers with \(0\le m\le n\), then the binomial coefficient “\(n\) choose \(m\)” is

\( \begin{aligned} {n\choose m} =\frac{n!}{m!(n-m)!} \end{aligned} \)

where \(m!=m\cdot(m-1)\cdot...\cdot3\cdot2\cdot1\) for \(m\gt0\) and \(0!=1\).

Lemma 2.10#

[LEMMA 2.10]

The number of unordered selections of \(m\) distinct objects from a set of \(n\) distinct objects is \(n\choose m\).

PROOF

An ordered selection of \(m\) distinct objects from a set of \(n\) distinct objects can be made in

\( \begin{aligned} n(n-1)...(n-m+1)=\frac{n!}{(n-m)!} \end{aligned} \)

ways, for the first object can be chosen in any of \(n\) ways, then the second in any of \(n-1\) ways, and so on.

Since there are \(m\cdot(m-1)\cdot...\cdot2\cdot1=m!\) ways of ordering the \(m\) objects chosen, the number of unordered selections is

\( \begin{aligned} \frac{n!}{m!(n-m)!} \end{aligned} \)

\(\blacksquare\)