Add `Q`, `R`, `C`, and `GF2` fields
- add `lalib.fields.base.Field`, a blueprint for all concrete fields,
providing a unified interface to be used outside of the
`lalib.fields` sub-package
- implement `lalib.fields.complex_.ComplexField`, or `C` for short,
the field over the complex numbers (modeled as `complex` numbers)
- implement `lalib.fields.galois.GaloisField2`, or `GF2` for short,
the (finite) field over the two elements `one` and `zero`
+ adapt `lalib.elements.galois.GF2Element.__eq__()` to return
`NotImplemented` instead of `False` for non-castable `other`s
=> this fixes a minor issue with `pytest.approx()`
- implement `lalib.fields.rational.RationalField`, or `Q` for short,
the field over the rational numbers (modeled as `fractions.Fraction`s)
- implement `lalib.fields.real.RealField`, or `R` for short,
the field over the real numbers (modeled as `float`s)
- organize top-level imports for `lalib.fields`,
making `Q`, `R`, `C`, and `GF2` importable with
`from lalib.fields import *`
- provide extensive unit and integration tests for the new objects:
+ test generic and common behavior in `tests.fields.test_base`
+ test specific behavior is other modules
+ test the well-known math axioms for all fields (integration tests)
+ test the new objects' docstrings
+ add "pytest-repeat" to run randomized tests many times
2024-10-14 15:17:42 +02:00
|
|
|
"""Ensure all `Field`s fulfill the axioms from math.
|
|
|
|
|
|
|
|
Source: https://en.wikipedia.org/wiki/Field_(mathematics)#Classic_definition
|
|
|
|
"""
|
|
|
|
|
|
|
|
import contextlib
|
|
|
|
import operator
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
from tests.fields import utils
|
|
|
|
|
|
|
|
|
2024-10-15 01:49:32 +02:00
|
|
|
# None of the test cases below contributes towards higher coverage
|
|
|
|
pytestmark = pytest.mark.integration_test
|
|
|
|
|
|
|
|
|
Add `Q`, `R`, `C`, and `GF2` fields
- add `lalib.fields.base.Field`, a blueprint for all concrete fields,
providing a unified interface to be used outside of the
`lalib.fields` sub-package
- implement `lalib.fields.complex_.ComplexField`, or `C` for short,
the field over the complex numbers (modeled as `complex` numbers)
- implement `lalib.fields.galois.GaloisField2`, or `GF2` for short,
the (finite) field over the two elements `one` and `zero`
+ adapt `lalib.elements.galois.GF2Element.__eq__()` to return
`NotImplemented` instead of `False` for non-castable `other`s
=> this fixes a minor issue with `pytest.approx()`
- implement `lalib.fields.rational.RationalField`, or `Q` for short,
the field over the rational numbers (modeled as `fractions.Fraction`s)
- implement `lalib.fields.real.RealField`, or `R` for short,
the field over the real numbers (modeled as `float`s)
- organize top-level imports for `lalib.fields`,
making `Q`, `R`, `C`, and `GF2` importable with
`from lalib.fields import *`
- provide extensive unit and integration tests for the new objects:
+ test generic and common behavior in `tests.fields.test_base`
+ test specific behavior is other modules
+ test the well-known math axioms for all fields (integration tests)
+ test the new objects' docstrings
+ add "pytest-repeat" to run randomized tests many times
2024-10-14 15:17:42 +02:00
|
|
|
@pytest.mark.repeat(utils.N_RANDOM_DRAWS)
|
|
|
|
@pytest.mark.parametrize("field", utils.ALL_FIELDS)
|
|
|
|
class TestAllFieldsManyTimes:
|
|
|
|
"""Run the tests many times for all `field`s."""
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("opr", [operator.add, operator.mul])
|
|
|
|
def test_associativity(self, field, opr):
|
|
|
|
"""`a + (b + c) == (a + b) + c` ...
|
|
|
|
|
|
|
|
... and `a * (b * c) == (a * b) * c`.
|
|
|
|
"""
|
|
|
|
a, b, c = field.random(), field.random(), field.random()
|
|
|
|
|
|
|
|
left = opr(a, opr(b, c))
|
|
|
|
right = opr(opr(a, b), c)
|
|
|
|
|
|
|
|
assert left == pytest.approx(right, abs=utils.DEFAULT_THRESHOLD)
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("opr", [operator.add, operator.mul])
|
|
|
|
def test_commutativity(self, field, opr):
|
|
|
|
"""`a + b == b + a` ...
|
|
|
|
|
|
|
|
... and `a * b == b * a`.
|
|
|
|
"""
|
|
|
|
a, b = field.random(), field.random()
|
|
|
|
|
|
|
|
left = opr(a, b)
|
|
|
|
right = opr(b, a)
|
|
|
|
|
|
|
|
assert left == pytest.approx(right, abs=utils.DEFAULT_THRESHOLD)
|
|
|
|
|
|
|
|
def test_additive_identity(self, field):
|
|
|
|
"""`a + 0 == a`."""
|
|
|
|
a = field.random()
|
|
|
|
|
|
|
|
left = a + field.zero
|
|
|
|
right = a
|
|
|
|
|
|
|
|
assert left == pytest.approx(right, abs=utils.DEFAULT_THRESHOLD)
|
|
|
|
|
|
|
|
def test_multiplicative_identity(self, field):
|
|
|
|
"""`a * 1 == a`."""
|
|
|
|
a = field.random()
|
|
|
|
|
|
|
|
left = a * field.one
|
|
|
|
right = a
|
|
|
|
|
|
|
|
assert left == pytest.approx(right, abs=utils.DEFAULT_THRESHOLD)
|
|
|
|
|
|
|
|
def test_additive_inverse(self, field):
|
|
|
|
"""`a + (-a) == 0`."""
|
|
|
|
a = field.random()
|
|
|
|
|
|
|
|
left = a + (-a)
|
|
|
|
right = field.zero
|
|
|
|
|
|
|
|
assert left == pytest.approx(right, abs=utils.DEFAULT_THRESHOLD)
|
|
|
|
|
|
|
|
def test_multiplicative_inverse(self, field):
|
|
|
|
"""`a * (1 / a) == 1`."""
|
|
|
|
a = field.random()
|
|
|
|
|
|
|
|
# Realistically, `ZeroDivisionError` only occurs for `GF2`
|
|
|
|
# => With a high enough `utils.N_RANDOM_DRAWS`
|
|
|
|
# this test case is also `assert`ed for `GF2`
|
|
|
|
with contextlib.suppress(ZeroDivisionError):
|
|
|
|
left = a * (field.one / a)
|
|
|
|
right = field.one
|
|
|
|
|
|
|
|
assert left == pytest.approx(right, abs=utils.DEFAULT_THRESHOLD)
|
|
|
|
|
|
|
|
def test_distributivity(self, field):
|
|
|
|
"""`a * (b + c) == (a * b) + (a * c)`."""
|
|
|
|
a, b, c = field.random(), field.random(), field.random()
|
|
|
|
|
|
|
|
left = a * (b + c)
|
|
|
|
right = (a * b) + (a * c)
|
|
|
|
|
|
|
|
assert left == pytest.approx(right, abs=utils.DEFAULT_THRESHOLD)
|