I'm creating a new data type, with the intention of overloading the arithmetic operators. It's for Galois Field arithmetic. The obvious thing to do is create a class, the instances of which will be the variables. There's precious little doc, the maths is a bit agricultural, incomplete and fragile at the moment, it might even be wrong in places, the lists will become ndarrays in time, but it's not those I want comments for, it's the use of the class setup method. I am also aware that there are Galois Field libraries out there, but I'm re-inventing the wheel because I want to know how one works.
Setting up the log tables in the class is most easily done at run time. As I want to set up different tables for different fields, my first thought was to create a metaclass, with which I could make a class for the target field size. However, I'm only about 2 notches above a noob, and reading the metaclass documentation left me cross-eyed, the metaclass PEPs likewise.
My next thought was to have the try/except in ____init___ run the setup function if the tables hadn't been generated, but that requires the field size be passed when creating a instance. I could do some things with defaults, and first-time-only tests, but they felt kludgy.
Then I reasoned that as a class is a first class object, functions defined on it can be called directly. I coded this up as a test, and it worked as expected. I think it's pretty clear, both the class code and the way to use it are obvious and unsurprising (if I can figure it out from first principles then it is obvious). However, I've not seen this form of structure referred to, let alone recommended anywhere.
Is it a known way to do things, or even safe? Is it a hack? I did see the @staticmethod decorator referred to in places, but this doesn't use it and still works, I am not sure it would add anything here. Should I be doing this in a very different way? I am sure there are more complicated ways to do it, but I'd prefer to stay this simple if there is no good reason to complicate.
class GF(object):
""" implements Galois Field arithmetic for 2^4, 2^8 and 2^16
overloads +, -, *, / and str() """
def setup(g_power):
if g_power not in (4, 8, 16):
raise ValueError('cant do field size of {} yet'.format(g_power))
GF.field_power = g_power
GF.field_len = 1 << g_power
GF.mask = GF.field_len-1
GF.prim_poly = (0,0,0,0,9,0,0,0,29,0,0,0,0,0,0,0,32790)[g_power]
GF.alogs = []
GF.logs = [None]
sr = 1
for index in range(GF.field_len-1):
GF.alogs.append(sr)
sr <<= 1
if sr&GF.field_len:
sr ^= GF.prim_poly
sr &= (GF.mask)
for sr in range(1, GF.field_len):
GF.logs.append(GF.alogs.index(sr))
def __init__(self, n):
try:
self.value = n & GF.mask
except AttributeError:
raise RuntimeError('call GF.setup before making any instances of GF')
def __str__(self):
return 'GF({}) element {:0>3d}d 0x{:0>2x}'.format(self.field_power, self.value, self.value)
def __add__(self, other):
if isinstance(other, GF):
return(GF(self.value ^ other.value))
else:
raise TypeError('both args must be of GF type')
def __sub__(self, other):
return self+other
def __mul__(self, other):
if isinstance(other, GF):
if self.value==0:
return GF(0)
if other.value==0:
return GF(0)
log_s = GF.logs[self.value]
log_o = GF.logs[other.value]
log_p = (log_s + log_o) % self.mask
return GF(GF.alogs[log_p])
else:
raise TypeError('both args must be of GF type')
def __truediv__(self, other):
if isinstance(other, GF):
if other.value==0:
raise ValueError('cannot divide by 0')
if self.value==0:
return GF(0)
log_s = GF.logs[self.value]
log_o = GF.logs[other.value]
log_p = (log_s - log_o) % self.mask # always returns positive
return GF(GF.alogs[log_p])
else:
raise TypeError('both args must be of GF type')
if __name__ == '__main__':
GF.setup(8)
print(GF(12)+GF(7))
# test cases from the Intel paper
print(GF(2)*GF(8))
print(GF(18)*GF(5))
print(GF(13)/GF(17))
print(GF(2)/GF(11))