Exercises#

FCCT 1990 Hill, Raymond. A First Course in Coding Theory. Clarendon Press: Oxford Applied Mathematics and Computing Science Series.


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 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 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))
EXECUTED            : 2025-01-17 19:46:27.995738

Platform            : 15.2 | Darwin | 24.2.0 | arm64
                    : UTF-8

Python              : 3.11.11 | packaged by conda-forge | (main, Dec  5 2024, 14:21:42) [Clang 18.1.8 ]
                    : sys.version_info(major=3, minor=11, micro=11, releaselevel='final', serial=0)
                    : CPython

Matplotlib          : 3.9.3
NumPy               : 2.0.2

1#

[1.1]#

If the following message were received from outer space, why might it be conjectured that it was sent by a race of human-like beings who have one arm twice as long as the other?

[Hint: The number of digits in the message is the product of two prime numbers.]

message='00110000011000111111110110010011001001100101111000100100010010001001001100110'
message
'00110000011000111111110110010011001001100101111000100100010010001001001100110'
len(message)
77
c=np.array(list(message)).reshape(11,7)
c
array([['0', '0', '1', '1', '0', '0', '0'],
       ['0', '0', '1', '1', '0', '0', '0'],
       ['1', '1', '1', '1', '1', '1', '1'],
       ['1', '0', '1', '1', '0', '0', '1'],
       ['0', '0', '1', '1', '0', '0', '1'],
       ['0', '0', '1', '1', '0', '0', '1'],
       ['0', '1', '1', '1', '1', '0', '0'],
       ['0', '1', '0', '0', '1', '0', '0'],
       ['0', '1', '0', '0', '1', '0', '0'],
       ['0', '1', '0', '0', '1', '0', '0'],
       ['1', '1', '0', '0', '1', '1', '0']], dtype='<U1')
print(np.array([[''.join(row)] for row in np.array(list(message)).reshape(11,7)]))

fig = plt.figure(figsize=(6,6));
ax  = plt.subplot();

for i,row in enumerate(c):
  for j,col in enumerate(row):
    #print(i,j,col)
    if int(col):
      ax.add_patch(Rectangle((j,i),1,1,facecolor='blue'));

ax.set_aspect(1);
ax.set_xticks(ticks=np.arange(0,c.shape[1]+1));
ax.set_yticks(ticks=np.arange(0,c.shape[0]+1));
ax.set_xlim(0,c.shape[1]);
ax.set_ylim(c.shape[0],0);
[['0011000']
 ['0011000']
 ['1111111']
 ['1011001']
 ['0011001']
 ['0011001']
 ['0111100']
 ['0100100']
 ['0100100']
 ['0100100']
 ['1100110']]
../../../_images/9eae5040364d0ac55487f6b47c69b791eeacf50d4fb3c9b8638b692212ed0cfb.png

“Pictures have actually been transmitted from Earth into outer space in this way.

Two large prime numbers were used so that a much more detailed picture could be sent.

It is reasonable to expect that a civilized recipient of such a message would be able to work out how to reconstruct the picture, since factorization of a number into prime factors is a property independent of language or notation.”

[1.2]#

Suppose the binary repetition code of length \(5\) is used for a binary symmetric channel which has symbol error probability \(p\).

Show that the word error probability of the code is

\( 10p^3-15p^4+6p^5 \)

See the page on the binary repetition code of length \(5\).

[1.3]#

Show that a code \(C\) having minimum distance \(d(C)=4\) can be used simultaneously to correct single errors and detect double errors.

[\(C\) could also be used either as a single-error-correcting code or as a triple-error-detecting code, but not both simultaneously. Why not?]

\( \begin{aligned} d(C)\ge4&=(s=3)+1 \\ d(C)\ge4&=2(t=1.5)+1 \end{aligned} \)

Suppose the vector \(\mathbf{y}\) is received.

If \(d(\mathbf{x,y})\le1\) for some codeword \(\mathbf{x}\in C\), then \(\mathbf{y}\) is closer to \(\mathbf{x}\) than to any other codeword and so it will be decoded to it (and thus corrected).

If \(d(\mathbf{x,y})\ge2\) for all codewords \(\mathbf{x}\in C\), then \(\mathbf{y}\) is equally distant from them all and so cannot be decoded to a closest codeword; and it is not a codeword; so, it can be detected.

Why is this d(x,y)>=2, and not just d(x,y)=2?

If \(d(\mathbf{x,y})=3\) for some codeword \(\mathbf{x}\in C\), then \(\mathbf{y}\) is not a codeword by the minimum distance \(d(C)=4\) of the code.

But it could be the case that \(d(\mathbf{x',y})\le1\) for some other codeword \(\mathbf{x'\ne x}\in C\).

In an error-correcting context, \(\mathbf{y}\) would be decoded to \(\mathbf{x'}\).

[1.4]#

The code used by Mariner 9 will correct any received \(32\)-tuple provided not more than how many errors have occurred?

See the page on the \((32,64,16)\) Reed-Muller code.

[1.5]#

(i) Show that a \(3\)-ary \((3,M,2)\)-code must have \(M\le9\).

Suppose \(C\) is a \((3,M,2)\)-code.

The \(M\) ordered pairs obtained by deleting the third coordinate of each codeword must be distinct; for if any two were identical, then the corresponding codewords would differ only in the third position contradicting \(d(C)=2\).

Therefore, \(M\le q^2\).

(ii) Show that a \(3\)-ary \((3,9,2)\)-code exists.

(iii) Generalize the results of (i) and (ii) to \(q\)-ary \((3,M,2)\)-codes, for any integer \(q\ge2\).

\( \{(a,b,(a+b)\mod q)\mid(a,b)\in(F_q)^2\} \)

is a \(q\)-ary \((3,q^2,2)\)-code.


q=2,(3,4,2)-code#

\( \begin{aligned} (3,4,2)&\text{-code}\subseteq(F_2)^3 \end{aligned} \)

q=2
n=3
f=fqn(q,n)
M=fqn(q,2)
C=[list(m+str(sum([int(i) for i in m])%q) for m in sorted(list(M)))]
for k in range(q-1):
  C.append(list(''.join([str((int(i)+1)%q) for i in m]) for m in C[k]))

print(f"{q**n:,}")
print(f)
print(M)

pd.DataFrame(data=C)
8
{'001', '000', '010', '011', '111', '110', '100', '101'}
{'00', '01', '11', '10'}
0 1 2 3
0 000 011 101 110
1 111 100 010 001

q=3,(3,9,2)-code#

\( \begin{aligned} (3,9,2)&\text{-code}\subseteq(F_3)^3 \end{aligned} \)

q=3
n=3
f=fqn(q,n)
M=fqn(q,2)
C=[list(m+str(sum([int(i) for i in m])%q) for m in sorted(list(M)))]
for k in range(q-1):
  C.append(list(''.join([str((int(i)+1)%q) for i in m]) for m in C[k]))

print(f"{q**n:,}")
print(f)
print(M)

pd.DataFrame(data=C)
27
{'200', '211', '021', '112', '020', '221', '212', '010', '000', '002', '122', '222', '220', '001', '102', '022', '012', '121', '202', '110', '210', '201', '120', '011', '111', '100', '101'}
{'02', '00', '11', '22', '01', '12', '21', '20', '10'}
0 1 2 3 4 5 6 7 8
0 000 011 022 101 112 120 202 210 221
1 111 122 100 212 220 201 010 021 002
2 222 200 211 020 001 012 121 102 110

q=4,(3,16,2)-code#

\( \begin{aligned} (3,16,2)&\text{-code}\subseteq(F_4)^3 \end{aligned} \)

q=4
n=3
f=fqn(q,n)
M=fqn(q,2)
C=[list(m+str(sum([int(i) for i in m])%q) for m in sorted(list(M)))]
for k in range(q-1):
  C.append(list(''.join([str((int(i)+1)%q) for i in m]) for m in C[k]))

print(f"{q**n:,}")
print(f)
print(M)

pd.DataFrame(data=C)
64
{'302', '200', '231', '211', '230', '311', '130', '301', '021', '303', '003', '112', '323', '313', '020', '221', '212', '312', '010', '000', '013', '321', '331', '131', '322', '002', '031', '122', '113', '310', '133', '213', '222', '320', '220', '032', '232', '001', '102', '332', '223', '233', '330', '103', '022', '012', '121', '202', '110', '300', '210', '023', '203', '201', '030', '033', '120', '123', '132', '333', '011', '111', '100', '101'}
{'31', '02', '30', '00', '03', '32', '11', '33', '13', '22', '23', '01', '12', '21', '20', '10'}
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
0 000 011 022 033 101 112 123 130 202 213 220 231 303 310 321 332
1 111 122 133 100 212 223 230 201 313 320 331 302 010 021 032 003
2 222 233 200 211 323 330 301 312 020 031 002 013 121 132 103 110
3 333 300 311 322 030 001 012 023 131 102 113 120 232 203 210 221

q=5,(3,32,2)-code#

\( \begin{aligned} (3,32,2)&\text{-code}\subseteq(F_5)^3 \end{aligned} \)

q=5
n=3
f=fqn(q,n)
M=fqn(q,2)
C=[list(m+str(sum([int(i) for i in m])%q) for m in sorted(list(M)))]
for k in range(q-1):
  C.append(list(''.join([str((int(i)+1)%q) for i in m]) for m in C[k]))

print(f"{q**n:,}")
print(f)
print(M)

pd.DataFrame(data=C)
125
{'200', '230', '240', '443', '440', '021', '420', '144', '212', '243', '024', '310', '233', '404', '012', '121', '210', '023', '203', '401', '412', '100', '231', '314', '301', '303', '432', '313', '430', '221', '214', '331', '131', '340', '043', '133', '032', '232', '040', '241', '204', '324', '140', '141', '441', '132', '333', '111', '341', '134', '042', '400', '403', '112', '323', '434', '020', '421', '041', '000', '034', '321', '014', '304', '322', '414', '433', '113', '234', '320', '220', '410', '004', '332', '124', '330', '103', '343', '201', '244', '030', '011', '242', '422', '101', '302', '142', '044', '211', '311', '104', '130', '143', '003', '344', '423', '114', '010', '342', '013', '002', '031', '122', '334', '442', '213', '222', '431', '411', '001', '102', '223', '224', '022', '424', '202', '110', '300', '402', '033', '444', '120', '123', '413', '312'}
{'04', '42', '22', '21', '20', '12', '02', '00', '03', '13', '33', '10', '34', '14', '31', '24', '32', '11', '23', '30', '44', '01', '41', '43', '40'}
0 1 2 3 4 5 6 7 8 9 ... 15 16 17 18 19 20 21 22 23 24
0 000 011 022 033 044 101 112 123 134 140 ... 303 314 320 331 342 404 410 421 432 443
1 111 122 133 144 100 212 223 234 240 201 ... 414 420 431 442 403 010 021 032 043 004
2 222 233 244 200 211 323 334 340 301 312 ... 020 031 042 003 014 121 132 143 104 110
3 333 344 300 311 322 434 440 401 412 423 ... 131 142 103 114 120 232 243 204 210 221
4 444 400 411 422 433 040 001 012 023 034 ... 242 203 214 220 231 343 304 310 321 332

5 rows × 25 columns