-
Notifications
You must be signed in to change notification settings - Fork 31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Wolfram's primitive polynomials #464
base: release/0.3.x
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## release/0.3.x #464 +/- ##
=================================================
- Coverage 96.38% 96.35% -0.04%
=================================================
Files 43 44 +1
Lines 5616 5733 +117
=================================================
+ Hits 5413 5524 +111
- Misses 203 209 +6 ☔ View full report in Codecov by Sentry. |
How do you propose handling accessing this database? In general, Wolfram's primitive polynomials are not the same as the ones obtained using from galois import Poly, primitive_poly
from galois._databases import PrimitivePolyDatabase
db = PrimitivePolyDatabase()
for degree in range(2, 50):
degrees, coeffs = db.fetch(2, degree)
poly = Poly.Degrees(degrees, coeffs)
poly_ = primitive_poly(2, degree)
if poly != poly_:
print(f"GF(2^{degree})")
print(f"primitive_poly(2, {degree}) = {poly_}")
print(f"Wolfram's primitive poly = {poly}\n")
assert poly.is_primitive()
GF(2^18)
primitive_poly(2, 18) = x^18 + x^5 + x^2 + x + 1
Wolfram's primitive poly = x^18 + x^7 + 1
GF(2^32)
primitive_poly(2, 32) = x^32 + x^7 + x^5 + x^3 + x^2 + x + 1
Wolfram's primitive poly = x^32 + x^7 + x^6 + x^2 + 1
GF(2^33)
primitive_poly(2, 33) = x^33 + x^6 + x^4 + x + 1
Wolfram's primitive poly = x^33 + x^13 + 1
GF(2^34)
primitive_poly(2, 34) = x^34 + x^7 + x^6 + x^5 + x^2 + x + 1
Wolfram's primitive poly = x^34 + x^8 + x^4 + x^3 + 1
GF(2^36)
primitive_poly(2, 36) = x^36 + x^6 + x^5 + x^4 + x^2 + x + 1
Wolfram's primitive poly = x^36 + x^11 + 1
GF(2^37)
primitive_poly(2, 37) = x^37 + x^5 + x^4 + x^3 + x^2 + x + 1
Wolfram's primitive poly = x^37 + x^6 + x^4 + x + 1
GF(2^42)
primitive_poly(2, 42) = x^42 + x^5 + x^4 + x^3 + x^2 + x + 1
Wolfram's primitive poly = x^42 + x^7 + x^4 + x^3 + 1
GF(2^46)
primitive_poly(2, 46) = x^46 + x^8 + x^5 + x^3 + x^2 + x + 1
Wolfram's primitive poly = x^46 + x^8 + x^7 + x^6 + 1
GF(2^48)
primitive_poly(2, 48) = x^48 + x^7 + x^5 + x^4 + x^2 + x + 1
Wolfram's primitive poly = x^48 + x^9 + x^7 + x^4 + 1
GF(2^49)
primitive_poly(2, 49) = x^49 + x^6 + x^5 + x^4 + 1
Wolfram's primitive poly = x^49 + x^9 + 1 I don't know if the ones generated by |
This is great, @iyanmv. Thanks for submitting this!
I was hoping these polynomials were the lexicographically-first primitive polynomial with minimal terms. I believe this is the case from the spot checks below (using my #463 branch). Although, we should do more confirmation and testing. In [1]: import galois
In [2]: galois.primitive_poly(2, 18)
Out[2]: Poly(x^18 + x^5 + x^2 + x + 1, GF(2))
In [3]: galois.primitive_poly(2, 18, terms=3)
Out[3]: Poly(x^18 + x^7 + 1, GF(2))
In [4]: galois.primitive_poly(2, 32)
Out[4]: Poly(x^32 + x^7 + x^5 + x^3 + x^2 + x + 1, GF(2))
In [5]: galois.primitive_poly(2, 32, terms=5)
Out[5]: Poly(x^32 + x^7 + x^6 + x^2 + 1, GF(2))
In [6]: galois.primitive_poly(2, 33)
Out[6]: Poly(x^33 + x^6 + x^4 + x + 1, GF(2))
In [7]: galois.primitive_poly(2, 33, terms=3)
Out[7]: Poly(x^33 + x^13 + 1, GF(2))
In [8]: galois.primitive_poly(2, 34)
Out[8]: Poly(x^34 + x^7 + x^6 + x^5 + x^2 + x + 1, GF(2))
In [9]: galois.primitive_poly(2, 34, terms=5)
Out[9]: Poly(x^34 + x^8 + x^4 + x^3 + 1, GF(2)) If this is generally true, I suggest we hook into |
I think that's the case, but I will check a bit more.
Okay, sounds good. Last commit makes |
- New script to create a primitive polynomials database - New PrimitivePolyDatabase class to handle access to the database - Add Wolfram's primitive polynomials
- Try to fetch nonzero degrees and coefficients from the database when primitive_poly() is called with terms="min". If poly is not in the database, fallback to compute it.
- Add LUTs for high degree primitive polynomials from Wolfram's database - Add test_minimum_terms_from_database with a timeout=1s
1a9d1d0
to
41486bf
Compare
Windows doesn't like the |
It looks like
Yeah. We could always implement a poor man's timeout. @pytest.mark.parametrize("order,degree,polys", PARAMS_DB)
def test_minimum_terms_from_database(order, degree, polys):
tick = time.time()
p = galois.primitive_poly(order, degree, terms="min")
tock = time.time()
assert tock - tick < 1.0
db_degrees = p.nonzero_degrees.tolist()
db_coeffs = p.nonzero_coeffs.tolist()
exp_degrees, exp_coeffs = polys
assert db_degrees == exp_degrees and db_coeffs == exp_coeffs |
I think you can directly fetch from your repo without having to clone my fork.
|
I updated previous command to create a new local branch. |
I'm running a local test to verify that the polynomials returned by the database are the same as those found by this library on its own. I likely won't get through testing all of them, but I'd like to test a bunch. import galois
order = 2
for degree in range(1, 1200):
p1 = galois.primitive_poly(order, degree, use_database=False)
p2 = galois.primitive_poly(order, degree, use_database=True)
print(f"{order}^{degree}: {p1 == p2}\t{p1}\t{p2}") I'm running each order in parallel. I'll let it run for a few hours (or maybe overnight). I'll report back with findings. |
Is |
I just did this for testing. I believe we should always use the database once we are confident it always gives us the answer we expect. if use_database and terms == "min" and method == "min": Currently up to this state, and still running strong 💪: 2^327: True x^327 + x^7 + x^6 + x^5 + x^3 + x^2 + 1 x^327 + x^7 + x^6 + x^5 + x^3 + x^2 + 1
3^40: True x^40 + x + 2 x^40 + x + 2
5^109: True x^109 + x^2 + 2x + 3 x^109 + x^2 + 2x + 3
7^107: True x^107 + 6x^2 + x + 2 x^107 + 6x^2 + x + 2 |
One thing I think we should do is add a blurb discussing the performance benefit (for select orders and degrees) that comes with the database when using The reason I think the blurb is important is because someone may call We can place an admonition inline with the argument like this.
|
Well that was embarrassing... my script didn't even test the code it was supposed to! It needed a I updated the scriptimport pprint
import time
import galois
order = 2
if order == 2:
degrees = range(1, 1200)
elif order == 3:
degrees = range(1, 660)
elif order == 5:
degrees = range(1, 430)
elif order == 7:
degrees = range(1, 358)
else:
raise AssertionError
different_polys = []
for degree in degrees:
print(f"{order}^{degree}:")
tick = time.time_ns()
p1 = galois.primitive_poly(order, degree, terms="min", use_database=False)
tock = time.time_ns()
print(f"\tWithout database: {(tock - tick)*1e-9} seconds")
tick = time.time()
p2 = galois.primitive_poly(order, degree, terms="min", use_database=True)
tock = time.time()
print(f"\tWith database: {(tock - tick)*1e-9} seconds")
print(f"\t{p1 == p2}\t{p1}\t{p2}")
if p1 != p2:
different_polys.append((order, degree, p1, p2))
pprint.pprint(different_polys) However, I've run into some discrepancies. (The binary cases haven't disagreed yet.) 3^17:
Without database: 0.023376024000000002 seconds
With database: 5.013704299926758e-12 seconds
True x^17 + 2x + 1 x^17 + 2x + 1
3^18:
Without database: 1.1321898590000001 seconds
With database: 4.955530166625977e-12 seconds
False x^18 + x^9 + x^5 + 2 x^18 + x^13 + x + 2
Traceback (most recent call last):
File "test_3.py", line 30, in <module>
assert p1 == p2
AssertionError 5^3:
Without database: 0.05411400500000001 seconds
With database: 5.683422088623047e-12 seconds
True x^3 + 3x + 2 x^3 + 3x + 2
5^4:
Without database: 0.12074545 seconds
With database: 5.507707595825196e-12 seconds
False x^4 + x^2 + 2x + 2 x^4 + 4x^2 + x + 2
Traceback (most recent call last):
File "test_3.py", line 30, in <module>
assert p1 == p2
AssertionError 7^3:
Without database: 0.060279397000000005 seconds
With database: 5.127191543579102e-12 seconds
True x^3 + 3x + 2 x^3 + 3x + 2
7^4:
Without database: 0.20767921 seconds
With database: 5.424976348876954e-12 seconds
False x^4 + x^2 + 3x + 5 x^4 + 6x^2 + x + 3
Traceback (most recent call last):
File "test_3.py", line 30, in <module>
assert p1 == p2
AssertionError I haven't debugged the root issues yet, but wanted to share my findings. And share the script so you can run it locally too. |
I think the problem is that we are not doing what we want when the database doesn't contain the polynomial: galois/src/galois/_polys/_primitive.py Lines 135 to 156 in 06d0ab9
For example, when you do |
Okay, there were two issues. First, the try/if statements were not right. And second, Wolfram's database does not contain lexicographically-first primitive polynomials, but lexicographically-last ones! |
Actually no, it looks like it's something in between. Can you share with me some reference to understand better the lexicographically-first and lexicographically-last? I understand your code, but I don't know how these terms are used in the literature. Maybe there are different conventions? |
Last commit fixes what I meant by wrong order in the if statements. I think it's better to compute the number of min terms in the except part. I kept the |
That was not a good idea. Now I think I got right all possible cases. |
I think the original code and order of
See https://en.wikipedia.org/wiki/Lexicographic_order. My understanding of lexicographical order is that it is equivalent to ordering the polynomials by their integer representation. See the example below of degree-3 polynomials over GF(3) in lexicographical order. In [6]: field = galois.GF(3)
In [7]: for i in range(field.order**3, 2*field.order**3):
...: poly = galois.Poly.Int(i, field=field)
...: print(poly)
...:
x^3
x^3 + 1
x^3 + 2
x^3 + x
x^3 + x + 1
x^3 + x + 2
x^3 + 2x
x^3 + 2x + 1
x^3 + 2x + 2
x^3 + x^2
x^3 + x^2 + 1
x^3 + x^2 + 2
x^3 + x^2 + x
x^3 + x^2 + x + 1
x^3 + x^2 + x + 2
x^3 + x^2 + 2x
x^3 + x^2 + 2x + 1
x^3 + x^2 + 2x + 2
x^3 + 2x^2
x^3 + 2x^2 + 1
x^3 + 2x^2 + 2
x^3 + 2x^2 + x
x^3 + 2x^2 + x + 1
x^3 + 2x^2 + x + 2
x^3 + 2x^2 + 2x
x^3 + 2x^2 + 2x + 1
x^3 + 2x^2 + 2x + 2
I think this is correct. I think Wolfram often gives the lexicographically-first polynomial, but sometimes it is close to the first. I don't know why or how they did this. I would understand randomly-found polynomials or deterministically-first polynomials (as with the HP irreducible list you found), but an assortment is perplexing. 😞 |
Since the Wolfram database doesn't neatly plug in to our existing cases (lexicographically first or last, minimal terms or any terms), I see a couple of options. Approach 1Maybe we offer a Approach 2Perhaps we offer the Wolfram database as a separate option inside @export
def primitive_poly(
order: int,
degree: int,
terms: int | str | None = None,
method: Literal["min", "max", "random", "matlab", "wolfram"] = "min",
use_database: bool = True,
) -> Poly: If we do this, then I may later graft What are your thoughts on those two approaches? Do you have any other ideas? |
Oh, I see, I didn't realize
Yes, it makes sense.
I contacted their support (taking advantage of the uni license) asking about how that resource was computed. I will let you know when (if) they answer.
I guess I prefer second approach. Let me think a bit about it. |
Hey, that's great! I'm curious if and how they'll respond. |
Regarding approaches moving forward, here's another thing to think about. I previously had the idea of making custom polynomial databases. Basically running common cases ( Just another idea for you to consider. |
2864e4e
to
db0fde5
Compare
I finally got this response. At least they are looking at it.
|
db9d2ab
to
a140a46
Compare
a0c03ba
to
529ba26
Compare
lines = self._consume_to_next_section() | ||
|
||
# Added: strip whitespace from lines to satisfy _parse_numpydoc_see_also_section() | ||
for i in range(len(lines)): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can just do
for line in lines:
line = line.strip()
...
I'm sorry! I deleted |
In the same line as #462 regarding #140
PrimitivePolyDatabase
class to handle access to the databaseGF(2^1_200)
,GF(3^660)
,GF(5^430)
, andGF(7^358)
galois.primitive_poly