def init_lut(generator=3, prim=0x11b, c_exp=8):
    '''Precompute the logarithm and anti-log (look-up) tables for faster computation later, using the provided primitive polynomial.
    These tables are used for multiplication/division since addition/substraction are simple XOR operations inside GF of characteristic 2.
    The basic idea is quite simple: since b**(log_b(x), log_b(y)) == x * y given any number b (the base or generator of the logarithm), then we can use any number b to precompute logarithm and anti-log (exponentiation) tables to use for multiplying two numbers x and y.
    That's why when we use a different base/generator number, the log and anti-log tables are drastically different, but the resulting computations are the same given any such tables.
    For more infos, see https://en.wikipedia.org/wiki/Finite_field_arithmetic#Implementation_tricks
    '''
    # generator is the generator number (the "increment" that will be used to walk through the field by multiplication, this must be a prime number). This is basically the base of the logarithm/anti-log tables. Also often noted "alpha" in academic books.
    # prim is the primitive/prime (binary) polynomial and must be irreducible (it can't represented as the product of two smaller polynomials). It's a polynomial in the binary sense: each bit is a coefficient, but in fact it's an integer between 0 and 255, and not a list of gf values. For more infos: http://research.swtch.com/field
    # note that the choice of generator or prime polynomial doesn't matter very much: any two finite fields of size p^n have identical structure, even if they give the individual elements different names (ie, the coefficients of the codeword will be different, but the final result will be the same: you can always correct as many errors/erasures with any choice for those parameters). That's why it makes sense to refer to all the finite fields, and all decoders based on Reed-Solomon, of size p^n as one concept: GF(p^n). It can however impact sensibly the speed (because some parameters will generate sparser tables).

    global GF2int_exptable, GF2int_logtable, GF2_charac, GF2_c_exp
    GF2_charac = int(2**c_exp - 1)
    GF2_c_exp = int(c_exp)
    exptable = [-1] * (GF2_charac+1) # anti-log (exponential) table. The first two elements will always be [GF2int(1), generator]
    logtable = [-1] * (GF2_charac+1) # log table, log[0] is impossible and thus unused

    # Construct the anti-log table
    # It's basically the cumulative product of 1 by the generator number, on and on and on until you have walked through the whole field.
    # That's why exptable is always dense (all entries are filled), but logtable may be sparse (lots of empty values, because multiple logtable's entries point to the same exptable's entry).
    g = GF2int(1)
    for i in range(GF2_charac+1): # note that the last item of exptable will always be equal to the first item in the table, because g^p==g^0 because of the modulo p (we're in a finite field!).
        exptable[i] = g # compute anti-log for this value and store it in a table
        #logtable[g] = i # compute logtable at the same time as exptable (but log[1] will always be equal to g^255, which may be weird when compared to lists of logtables online but this is equivalent)
        g = g.multiply(generator, prim, GF2_charac+1) # equivalent to: g = generator**(i+1)

    # Construct the log table
    # Ignore the last element of the field because fields wrap back around.
    # The log of 1 can have two values: either g^0 (the exact value change depending on parameters) or it could be 255 (g^255=1) because of the wraparound
    # Note that either way, this does not change anything any output later (ie, the ecc symbols will be the same either way).
    for i, x in enumerate(exptable[:-1]):
        logtable[x] = i

    # Optimization: convert to integer arrays
    GF2int_exptable = array.array('i', exptable)
    GF2int_logtable = array.array('i', logtable)
    return GF2int_exptable, GF2int_logtable