commit 93c2329d066ea337242bb847b512d6da080688be Author: Ethan L. Miller Date: Thu Nov 12 13:00:11 2015 -0800 Initial version of Galois field arithmetic in python. This uses log tables. diff --git a/galois.py b/galois.py new file mode 100755 index 0000000..9132ef4 --- /dev/null +++ b/galois.py @@ -0,0 +1,156 @@ +#!/usr/bin/python +# +# Native python routines for Galois field calculations. +# +# These calculations can be done much faster in C/C++ (and with vectorization), but +# this code helps illustrate how Galois field math works. Also, it's helpful if +# you want to do small amounts of calculation without working with native C/C++ +# code. +# + +import random, copy + +class GaloisException(Exception): + def __init__ (self, value): + self.value = value + def __str__ (self): + return repr(self.value) + +class GaloisNumber: + ''' + Class to represent a number in a Galois (finite) field. + + The class supports "normal" syntax for the addition, subtraction, multiplication, + division, additive inverse (-), and multiplicative inverse (~) operations. + ''' + def __init__ (self, field, value=0): + self.field = field + self.assign (value) + + def assign (self, v): + ''' + Assign a new integer value to this Galois field number. The number must be valid in + the field with which the GaloisNumber instance was defined. + ''' + if v > self.field.size: + raise GaloisException ("Value {0} is outside field".format (value)) + self.value = v + + def __add__ (self, other): + if self.field != other.field: + raise GaloisException ("Field elements from different fields") + return GaloisNumber (self.field, self.value ^ other.value) + + def __invert__ (self): + return self.field.invert (self) + + def __neg__ (self): + # We want a shallow copy so that the field reference remains the same + return copy.copy (self) + + def __mul__ (self, other): + if self.field != other.field: + raise GaloisException ("Field elements from different fields") + return self.field.multiply (self, other) + + def __div__ (self, other): + if self.field != other.field: + raise GaloisException ("Field elements from different fields") + return self.field.divide (self, other) + + def __sub__ (self, other): + return self + other + + def __eq__ (self, other): + if self.field != other.field: + raise GaloisException ("Field elements from different fields") + return self.value == other.value + + def __repr__ (self): + return self.field.fmt (self.value) + +class GaloisFieldLog: + ''' + Pure python implementation of Galois (finite) field arithmetic routines. + + There only needs to be one instantiation of the field for a given set of parameters, + but elements from different field instances with the same parameters may be mixed. + ''' + field_widths = (4, 8, 12, 16) + poly_defaults = {4: 0x13, 8: 0x11d, 12:0x1053, 16: 0x1100b} + multiply_test_size = 10000 + def __init__ (self, bits, primitive_polynomial = None, repr_prefix = 'G', alpha = 1): + ''' + Create a Galois field using log/antilog tables for arithmetic. + ''' + if bits not in self.field_widths: + raise GaloisException ("Field widths supported: {0}".format (self.field_widths)) + self.bits = bits + self.size = (1 << bits) + self.prim = self.poly_defaults[bits] if not primitive_polynomial else primitive_polynomial + self.value_format = repr_prefix + '{:0>' + str(bits / 4) + 'x}' + self.alpha = alpha + # Set up the log and anti-log tables + self.log_tbl = [0] * self.size + self.antilog_tbl = [0] * (self.size - 1) + b = 1 + for i in range (self.size - 1): + self.log_tbl[b] = i + self.antilog_tbl[i] = b + b <<= 1 + if b >= self.size: + b ^= self.prim + + def __eq__ (self, other): + return self.bits == other.bits and self.prim == other.prim and self.alpha == other.alpha + + def fmt (self, v): + return self.value_format.format (v) + + def multiply (self, v1, v2): + a = v1.value + b = v2.value + if a == 0 or b == 0: + return GaloisNumber (self, 0) + return GaloisNumber (self, self.antilog_tbl[(self.log_tbl[a] + self.log_tbl[b]) % + (self.size - 1)]) + + def invert (self, v): + if v.value == 0: + return GaloisNumber(self, 0) + elif v.value == 1: + return GaloisNumber (self, 1) + else: + return GaloisNumber (self, self.antilog_tbl[self.size - 1 - self.log_tbl[v.value]]) + + def divide (self, v1, v2): + return self.multiply (v1, self.invert(v2)) + + def self_test (self): + mul_identity = GaloisNumber (self, 1) + v = GaloisNumber (self) + g_0 = GaloisNumber (self, 0) + g_1 = GaloisNumber (self, 1) + for i in range (self.size): + v.assign (i) + if i == 0: continue + assert v * ~v == mul_identity, "Multiplicative inverse failed at {}".format (i) + assert g_0 - v == -v, "Additive inverse failed at {}".format (i) + assert v * g_1 == v, "Multiplicative identity failed at {}".format (i) + vb = GaloisNumber (self) + for a in range (1, self.multiply_test_size): + v.assign (random.randint (1, self.size - 1)) + vb.assign (random.randint (1, self.size - 1)) + product = v * vb + assert product / v == vb, "Multiplication failed for {} * {}".format(v.value,vb.value) + assert product / vb == v, "Multiplication failed for {} * {}".format(v.value,vb.value) + return True + +if __name__ == '__main__': + for width in GaloisFieldLog.field_widths: + field = GaloisFieldLog (width) + if field.self_test (): + print "{0} bit field passed!".format (width) + g0 = GaloisNumber (field, 5) + g1 = GaloisNumber (field, 7) + print g0 + g1