Skip to content

Commit

Permalink
Don't update display mode of a Galois field class with defaults
Browse files Browse the repository at this point in the history
  • Loading branch information
mhostetter committed Aug 8, 2021
1 parent d9fc631 commit bb1fdc6
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 46 deletions.
80 changes: 50 additions & 30 deletions galois/_field/_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,10 @@


@set_module("galois")
def GF(order, irreducible_poly=None, primitive_element=None, verify=True, compile="auto", display="int"):
def GF(order, irreducible_poly=None, primitive_element=None, verify=True, compile="auto", display=None):
r"""
Factory function to construct a Galois field array class for :math:`\mathrm{GF}(p^m)`.
The created class will be a subclass of :obj:`galois.FieldArray` and instance of :obj:`galois.FieldClass`.
The :obj:`galois.FieldArray` inheritance provides the :obj:`numpy.ndarray` functionality. The :obj:`galois.FieldClass` metaclass
provides a variety of class attributes and methods relating to the finite field.
Parameters
----------
order : int
Expand All @@ -28,15 +24,15 @@ def GF(order, irreducible_poly=None, primitive_element=None, verify=True, compil
define the Galois field arithmetic.
* :obj:`None` (default): Uses the Conway polynomial :math:`C_{p,m}`, see :func:`galois.conway_poly`.
* :obj:`int`: The integer representation of the polynomial.
* :obj:`tuple`, :obj:`list`, :obj:`numpy.ndarray`: The polynomial coefficients in degree-descending order.
* :obj:`int`: The integer representation of the irreducible polynomial.
* :obj:`tuple`, :obj:`list`, :obj:`numpy.ndarray`: The irreducible polynomial coefficients in degree-descending order.
* :obj:`galois.Poly`: The irreducible polynomial as a polynomial object.
primitive_element : int, tuple, list, numpy.ndarray, galois.Poly, optional
Optionally specify a primitive element of the field :math:`\mathrm{GF}(p^m)`. This value is used when building the log/anti-log lookup tables and
computing :func:`np.log`. A primitive element is a generator of the multiplicative group of the field. For prime fields :math:`\mathrm{GF}(p)`, the
primitive element must be an integer and is a primitive root modulo :math:`p`. For extension fields :math:`\mathrm{GF}(p^m)`, the primitive element is a polynomial
of degree less than :math:`m` over :math:`\mathrm{GF}(p)`.
Optionally specify a primitive element of the field :math:`\mathrm{GF}(p^m)`. This value is used when building the log/anti-log
lookup tables and when computing :func:`np.log`. A primitive element is a generator of the multiplicative group of the field.
For prime fields :math:`\mathrm{GF}(p)`, the primitive element must be an integer and is a primitive root modulo :math:`p`. For extension
fields :math:`\mathrm{GF}(p^m)`, the primitive element is a polynomial of degree less than :math:`m` over :math:`\mathrm{GF}(p)`.
**For prime fields:**
Expand All @@ -51,28 +47,30 @@ def GF(order, irreducible_poly=None, primitive_element=None, verify=True, compil
* :obj:`galois.Poly`: The primitive element as a polynomial object.
verify : bool, optional
Indicates whether to verify that the specified irreducible polynomial is in fact irreducible and that the specified primitive element
Indicates whether to verify that the specified irreducible polynomial is in fact irreducible and whether the specified primitive element
is in fact a generator of the multiplicative group. The default is `True`. For large fields and irreducible polynomials that are already
known to be irreducible (and may take a long time to verify), this argument can be set to `False`. If the default irreducible polynomial
and primitive element are used, no verification is performed because the defaults are already guaranteed to be irreducible and a multiplicative
known to be irreducible (which may take a long time to verify), this argument can be set to `False`. If the default irreducible polynomial
and primitive element are used, no verification is performed because the defaults are guaranteed to be irreducible and a multiplicative
generator, respectively.
compile : str, optional
The ufunc calculation mode. This can be modified after class consstruction with the :func:`galois.FieldClass.compile` method.
* `"auto"` (default): Selects "jit-lookup" for fields with order less than :math:`2^{20}`, "jit-calculate" for larger fields, and "python-calculate" for
fields whose elements cannot be represented with :obj:`numpy.int64`.
* `"jit-lookup"`: JIT compiles arithmetic ufuncs to use Zech log, log, and anti-log lookup tables for efficient calculation. In the few cases where
explicit calculation is faster than table lookup, explicit calculation is used.
* `"jit-calculate"`: JIT compiles arithmetic ufuncs to use explicit calculation. The "jit-calculate" mode is designed for large fields that cannot
or should not store lookup tables in RAM. Generally, "jit-calculate" mode will be slower than "jit-lookup".
* `"python-calculate"`: Uses pure-python ufuncs with explicit calculation. This is reserved for fields whose elements cannot be represented with
:obj:`numpy.int64` and instead use :obj:`numpy.object_` with python :obj:`int` (which have arbitrary precision).
* `"auto"` (default): Selects "jit-lookup" for fields with order less than :math:`2^{20}`, "jit-calculate" for larger fields,
and "python-calculate" for fields whose elements cannot be represented with :obj:`numpy.int64`.
* `"jit-lookup"`: JIT compiles arithmetic ufuncs to use Zech log, log, and anti-log lookup tables for efficient computation.
In the few cases where explicit calculation is faster than table lookup, explicit calculation is used.
* `"jit-calculate"`: JIT compiles arithmetic ufuncs to use explicit calculation. The "jit-calculate" mode is designed for large
fields that cannot or should not store lookup tables in RAM. Generally, the "jit-calculate" mode is slower than "jit-lookup".
* `"python-calculate"`: Uses pure-python ufuncs with explicit calculation. This is reserved for fields whose elements cannot be
represented with :obj:`numpy.int64` and instead use :obj:`numpy.object_` with python :obj:`int` (which has arbitrary precision).
display : str, optional
The field element display representation. This can be modified after class consstruction with the :func:`galois.FieldClass.display` method.
* `"int"` (default): The element displayed as the integer representation of the polynomial. For example, :math:`2x^2 + x + 2` is an element of
* `None` (default): For newly-created classes, `None` corresponds to the integer representation (`"int"`). For Galois field array classes
of this type that were previously created, `None` does not modify the current display mode.
* `"int"`: The element displayed as the integer representation of the polynomial. For example, :math:`2x^2 + x + 2` is an element of
:math:`\mathrm{GF}(3^3)` and is equivalent to the integer :math:`23 = 2 \cdot 3^2 + 3 + 2`.
* `"poly"`: The element as a polynomial over :math:`\mathrm{GF}(p)` of degree less than :math:`m`. For example, :math:`2x^2 + x + 2` is an element
of :math:`\mathrm{GF}(3^3)`.
Expand All @@ -82,7 +80,18 @@ def GF(order, irreducible_poly=None, primitive_element=None, verify=True, compil
Returns
-------
galois.FieldClass
A new Galois field array class that is a subclass of :obj:`galois.FieldArray` and instance of :obj:`galois.FieldClass`.
A Galois field array class for :math:`\mathrm{GF}(p^m)`. If this class has already been created, a reference to that class is returned.
Notes
-----
The created class is a subclass of :obj:`galois.FieldArray` and an instance of :obj:`galois.FieldClass`.
The :obj:`galois.FieldArray` inheritance provides the :obj:`numpy.ndarray` functionality and some additional methods on
Galois field arrays, such as :func:`galois.FieldArray.row_reduce`. The :obj:`galois.FieldClass` metaclass provides a variety
of class attributes and methods relating to the finite field, such as the :func:`galois.FieldClass.display` method to
change the field element display representation.
Galois field array classes of the same type (order, irreducible polynomial, and primitive element) are singletons. So, calling this
class factory with arguments that correspond to the same class will return the same class object.
Examples
--------
Expand All @@ -109,8 +118,8 @@ def GF(order, irreducible_poly=None, primitive_element=None, verify=True, compil
.. ipython:: python
p = galois.Poly.Degrees([8,4,3,1,0]); p
GF256_AES = galois.GF(2**8, irreducible_poly=p)
poly = galois.Poly.Degrees([8,4,3,1,0]); poly
GF256_AES = galois.GF(2**8, irreducible_poly=poly)
print(GF256_AES.properties)
Very large fields are also supported but they use :obj:`numpy.object_` dtypes with python :obj:`int` and, therefore, do not have compiled ufuncs.
Expand All @@ -125,7 +134,7 @@ def GF(order, irreducible_poly=None, primitive_element=None, verify=True, compil
GFp = galois.GF(36893488147419103183); print(GFp.properties)
GFp.dtypes, GFp.ufunc_mode
The default display mode for fields is the integer representation. This can be modified by using the `display` keyword argument. It
The default display mode for field elements is the integer representation. This can be modified by using the `display` keyword argument. It
can also be changed after class construction by calling the :func:`galois.FieldClass.display` method.
.. ipython:: python
Expand All @@ -134,6 +143,17 @@ def GF(order, irreducible_poly=None, primitive_element=None, verify=True, compil
GF.Random()
GF = galois.GF(2**8, display="poly")
GF.Random()
@suppress
GF.display();
Galois field array classes of the same type (order, irreducible polynomial, and primitive element) are singletons. So, calling this
class factory with arguments that correspond to the same class will return the same field class object.
.. ipython:: python
poly1 = galois.Poly([1, 0, 0, 0, 1, 1, 0, 1, 1])
poly2 = poly1.integer
galois.GF(2**8, irreducible_poly=poly1) is galois.GF(2**8, irreducible_poly=poly2)
See :obj:`galois.FieldArray` and :obj:`galois.FieldClass` for more examples of what Galois field arrays can do.
"""
Expand All @@ -143,7 +163,7 @@ def GF(order, irreducible_poly=None, primitive_element=None, verify=True, compil
raise TypeError(f"Argument `verify` must be a bool, not {type(verify)}.")
if not isinstance(compile, str):
raise TypeError(f"Argument `compile` must be a string, not {type(compile)}.")
if not isinstance(display, str):
if not isinstance(display, (type(None), str)):
raise TypeError(f"Argument `display` must be a string, not {type(display)}.")

p, e = factors(order)
Expand All @@ -152,7 +172,7 @@ def GF(order, irreducible_poly=None, primitive_element=None, verify=True, compil
raise ValueError(f"Argument `order` must be a prime power, not {order} = {s}.")
if not compile in ["auto", "jit-lookup", "jit-calculate", "python-calculate"]:
raise ValueError(f"Argument `compile` must be in ['auto', 'jit-lookup', 'jit-calculate', 'python-calculate'], not {compile!r}.")
if not display in ["int", "poly", "power"]:
if not display in [None, "int", "poly", "power"]:
raise ValueError(f"Argument `display` must be in ['int', 'poly', 'power'], not {display!r}.")

p, m = p[0], e[0]
Expand All @@ -166,7 +186,7 @@ def GF(order, irreducible_poly=None, primitive_element=None, verify=True, compil


@set_module("galois")
def Field(order, irreducible_poly=None, primitive_element=None, verify=True, compile="auto", display="int"):
def Field(order, irreducible_poly=None, primitive_element=None, verify=True, compile="auto", display=None):
"""
Alias of :func:`galois.GF`.
"""
Expand Down
15 changes: 10 additions & 5 deletions galois/_field/_factory_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
# pylint: disable=redefined-builtin


def GF_extension(characteristic, degree, irreducible_poly=None, primitive_element=None, verify=True, compile="auto", display="int"):
def GF_extension(characteristic, degree, irreducible_poly=None, primitive_element=None, verify=True, compile="auto", display=None):
"""
Class factory for extension fields GF(p^m).
"""
Expand All @@ -28,7 +28,9 @@ def GF_extension(characteristic, degree, irreducible_poly=None, primitive_elemen
raise ValueError(f"Argument `characteristic` must be prime, not {characteristic}.")
if not degree > 1:
raise ValueError(f"Argument `degree` must be greater than 1, not {degree}.")

order = characteristic**degree
name = f"GF{characteristic}_{degree}"
prime_subfield = GF_prime(characteristic)
is_primitive_poly = None
verify_poly = verify
Expand Down Expand Up @@ -79,10 +81,13 @@ def GF_extension(characteristic, degree, irreducible_poly=None, primitive_elemen
if key in GF_extension._classes:
cls = GF_extension._classes[key]
cls.compile(compile)
cls.display(display)
if display is not None:
cls.display(display)
return cls

name = f"GF{characteristic}_{degree}" if degree > 1 else f"GF{characteristic}"
# Since this is a new class build, set compile and display to default values
if display is None:
display = "int"

if verify_poly and not is_irreducible(irreducible_poly):
raise ValueError(f"Argument `irreducible_poly` must be irreducible, {irreducible_poly} is not.")
Expand All @@ -94,7 +99,7 @@ def GF_extension(characteristic, degree, irreducible_poly=None, primitive_elemen
"metaclass": GF2mMeta,
"characteristic": characteristic,
"degree": degree,
"order": characteristic**degree,
"order": order,
"irreducible_poly": irreducible_poly,
"is_primitive_poly": is_primitive_poly,
"primitive_element": primitive_element.integer,
Expand All @@ -107,7 +112,7 @@ def GF_extension(characteristic, degree, irreducible_poly=None, primitive_elemen
"metaclass": GFpmMeta,
"characteristic": characteristic,
"degree": degree,
"order": characteristic**degree,
"order": order,
"irreducible_poly": irreducible_poly,
"is_primitive_poly": is_primitive_poly,
"primitive_element": primitive_element.integer,
Expand Down
32 changes: 21 additions & 11 deletions galois/_field/_factory_prime.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import types

from .._modular import is_primitive_root
from .._modular import primitive_root, is_primitive_root
from .._prime import is_prime

from ._array import FieldArray
Expand All @@ -10,7 +10,7 @@
# pylint: disable=redefined-builtin


def GF_prime(characteristic, primitive_element=None, verify=True, compile="auto", display="int"):
def GF_prime(characteristic, primitive_element=None, verify=True, compile="auto", display=None):
"""
Class factory for prime fields GF(p).
"""
Expand All @@ -20,24 +20,34 @@ def GF_prime(characteristic, primitive_element=None, verify=True, compile="auto"
raise TypeError(f"Argument `primitive_element` must be an int, not {type(primitive_element)}.")
if not is_prime(characteristic):
raise ValueError(f"Argument `characteristic` must be prime, not {characteristic}.")

degree = 1
order = characteristic**degree
name = f"GF{characteristic}"

# Get default primitive element
if primitive_element is None:
primitive_element = primitive_root(characteristic)

# Check primitive element range
if not 0 < primitive_element < order:
raise ValueError(f"Argument `primitive_element` must be non-zero in the field 0 < x < {order}, not {primitive_element}.")

# If the requested field has already been constructed, return it
key = (order, primitive_element)
if key in GF_prime._classes:
cls = GF_prime._classes[key]
cls.compile(compile)
cls.display(display)
if display is not None:
cls.display(display)
return cls

name = f"GF{characteristic}_{degree}" if degree > 1 else f"GF{characteristic}"
# Since this is a new class, set compile and display to default values
if display is None:
display = "int"

if primitive_element is not None:
if not 0 < primitive_element < order:
raise ValueError(f"Argument `primitive_element` must be non-zero in the field 0 < x < {order}, not {primitive_element}.")
if verify and not is_primitive_root(primitive_element, characteristic):
raise ValueError(f"Argument `primitive_element` must be a primitive root modulo {characteristic}, {primitive_element} is not.")
if verify and not is_primitive_root(primitive_element, characteristic):
raise ValueError(f"Argument `primitive_element` must be a primitive root modulo {characteristic}, {primitive_element} is not.")

if characteristic == 2:
cls = GF2
Expand All @@ -46,8 +56,8 @@ def GF_prime(characteristic, primitive_element=None, verify=True, compile="auto"
cls = types.new_class(name, bases=(FieldArray,), kwds={
"metaclass": GFpMeta,
"characteristic": characteristic,
"degree": 1,
"order": characteristic**1,
"degree": degree,
"order": order,
"primitive_element": primitive_element,
"is_primitive_poly": True,
"compile": compile
Expand Down

0 comments on commit bb1fdc6

Please sign in to comment.