diff --git a/galois.py b/galois.py index 9132ef4..ff9460e 100755 --- a/galois.py +++ b/galois.py @@ -8,7 +8,7 @@ # code. # -import random, copy +import random class GaloisException(Exception): def __init__ (self, value): @@ -23,9 +23,13 @@ class GaloisNumber: 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 __init__ (self, x, value=0): + if isinstance(x, GaloisNumber): + self.field = x.field + self.value = x.value + else: + self.field = x + self.assign (value) def assign (self, v): ''' @@ -41,26 +45,38 @@ class GaloisNumber: raise GaloisException ("Field elements from different fields") return GaloisNumber (self.field, self.value ^ other.value) + def __iadd__ (self, other): + if self.field != other.field: + raise GaloisException ("Field elements from different fields") + self.value ^= other.value + + def __sub__ (self, other): + return self + other + + def __isub__ (self, other): + self += other + 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) + return GaloisNumber (self) def __mul__ (self, other): if self.field != other.field: raise GaloisException ("Field elements from different fields") return self.field.multiply (self, other) + def __imul__ (self, other): + if self.field != other.field: + raise GaloisException ("Field elements from different fields") + self.value = self.field.direct_multiply (self.value, other.value) + 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") @@ -71,7 +87,8 @@ class GaloisNumber: class GaloisFieldLog: ''' - Pure python implementation of Galois (finite) field arithmetic routines. + Pure python implementation of Galois (finite) field arithmetic routines using log/antilog + tables. 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. @@ -146,11 +163,111 @@ class GaloisFieldLog: assert product / vb == v, "Multiplication failed for {} * {}".format(v.value,vb.value) return True +class GaloisFieldDirect: + ''' + Pure python implementation of Galois (finite) field arithmetic routines using direct + arithmetic (no log tables). + + 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 direct arithmetic. No log tables or inverses to + precalculate, since the field might be too large to store them + ''' + 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 + + 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): + return GaloisNumber (self, self.direct_multiply (v1.value, v2.value)) + + def direct_multiply (self, a, b): + # Multiplication is commutative, and it's faster if we use the smaller value as the + # multiplier since we can exit the while loop sooner. + if b > a: + a, b = b, a + if a == 0: + result = 0 + else: + result = a if b & 1 else 0 + tmp = a + b >>= 1 + while b != 0: + a <<= 1 + if a >= self.size: + a ^= self.prim + if b & 1: + result ^= a + b >>= 1 + return result + + def invert (self, v): + ''' + Calculate inverse(v) by computing v^(field_size-2). + This is just v^2 * v^4 ... v^(field_size / 2), so calculation time is proportional + to field width in bits. + ''' + if v.value == 0: + return GaloisNumber(self, 0) + elif v.value == 1: + return GaloisNumber (self, 1) + inv = 1 + sq = v.value + for i in range (1, self.bits): + sq = self.direct_multiply (sq, sq) + inv = self.direct_multiply (inv, sq) + return GaloisNumber (self, inv) + + 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) + for width in GaloisFieldDirect.field_widths: + field = GaloisFieldDirect (width) + g0 = GaloisNumber (field, 5) + g1 = GaloisNumber (field, 7) + print g0 + g1 + if field.self_test (): + print "{0} bit field (direct) passed!".format (width) + for width in GaloisFieldLog.field_widths: + field = GaloisFieldLog (width) + if field.self_test (): + print "{0} bit field (log) passed!".format (width) g0 = GaloisNumber (field, 5) g1 = GaloisNumber (field, 7) print g0 + g1