Source code for galois.meta_gf
import numpy as np
from .meta_mixin_target import TargetMixin
from .modular import totatives
[docs]class GFMeta(TargetMixin):
"""
Defines a metaclass for all :obj:`galois.GFArray` classes.
This metaclass gives :obj:`galois.GFArray` classes returned from :func:`galois.GF` class methods and properties
relating to its Galois field.
"""
# pylint: disable=no-value-for-parameter,comparison-with-callable,too-many-public-methods
def __new__(cls, name, bases, namespace, **kwargs): # pylint: disable=unused-argument
return super().__new__(cls, name, bases, namespace)
def __init__(cls, name, bases, namespace, **kwargs):
super().__init__(name, bases, namespace, **kwargs)
cls._characteristic = None
cls._degree = None
cls._order = None
cls._irreducible_poly = None
cls._is_primitive_poly = None
cls._primitive_element = None
cls._ground_field = None
cls._ufunc_mode = None
cls._ufunc_target = None
cls._display_mode = "int"
cls._display_poly_var = "α"
def __str__(cls):
return f"<class 'numpy.ndarray over {cls.name}'>"
def __repr__(cls):
return f"<class 'numpy.ndarray over {cls.name}'>"
###############################################################################
# Class methods
###############################################################################
[docs] def compile(cls, mode, target="cpu"):
"""
Recompile the just-in-time compiled numba ufuncs with a new calculation mode or target.
Parameters
----------
mode : str
The method of field computation, either `"jit-lookup"`, `"jit-calculate"`, `"python-calculate"`. The "jit-lookup" mode will
use Zech log, log, and anti-log lookup tables for speed. The "jit-calculate" mode will not store any lookup tables, but perform field
arithmetic on the fly. The "jit-calculate" mode is designed for large fields that cannot store lookup tables in RAM.
Generally, "jit-calculate" is slower than "jit-lookup". The "python-calculate" mode is reserved for extremely large fields. In
this mode the ufuncs are not JIT-compiled, but are pur python functions operating on python ints. The list of valid
modes for this field is in :obj:`galois.GFMeta.ufunc_modes`.
target : str, optional
The `target` keyword argument from :obj:`numba.vectorize`, either `"cpu"`, `"parallel"`, or `"cuda"`. The default
is `"cpu"`. For extremely large fields the only supported target is `"cpu"` (which doesn't use numba it uses pure python to
calculate the field arithmetic). The list of valid targets for this field is in :obj:`galois.GFMeta.ufunc_targets`.
"""
mode = cls.default_ufunc_mode if mode == "auto" else mode
if mode not in cls.ufunc_modes:
raise ValueError(f"Argument `mode` must be in {cls.ufunc_modes} for {cls.name}, not {mode}.")
if target not in cls.ufunc_targets:
raise ValueError(f"Argument `target` must be in {cls.ufunc_targets} for {cls.name}, not {target}.")
if mode == cls.ufunc_mode and target == cls.ufunc_target:
# Don't need to rebuild these ufuncs
return
cls._ufunc_mode = mode
cls._ufunc_target = target
if cls.ufunc_mode == "jit-lookup":
cls._compile_jit_lookup(target)
elif cls.ufunc_mode == "jit-calculate":
cls._compile_jit_calculate(target)
elif cls.ufunc_mode == "python-calculate":
cls._compile_python_calculate()
else:
raise RuntimeError(f"Attribute `ufunc_mode` was not processed, {cls._ufunc_mode}. Please submit a GitHub issue at https://github.com/mhostetter/galois/issues.")
[docs] def display(cls, mode="int", poly_var="α"):
"""
Sets the display mode for all Galois field arrays of this type.
The display mode can be set to either the integer representation or polynomial representation.
This function updates :obj:`display_mode` and :obj:`display_poly_var`.
Parameters
----------
mode : str, optional
The field element display mode, either `"int"` (default) or `"poly"`.
poly_var : str, optional
The polynomial representation's variable. The default is `"α"`.
Examples
--------
Change the display mode by calling the :func:`display` method.
.. ipython:: python
GF = galois.GF(2**8)
a = GF.Random(); a
GF.display("poly"); a
# Reset to the default display mode
GF.display(); a
The :func:`display` method can also be used as a context manager.
.. ipython:: python
# The original display mode
a
# The new display context
with GF.display("poly"):
print(a)
# Returns to the default display mode
a
"""
if mode not in ["int", "poly"]:
raise ValueError(f"Argument `mode` must be in ['int', 'poly'], not {mode}.")
if not isinstance(poly_var, str):
raise TypeError(f"Argument `poly_var` must be a string, not {type(poly_var)}.")
context = DisplayContext(cls)
# Set the new state
cls._display_mode = mode
cls._display_poly_var = poly_var
return context
###############################################################################
# Class attributes
###############################################################################
@property
def name(cls):
"""
str: The Galois field name.
Examples
--------
.. ipython:: python
galois.GF(2).name
galois.GF(2**8).name
galois.GF(31).name
# galois.GF(7**5).name
"""
if cls._degree == 1:
return f"GF({cls._characteristic})"
else:
return f"GF({cls._characteristic}^{cls._degree})"
@property
def characteristic(cls):
"""
int: The prime characteristic :math:`p` of the Galois field :math:`\\mathrm{GF}(p^m)`. Adding
:math:`p` copies of any element will always result in :math:`0`.
Examples
--------
.. ipython:: python
GF = galois.GF(2**8)
GF.characteristic
a = GF.Random(); a
a * GF.characteristic
.. ipython:: python
GF = galois.GF(31)
GF.characteristic
a = GF.Random(); a
a * GF.characteristic
"""
return cls._characteristic
@property
def degree(cls):
"""
int: The prime characteristic's degree :math:`m` of the Galois field :math:`\\mathrm{GF}(p^m)`. The degree
is a positive integer.
Examples
--------
.. ipython:: python
galois.GF(2).degree
galois.GF(2**8).degree
galois.GF(31).degree
# galois.GF(7**5).degree
"""
return cls._degree
@property
def order(cls):
"""
int: The order :math:`p^m` of the Galois field :math:`\\mathrm{GF}(p^m)`. The order of the field is also equal to
the field's size.
Examples
--------
.. ipython:: python
galois.GF(2).order
galois.GF(2**8).order
galois.GF(31).order
# galois.GF(7**5).order
"""
return cls._order
@property
def irreducible_poly(cls):
"""
galois.Poly: The irreducible polynomial :math:`f(x)` of the Galois field :math:`\\mathrm{GF}(p^m)`. The irreducible
polynomial is of degree :math:`m` over :math:`\\mathrm{GF}(p)`.
Examples
--------
.. ipython:: python
galois.GF(2).irreducible_poly
galois.GF(2**8).irreducible_poly
galois.GF(31).irreducible_poly
# galois.GF(7**5).irreducible_poly
"""
# Ensure accesses of this property don't alter it
return cls._irreducible_poly.copy()
@property
def is_primitive_poly(cls):
"""
bool: Indicates whether the :obj:`irreducible_poly` is a primitive polynomial.
Examples
--------
.. ipython:: python
GF = galois.GF(2**8)
GF.irreducible_poly
GF.primitive_element
# The irreducible polynomial is a primitive polynomial is the primitive element is a root
GF.irreducible_poly(GF.primitive_element, field=GF)
GF.is_primitive_poly
.. ipython:: python
# Field used in AES
GF = galois.GF(2**8, irreducible_poly=galois.Poly.Degrees([8,4,3,1,0]))
GF.irreducible_poly
GF.primitive_element
# The irreducible polynomial is a primitive polynomial is the primitive element is a root
GF.irreducible_poly(GF.primitive_element, field=GF)
GF.is_primitive_poly
"""
return cls._is_primitive_poly
@property
def primitive_element(cls):
"""
int: A primitive element :math:`\\alpha` of the Galois field :math:`\\mathrm{GF}(p^m)`. A primitive element is a multiplicative
generator of the field, such that :math:`\\mathrm{GF}(p^m) = \\{0, 1, \\alpha^1, \\alpha^2, \\dots, \\alpha^{p^m - 2}\\}`.
A primitive element is a root of the primitive polynomial :math:`\\f(x)`, such that :math:`f(\\alpha) = 0` over
:math:`\\mathrm{GF}(p^m)`.
Examples
--------
.. ipython:: python
galois.GF(2).primitive_element
galois.GF(2**8).primitive_element
galois.GF(31).primitive_element
# galois.GF(7**5).primitive_element
"""
# Ensure accesses of this property don't alter it
return np.copy(cls._primitive_element)
@property
def primitive_elements(cls):
"""
int: All primitive elements :math:`\\alpha` of the Galois field :math:`\\mathrm{GF}(p^m)`. A primitive element is a multiplicative
generator of the field, such that :math:`\\mathrm{GF}(p^m) = \\{0, 1, \\alpha^1, \\alpha^2, \\dots, \\alpha^{p^m - 2}\\}`.
Examples
--------
.. ipython:: python
galois.GF(2).primitive_elements
galois.GF(2**8).primitive_elements
galois.GF(31).primitive_elements
# galois.GF(7**5).primitive_elements
"""
powers = np.array(totatives(cls.order - 1))
return np.sort(cls.primitive_element ** powers)
@property
def is_prime_field(cls):
"""
bool: Indicates if the field's order is prime.
Examples
--------
.. ipython:: python
galois.GF(2).is_prime_field
galois.GF(2**8).is_prime_field
galois.GF(31).is_prime_field
# galois.GF(7**5).is_prime_field
"""
return cls._degree == 1
@property
def is_extension_field(cls):
"""
bool: Indicates if the field's order is a prime power.
Examples
--------
.. ipython:: python
galois.GF(2).is_extension_field
galois.GF(2**8).is_extension_field
galois.GF(31).is_extension_field
# galois.GF(7**5).is_extension_field
"""
return cls._degree > 1
@property
def ground_field(cls):
"""
galois.GFMeta: The ground field :math:`\\mathrm{GF}(p)` of the extension field :math:`\\mathrm{GF}(p^m)`.
Examples
--------
.. ipython:: python
print(galois.GF(2).ground_field.properties)
print(galois.GF(2**8).ground_field.properties)
print(galois.GF(31).ground_field.properties)
# print(galois.GF(7**5).ground_field.properties)
"""
return cls._ground_field
@property
def dtypes(cls):
"""
list: List of valid integer :obj:`numpy.dtype` objects that are compatible with this Galois field. Valid data
types are signed and unsinged integers that can represent decimal values in :math:`[0, p^m)`.
Examples
--------
.. ipython:: python
galois.GF(2).dtypes
galois.GF(2**8).dtypes
galois.GF(31).dtypes
# galois.GF(7**5).dtypes
For field's with orders that cannot be represented by :obj:`numpy.int64`, the only valid dtype is :obj:`numpy.object_`.
.. ipython:: python
galois.GF(2**100).dtypes
galois.GF(36893488147419103183).dtypes
"""
raise NotImplementedError
@property
def ufunc_mode(cls):
"""
str: The mode for ufunc compilation, either `"jit-lookup"`, `"jit-calculate"`, `"python-calculate"`.
Examples
--------
.. ipython:: python
galois.GF(2).ufunc_mode
galois.GF(2**8).ufunc_mode
galois.GF(31).ufunc_mode
# galois.GF(7**5).ufunc_mode
"""
return cls._ufunc_mode
@property
def ufunc_modes(cls):
"""
list: All supported ufunc modes for this Galois field array class.
Examples
--------
.. ipython:: python
galois.GF(2).ufunc_modes
galois.GF(2**8).ufunc_modes
galois.GF(31).ufunc_modes
galois.GF(2**100).ufunc_modes
"""
if cls.dtypes == [np.object_]:
return ["python-calculate"]
else:
return ["jit-lookup", "jit-calculate"]
@property
def default_ufunc_mode(cls):
"""
str: The default ufunc arithmetic mode for this Galois field.
Examples
--------
.. ipython:: python
galois.GF(2).default_ufunc_mode
galois.GF(2**8).default_ufunc_mode
galois.GF(31).default_ufunc_mode
galois.GF(2**100).default_ufunc_mode
"""
if cls.dtypes == [np.object_]:
return "python-calculate"
elif cls.order <= 2**20:
return "jit-lookup"
else:
return "jit-calculate"
@property
def ufunc_target(cls):
"""
str: The numba target for the JIT-compiled ufuncs, either `"cpu"`, `"parallel"`, or `"cuda"`.
Examples
--------
.. ipython:: python
galois.GF(2).ufunc_target
galois.GF(2**8).ufunc_target
galois.GF(31).ufunc_target
# galois.GF(7**5).ufunc_target
"""
return cls._ufunc_target
@property
def ufunc_targets(cls):
"""
list: All supported ufunc targets for this Galois field array class.
Examples
--------
.. ipython:: python
galois.GF(2).ufunc_targets
galois.GF(2**8).ufunc_targets
galois.GF(31).ufunc_targets
galois.GF(2**100).ufunc_targets
"""
if cls.dtypes == [np.object_]:
return ["cpu"]
else:
return ["cpu", "parallel", "cuda"]
@property
def display_mode(cls):
"""
str: The representation of Galois field elements, either `"int"` or `"poly"`. This can be
changed with :func:`display`.
Examples
--------
.. ipython:: python
GF = galois.GF(2**8)
GF.display_mode
a = GF.Random(); a
with GF.display("poly"):
print(GF.display_mode)
print(a)
"""
return cls._display_mode
@property
def display_poly_var(cls):
"""
str: The polynomial indeterminate for the polynomial representation of the field elements. The default
is `"α"`. This can be changed with :func:`display`.
Examples
--------
.. ipython:: python
GF = galois.GF(2**8)
GF.display_mode, GF.display_poly_var
a = GF.Random(); a
with GF.display("poly"):
print(GF.display_mode, GF.display_poly_var)
print(a)
"""
return cls._display_poly_var
@property
def properties(cls):
"""
str: A formmatted string displaying relevant properties of the Galois field.
Examples
--------
.. ipython:: python
print(galois.GF(2).properties)
print(galois.GF(2**8).properties)
print(galois.GF(31).properties)
# print(galois.GF(7**5).properties)
"""
string = f"{cls.name}:"
string += f"\n characteristic: {cls.characteristic}"
string += f"\n degree: {cls.degree}"
string += f"\n order: {cls.order}"
string += f"\n irreducible_poly: {cls.irreducible_poly}"
string += f"\n is_primitive_poly: {cls.is_primitive_poly}"
string += f"\n primitive_element: {cls.primitive_element!r}"
return string
class DisplayContext:
"""
Simple context manager for the :obj:`GFArrayMeta.display` method.
"""
def __init__(self, cls):
# Save the previous state
self.cls = cls
self.mode = cls.display_mode
self.poly_var = cls.display_poly_var
def __enter__(self):
# Don't need to do anything, we already set the new mode and poly_var in the display() method
pass
def __exit__(self, exc_type, exc_value, traceback):
# Reset mode and poly_var upon exiting the context
self.cls._display_mode = self.mode
self.cls._display_poly_var = self.poly_var