Skip to content

Commit 2bc3ac2

Browse files
Add bindings for type qfb_t
1 parent 5e387c9 commit 2bc3ac2

File tree

8 files changed

+219
-1
lines changed

8 files changed

+219
-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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from .types.acb_mat import acb_mat
4040
from .types.acb_series import acb_series
4141

42+
from .types.qfb import qfb
4243
from .types.dirichlet import dirichlet_char, dirichlet_group
4344
from .functions.showgood import good, showgood
4445

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
@@ -1645,6 +1645,34 @@ def test_nmod_series():
16451645
# XXX: currently no code in nmod_series.pyx
16461646
pass
16471647

1648+
def test_qfb():
1649+
Q = flint.qfb
1650+
1651+
assert raises(lambda: Q(1, 2, "asd"), TypeError)
1652+
assert raises(lambda: Q(1, "asd", 2), TypeError)
1653+
assert raises(lambda: Q("asd", 1, 2), TypeError)
1654+
1655+
q = Q.prime_form(-163, 53)
1656+
assert repr(q) == "qfb(53, 7, 1)"
1657+
assert q == Q(53, 7, 1)
1658+
assert not q.is_reduced()
1659+
assert q.reduce() == Q(1, 1, 41)
1660+
1661+
q = Q.prime_form(-199, 2)
1662+
assert q.is_reduced()
1663+
assert q**0 == Q(1, 1, 50)
1664+
assert q**9 == Q(1, 1, 50)
1665+
assert q**2 * q**5 == q**7
1666+
assert q.inverse() == q**-1
1667+
assert q.inverse() == q**8
1668+
1669+
q = Q.prime_form(-3212123, 7)
1670+
assert q**123456789123456789123456789123456789 == q.inverse()
1671+
assert q**-123456789123456789123456789123456789 == q
1672+
1673+
q = Q(291233996924844144901, 405366016683999883959, 141056340620716310090)
1674+
assert q.discriminant() == -976098765432101234567890679
1675+
assert q**18045470076579 == Q(1, 1, 244024691358025308641972670)
16481676

16491677
def test_arb():
16501678
A = flint.arb
@@ -5037,6 +5065,8 @@ def test_all_tests():
50375065
test_fq_default,
50385066
test_fq_default_poly,
50395067

5068+
test_qfb,
5069+
50405070
test_arb,
50415071

50425072
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)