Ryan Rueger

ryan@rueg.re / picture / key / home
aboutsummaryrefslogtreecommitdiffhomepage
path: root/ideals.py
blob: 7065c2ea85c3e1fa82d1f7c9c5dccdfda9606fe8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
from sage.all import *
import itertools as itt
from time import time

def NormEl(el, p):
    return el[0]**2 + p*el[1]**2

def MulEl(el1, el2, p):
    a1, b1 = el1
    a2, b2 = el2
    return [a1*a2 - p*b1*b2, a1*b2 + b1*a2]

def ScaleEl(el, scal):
    a, b = el
    return [a/scal, b/scal]

def _norm(v):
    return sum([x**2 for x in v])

def _add(a, b):
    return [ia + ib for ia, ib in zip(a,b)]

def _mul(v, n):
    return [x*n for x in v]

def _is_zero(v):
    return all([x == 0 for x in v])

def ShortVectors(L, B, cf_b=15):
    """
    Enumerate vectors in L with norm bounded by B.
    Tries linear combinations with coefficients up to cf_b
    """
    sv = {}
    assert len(L) == 2 # Avoiding sign repeats
    for cff in itt.product(range(-cf_b, cf_b), range(0, cf_b)):
    # for cff in itt.product(range(-cf_b, cf_b), repeat=len(L)):
        v = [0 for _ in range(len(L[0]))]
        for i in range(len(L)):
            v = _add(v, _mul(L[i], cff[i]))
        nv = _norm(v)
        if nv < B and nv != 0 and not nv in sv:
            sv[nv] = v
    sv = [(k, sv[k]) for k in sv]
    sv.sort()
    return sv

def ShortIdeals(I, N, B, p, spr=None, cf_b=15):
    """
    For a given ideal returns the shortest vectors (or equivalently the smallest
    equivalent ideals).

    Input:
    - I: an ideal as list of generators, where the element [a1, a2] is a1 + i*a2
    - N: the norm of I
    - B: bound on the (reduced) norm of the elements returned
    - p: the field prime
    - spr: isqrt(p) if precomputed # TODO: probably remove
    - cf_b: number of combinations to try in ShortVectors
    Output:
    - res: a list of pairs (n, [a, b, c]) where [a, b] are generators (as list) of
        and ideal I1 equivalent to I, n is the norm of I1 and c is the short element
        in I sending I to I1
    """
    if not spr:
        spr = isqrt(p)

    L = Matrix(ZZ, 2,[
        I[0][0], spr*I[0][1],
        I[1][0], spr*I[1][1]
    ])

    L = L.LLL()
    L = [L[0], L[1]]
    res = []
    sv2 = ShortVectors(L, N*B, cf_b=cf_b)
    for _, sh in sv2:
        cshel = [sh[0], -sh[1]/spr]
        if sh[1] % spr != 0:
            print('Non divisible')
        idl = [ScaleEl(MulEl(I[0], cshel, p), N), ScaleEl(MulEl(I[1], cshel, p), N), cshel]
        res.append((NormEl(cshel, p)/N, idl))
    return res

def ReducedBasis(I_basis):
    #LLL is way overkill here, change later
    def _matrix_to_gens(M, B):
        return [sum(c * g for c, g in zip(row, B)) for row in M]

    def gram_matrix(basis):
        M = []
        for a in basis:
            M.append([(a*b.conjugate()).trace() for b in basis])
        return Matrix(QQ, M)

    G = gram_matrix(I_basis)
    U = G.LLL_gram().transpose()
    reduced_basis_elements = _matrix_to_gens(U, I_basis)
    return reduced_basis_elements

def PrincipalGenerator(frak_a):
    N = frak_a.norm()

    assert len(frak_a.gens()) == 2, "The ideal has only one generator (which is not really an issue)"
    for gen in ReducedBasis(frak_a.gens()):
        if gen.norm() == N:
            return gen
    assert False, "WARNING: Not a principal ideal"

def Conjugate(order, frak_a):
    a, b = frak_a.gens_two()
    return order*a.conjugate() + order*b.conjugate()

def RandomDegreeOnePrimeIdeal(order, gen, p):
    ell = next_prime(randint(-order.discriminant(), -10*order.discriminant()))
    while kronecker(-p, ell) != 1:
        ell = next_prime(randint(-order.discriminant(), -10*order.discriminant()))

    lam = Integer(GF(ell)(-p).sqrt())

    frak_ell = order*ell + order*(gen-lam)

    assert frak_ell.norm() == ell
    return ell, frak_ell

def RandomFixedPrimeIdeal(order, gen, p, ell):
    lam = Integer(GF(ell)(-p).sqrt())

    frak_ell = order*ell + order*(gen-lam)

    assert frak_ell.norm() == ell
    return ell, frak_ell

def ideal_to_sage(I, O):
    """
    Converts an ideal as a list of lists into
    an ideal in the order O
    """
    I[0] = [QQ(i) for i in I[0]]
    I[1] = [QQ(i) for i in I[1]]

    pi = O.gens()[1]

    g1 = I[0][0] + pi*I[0][1]
    g2 = I[1][0] + pi*I[1][1]
    return O*g1 + O*g2