Skip to content

Commit

Permalink
Ensure pure-Python ufuncs are performing integer arithmetic
Browse files Browse the repository at this point in the history
Fixes #253
  • Loading branch information
mhostetter committed Feb 10, 2022
1 parent 8993630 commit 3aa7d1b
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 20 deletions.
36 changes: 33 additions & 3 deletions galois/_fields/_gf2m.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,27 +58,42 @@ def _reset_globals(cls):

###############################################################################
# Arithmetic functions using explicit calculation
#
# NOTE: The ufunc inputs a and b are cast to integers at the beginning of each
# ufunc to prevent the non-JIT-compiled invocations (used in "large"
# fields with dtype=object) from performing infintely recursive
# arithmetic. Instead, the intended arithmetic inside the ufuncs is
# integer arithmetic.
# See https://github.com/mhostetter/galois/issues/253.
###############################################################################

@staticmethod
def _add_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
"""
Not actually used. `np.bitwise_xor()` is faster.
"""
a = int(a)
b = int(b)

return a ^ b

@staticmethod
def _negative_calculate(a, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
"""
Not actually used. `np.positive()` is faster.
"""
a = int(a)

return a

@staticmethod
def _subtract_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
"""
Not actually used. `np.bitwise_xor()` is faster.
"""
a = int(a)
b = int(b)

return a ^ b

@staticmethod
Expand All @@ -95,6 +110,8 @@ def _multiply_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
= c
"""
ORDER = CHARACTERISTIC**DEGREE
a = int(a)
b = int(b)

# Re-order operands such that a > b so the while loop has less loops
if b > a:
Expand Down Expand Up @@ -128,6 +145,8 @@ def _reciprocal_calculate(a, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
raise ZeroDivisionError("Cannot compute the multiplicative inverse of 0 in a Galois field.")

ORDER = CHARACTERISTIC**DEGREE
a = int(a)

exponent = ORDER - 2
result_s = a # The "squaring" part
result_m = 1 # The "multiplicative" part
Expand All @@ -150,11 +169,16 @@ def _divide_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
if b == 0:
raise ZeroDivisionError("Cannot compute the multiplicative inverse of 0 in a Galois field.")

a = int(a)
b = int(b)

if a == 0:
return 0
c = 0
else:
b_inv = RECIPROCAL(b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY)
return MULTIPLY(a, b_inv, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY)
c = MULTIPLY(a, b_inv, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY)

return c

@staticmethod
@numba.extending.register_jitable
Expand All @@ -173,6 +197,9 @@ def _power_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
if a == 0 and b < 0:
raise ZeroDivisionError("Cannot compute the multiplicative inverse of 0 in a Galois field.")

a = int(a)
b = int(b)

if b == 0:
return 1
elif b < 0:
Expand Down Expand Up @@ -208,8 +235,11 @@ def _log_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
if a == 0:
raise ArithmeticError("Cannot compute the discrete logarithm of 0 in a Galois field.")

# Naive algorithm
ORDER = CHARACTERISTIC**DEGREE
a = int(a)
b = int(b)

# Naive algorithm
result = 1
for i in range(0, ORDER - 1):
if result == a:
Expand Down
54 changes: 46 additions & 8 deletions galois/_fields/_gfp.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,36 +56,61 @@ def _reset_globals(cls):

###############################################################################
# Arithmetic functions using explicit calculation
#
# NOTE: The ufunc inputs a and b are cast to integers at the beginning of each
# ufunc to prevent the non-JIT-compiled invocations (used in "large"
# fields with dtype=object) from performing infintely recursive
# arithmetic. Instead, the intended arithmetic inside the ufuncs is
# integer arithmetic.
# See https://github.com/mhostetter/galois/issues/253.
###############################################################################

@staticmethod
@numba.extending.register_jitable
def _add_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
a = int(a)
b = int(b)

c = a + b
if c >= CHARACTERISTIC:
c -= CHARACTERISTIC

return c

@staticmethod
@numba.extending.register_jitable
def _negative_calculate(a, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
a = int(a)

if a == 0:
return 0
c = 0
else:
return CHARACTERISTIC - a
c = CHARACTERISTIC - a

return c

@staticmethod
@numba.extending.register_jitable
def _subtract_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
a = int(a)
b = int(b)

if a >= b:
return a - b
c = a - b
else:
return CHARACTERISTIC + a - b
c = CHARACTERISTIC + a - b

return c

@staticmethod
@numba.extending.register_jitable
def _multiply_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
return (a * b) % CHARACTERISTIC
a = int(a)
b = int(b)

c = (a * b) % CHARACTERISTIC

return c

@staticmethod
@numba.extending.register_jitable
Expand All @@ -99,6 +124,8 @@ def _reciprocal_calculate(a, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
if a == 0:
raise ZeroDivisionError("Cannot compute the multiplicative inverse of 0 in a Galois field.")

a = int(a)

r2, r1 = CHARACTERISTIC, a
t2, t1 = 0, 1

Expand All @@ -118,11 +145,16 @@ def _divide_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
if b == 0:
raise ZeroDivisionError("Cannot compute the multiplicative inverse of 0 in a Galois field.")

a = int(a)
b = int(b)

if a == 0:
return 0
c = 0
else:
b_inv = RECIPROCAL(b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY)
return (a * b_inv) % CHARACTERISTIC
c = (a * b_inv) % CHARACTERISTIC

return c

@staticmethod
@numba.extending.register_jitable
Expand All @@ -141,6 +173,9 @@ def _power_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
if a == 0 and b < 0:
raise ZeroDivisionError("Cannot compute the multiplicative inverse of 0 in a Galois field.")

a = int(a)
b = int(b)

if b == 0:
return 1
elif b < 0:
Expand Down Expand Up @@ -176,8 +211,11 @@ def _log_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
if a == 0:
raise ArithmeticError("Cannot compute the discrete logarithm of 0 in a Galois field.")

# Naive algorithm
ORDER = CHARACTERISTIC**DEGREE
a = int(a)
b = int(b)

# Naive algorithm
result = 1
for i in range(0, ORDER - 1):
if result == a:
Expand Down
57 changes: 50 additions & 7 deletions galois/_fields/_gfpm.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ def _ufunc_routine_subtract(cls, ufunc, method, inputs, kwargs, meta):

###############################################################################
# Arithmetic functions using explicit calculation
#
# NOTE: The ufunc inputs a and b are cast to integers at the beginning of each
# ufunc to prevent the non-JIT-compiled invocations (used in "large"
# fields with dtype=object) from performing infintely recursive
# arithmetic. Instead, the intended arithmetic inside the ufuncs is
# integer arithmetic.
# See https://github.com/mhostetter/galois/issues/253.
###############################################################################

@staticmethod
Expand All @@ -162,11 +169,14 @@ def _int_to_poly(a, CHARACTERISTIC, DEGREE):
"""
Convert the integer representation to vector/polynomial representation
"""
a = int(a)

a_vec = np.zeros(DEGREE, dtype=DTYPE)
for i in range(0, DEGREE):
q = a // CHARACTERISTIC**(DEGREE - 1 - i)
a -= q*CHARACTERISTIC**(DEGREE - 1 - i)
a_vec[i] = q

return a_vec

@staticmethod
Expand All @@ -178,34 +188,52 @@ def _poly_to_int(a_vec, CHARACTERISTIC, DEGREE):
a = 0
for i in range(0, DEGREE):
a += a_vec[i]*CHARACTERISTIC**(DEGREE - 1 - i)

return a

@staticmethod
@numba.extending.register_jitable
def _add_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
a = int(a)
b = int(b)

a_vec = INT_TO_POLY(a, CHARACTERISTIC, DEGREE)
b_vec = INT_TO_POLY(b, CHARACTERISTIC, DEGREE)
c_vec = (a_vec + b_vec) % CHARACTERISTIC
return POLY_TO_INT(c_vec, CHARACTERISTIC, DEGREE)
c = POLY_TO_INT(c_vec, CHARACTERISTIC, DEGREE)

return c

@staticmethod
@numba.extending.register_jitable
def _negative_calculate(a, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
a = int(a)

a_vec = INT_TO_POLY(a, CHARACTERISTIC, DEGREE)
a_vec = (-a_vec) % CHARACTERISTIC
return POLY_TO_INT(a_vec, CHARACTERISTIC, DEGREE)
c = POLY_TO_INT(a_vec, CHARACTERISTIC, DEGREE)

return c

@staticmethod
@numba.extending.register_jitable
def _subtract_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
a = int(a)
b = int(b)

a_vec = INT_TO_POLY(a, CHARACTERISTIC, DEGREE)
b_vec = INT_TO_POLY(b, CHARACTERISTIC, DEGREE)
c_vec = (a_vec - b_vec) % CHARACTERISTIC
return POLY_TO_INT(c_vec, CHARACTERISTIC, DEGREE)
c = POLY_TO_INT(c_vec, CHARACTERISTIC, DEGREE)

return c

@staticmethod
@numba.extending.register_jitable
def _multiply_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
a = int(a)
b = int(b)

a_vec = INT_TO_POLY(a, CHARACTERISTIC, DEGREE)
b_vec = INT_TO_POLY(b, CHARACTERISTIC, DEGREE)

Expand All @@ -230,7 +258,9 @@ def _multiply_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
b_vec[1:] = b_vec[:-1]
b_vec[0] = 0

return POLY_TO_INT(c_vec, CHARACTERISTIC, DEGREE)
c = POLY_TO_INT(c_vec, CHARACTERISTIC, DEGREE)

return c

@staticmethod
@numba.extending.register_jitable
Expand All @@ -247,6 +277,8 @@ def _reciprocal_calculate(a, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
raise ZeroDivisionError("Cannot compute the multiplicative inverse of 0 in a Galois field.")

ORDER = CHARACTERISTIC**DEGREE
a = int(a)

exponent = ORDER - 2
result_s = a # The "squaring" part
result_m = 1 # The "multiplicative" part
Expand All @@ -269,11 +301,16 @@ def _divide_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
if b == 0:
raise ZeroDivisionError("Cannot compute the multiplicative inverse of 0 in a Galois field.")

a = int(a)
b = int(b)

if a == 0:
return 0
c = 0
else:
b_inv = RECIPROCAL(b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY)
return MULTIPLY(a, b_inv, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY)
c = MULTIPLY(a, b_inv, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY)

return c

@staticmethod
@numba.extending.register_jitable
Expand All @@ -292,6 +329,9 @@ def _power_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
if a == 0 and b < 0:
raise ZeroDivisionError("Cannot compute the multiplicative inverse of 0 in a Galois field.")

a = int(a)
b = int(b)

if b == 0:
return 1
elif b < 0:
Expand Down Expand Up @@ -327,8 +367,11 @@ def _log_calculate(a, b, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY):
if a == 0:
raise ArithmeticError("Cannot compute the discrete logarithm of 0 in a Galois field.")

# Naive algorithm
ORDER = CHARACTERISTIC**DEGREE
a = int(a)
b = int(b)

# Naive algorithm
result = 1
for i in range(0, ORDER - 1):
if result == a:
Expand Down
Loading

0 comments on commit 3aa7d1b

Please sign in to comment.