Skip to content

Commit f664fa4

Browse files
Add bindings for type qfb_t
1 parent 04f5ba5 commit f664fa4

File tree

8 files changed

+220
-1
lines changed

8 files changed

+220
-1
lines changed

bin/all_rst_to_pxd.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ modules=(
7272
# "dlog"
7373
# "bool_mat"
7474
# "perm"
75-
# "qfb"
75+
"qfb"
7676
# "nf"
7777
# "nf_elem"
7878
# "fmpzi"

src/flint/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
from .types.fmpq_mpoly import fmpq_mpoly_ctx, fmpq_mpoly, fmpq_mpoly_vec
2828

29+
from .types.qfb import *
30+
2931
from .types.fq_default import *
3032
from .types.fq_default_poly import *
3133

src/flint/flintlib/functions/qfb.pxd

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from flint.flintlib.types.flint cimport fmpz_t, slong, ulong
2+
from flint.flintlib.types.qfb cimport qfb_t
3+
4+
# unknown type qfb
5+
# unknown type qfb_hash_t
6+
7+
8+
cdef extern from "flint/qfb.h":
9+
void qfb_init(qfb_t q)
10+
void qfb_clear(qfb_t q)
11+
# void qfb_array_clear(qfb ** forms, slong num)
12+
# qfb_hash_t * qfb_hash_init(slong depth)
13+
# void qfb_hash_clear(qfb_hash_t * qhash, slong depth)
14+
# void qfb_hash_insert(qfb_hash_t * qhash, qfb_t q, qfb_t q2, slong iter, slong depth)
15+
# slong qfb_hash_find(qfb_hash_t * qhash, qfb_t q, slong depth)
16+
void qfb_set(qfb_t f, qfb_t g)
17+
int qfb_equal(qfb_t f, qfb_t g)
18+
void qfb_print(qfb_t q)
19+
void qfb_discriminant(fmpz_t D, qfb_t f)
20+
void qfb_reduce(qfb_t r, qfb_t f, fmpz_t D)
21+
int qfb_is_reduced(qfb_t r)
22+
# slong qfb_reduced_forms(qfb ** forms, slong d)
23+
# slong qfb_reduced_forms_large(qfb ** forms, slong d)
24+
void qfb_nucomp(qfb_t r, const qfb_t f, const qfb_t g, fmpz_t D, fmpz_t L)
25+
void qfb_nudupl(qfb_t r, const qfb_t f, fmpz_t D, fmpz_t L)
26+
void qfb_pow_ui(qfb_t r, qfb_t f, fmpz_t D, ulong exp)
27+
void qfb_pow(qfb_t r, qfb_t f, fmpz_t D, fmpz_t exp)
28+
void qfb_inverse(qfb_t r, qfb_t f)
29+
int qfb_is_principal_form(qfb_t f, fmpz_t D)
30+
void qfb_principal_form(qfb_t f, fmpz_t D)
31+
int qfb_is_primitive(qfb_t f)
32+
void qfb_prime_form(qfb_t r, fmpz_t D, fmpz_t p)
33+
int qfb_exponent_element(fmpz_t exponent, qfb_t f, fmpz_t n, ulong B1, ulong B2_sqrt)
34+
int qfb_exponent(fmpz_t exponent, fmpz_t n, ulong B1, ulong B2_sqrt, slong c)
35+
int qfb_exponent_grh(fmpz_t exponent, fmpz_t n, ulong B1, ulong B2_sqrt)

src/flint/flintlib/types/qfb.pxd

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from flint.flintlib.types.flint cimport fmpz_t
2+
3+
cdef extern from "flint/qfb.h":
4+
5+
ctypedef struct qfb_struct:
6+
fmpz_t a
7+
fmpz_t b
8+
fmpz_t c
9+
10+
ctypedef qfb_struct qfb_t[1]
11+

src/flint/test/test_all.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1617,6 +1617,34 @@ def test_nmod_series():
16171617
# XXX: currently no code in nmod_series.pyx
16181618
pass
16191619

1620+
def test_qfb():
1621+
Q = flint.qfb
1622+
1623+
assert raises(lambda: Q(1, 2, "asd"), TypeError)
1624+
assert raises(lambda: Q(1, "asd", 2), TypeError)
1625+
assert raises(lambda: Q("asd", 1, 2), TypeError)
1626+
1627+
q = Q.prime_form(-163, 53)
1628+
assert repr(q) == "qfb(53, 7, 1)"
1629+
assert q == Q(53, 7, 1)
1630+
assert not q.is_reduced()
1631+
assert q.reduce() == Q(1, 1, 41)
1632+
1633+
q = Q.prime_form(-199, 2)
1634+
assert q.is_reduced()
1635+
assert q**0 == Q(1, 1, 50)
1636+
assert q**9 == Q(1, 1, 50)
1637+
assert q**2 * q**5 == q**7
1638+
assert q.inverse() == q**-1
1639+
assert q.inverse() == q**8
1640+
1641+
q = Q.prime_form(-3212123, 7)
1642+
assert q**123456789123456789123456789123456789 == q.inverse()
1643+
assert q**-123456789123456789123456789123456789 == q
1644+
1645+
q = Q(291233996924844144901, 405366016683999883959, 141056340620716310090)
1646+
assert q.discriminant() == -976098765432101234567890679
1647+
assert q**18045470076579 == Q(1, 1, 244024691358025308641972670)
16201648

16211649
def test_arb():
16221650
A = flint.arb
@@ -4897,6 +4925,8 @@ def test_all_tests():
48974925
test_fq_default,
48984926
test_fq_default_poly,
48994927

4928+
test_qfb,
4929+
49004930
test_arb,
49014931

49024932
test_pickling,

src/flint/types/meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ exts = [
3030
'fq_default',
3131
'fq_default_poly',
3232

33+
'qfb',
34+
3335
'arf',
3436

3537
'arb',

src/flint/types/qfb.pxd

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from flint.flintlib.functions.qfb cimport *
2+
from flint.types.fmpz cimport fmpz
3+
4+
cdef class qfb:
5+
cdef qfb_t val
6+
# the discriminant, stored for convenience
7+
cdef fmpz D

src/flint/types/qfb.pyx

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
from flint.flintlib.functions.fmpz cimport fmpz_abs, fmpz_root, fmpz_set
2+
from flint.types.fmpz cimport fmpz, any_as_fmpz
3+
from flint.utils.typecheck cimport typecheck
4+
5+
cdef class qfb:
6+
"""
7+
The qfb type represents definite binary quadratic forms
8+
over Z, with composition, inverse and power operations
9+
compatible with the class group of a given discriminant.
10+
11+
Some operations require the form to be primitive.
12+
"""
13+
def __cinit__(self):
14+
qfb_init(self.val)
15+
self.D = fmpz(0)
16+
17+
def __dealloc__(self):
18+
qfb_clear(self.val)
19+
20+
def __init__(self, a, b, c):
21+
a_fmpz = any_as_fmpz(a)
22+
b_fmpz = any_as_fmpz(b)
23+
c_fmpz = any_as_fmpz(c)
24+
if a_fmpz is NotImplemented:
25+
raise TypeError(f"Incorrect type {type(a)} for qfb coefficient")
26+
if b_fmpz is NotImplemented:
27+
raise TypeError(f"Incorrect type {type(b)} for qfb coefficient")
28+
if c_fmpz is NotImplemented:
29+
raise TypeError(f"Incorrect type {type(c)} for qfb coefficient")
30+
fmpz_set(self.val[0].a, (<fmpz>a_fmpz).val)
31+
fmpz_set(self.val[0].b, (<fmpz>b_fmpz).val)
32+
fmpz_set(self.val[0].c, (<fmpz>c_fmpz).val)
33+
D = fmpz()
34+
qfb_discriminant(D.val, self.val)
35+
self.D = D
36+
37+
def __repr__(self):
38+
a, b, c = self.coefficients()
39+
return f"qfb({a}, {b}, {c})"
40+
41+
def __eq__(self, other):
42+
if self is other:
43+
return True
44+
45+
if typecheck(other, qfb):
46+
return bool(qfb_equal(self.val, (<qfb>other).val))
47+
48+
return False
49+
50+
def __mul__(q1, q2):
51+
"Returns a reduced form equivalent to the composition of q1 and q2"
52+
if not q1.is_primitive():
53+
raise ValueError(f"{q1} is not primitive")
54+
55+
cdef qfb res = qfb.__new__(qfb)
56+
cdef fmpz_t L
57+
fmpz_abs(L, q1.D.val)
58+
fmpz_root(L, L, 4)
59+
qfb_nucomp(res.val, q1.val, (<qfb>q2).val, q1.D.val, L)
60+
qfb_reduce(res.val, res.val, q1.D.val)
61+
res.D = q1.D
62+
return res
63+
64+
def __pow__(q, e, mod):
65+
"Returns a reduced form equivalent to the e-th iterated composition of q"
66+
if mod is not None:
67+
raise NotImplementedError("modular exponentiation")
68+
69+
if not q.is_primitive():
70+
raise ValueError(f"{q} is not primitive")
71+
72+
e_fmpz = any_as_fmpz(e)
73+
if e_fmpz is NotImplemented:
74+
raise TypeError(f"exponent cannot be cast to an fmpz type: {e}")
75+
76+
# qfb_pow does not support negative exponents and will loop forever
77+
# if a negative integer is provided.
78+
e_abs = abs(e_fmpz)
79+
80+
cdef qfb res = qfb.__new__(qfb)
81+
qfb_pow(res.val, q.val, q.D.val, (<fmpz>e_abs).val)
82+
if e_fmpz < 0:
83+
qfb_inverse(res.val, res.val)
84+
res.D = q.D
85+
return res
86+
87+
def coefficients(self):
88+
"""
89+
Returns coefficients (a, b, c) of the form as a polynomial q(x,y)=ax²+bxy+cy²
90+
"""
91+
a = fmpz()
92+
fmpz_set(a.val, self.val[0].a)
93+
b = fmpz()
94+
fmpz_set(b.val, self.val[0].b)
95+
c = fmpz()
96+
fmpz_set(c.val, self.val[0].c)
97+
return a, b, c
98+
99+
def discriminant(self):
100+
return self.D
101+
102+
def is_reduced(self):
103+
return bool(qfb_is_reduced(self.val))
104+
105+
def is_primitive(self):
106+
return bool(qfb_is_primitive(self.val))
107+
108+
def inverse(self):
109+
cdef qfb res = qfb.__new__(qfb)
110+
qfb_inverse(res.val, self.val)
111+
res.D = self.D
112+
return res
113+
114+
def reduce(self):
115+
cdef qfb res = qfb.__new__(qfb)
116+
qfb_reduce(res.val, self.val, self.D.val)
117+
res.D = self.D
118+
return res
119+
120+
@classmethod
121+
def prime_form(cls, D, p):
122+
"""
123+
Returns the unique reduced form with 0 < b ≤ p. Requires that p is prime.
124+
"""
125+
126+
d_fmpz = any_as_fmpz(D)
127+
p_fmpz = any_as_fmpz(p)
128+
129+
cdef qfb res = qfb.__new__(qfb)
130+
qfb_prime_form(res.val, (<fmpz>d_fmpz).val, (<fmpz>p_fmpz).val)
131+
res.D = d_fmpz
132+
return res

0 commit comments

Comments
 (0)