Skip to content

Commit

Permalink
feat: use fancy(?) factor(::ZZRingElem) (#1850)
Browse files Browse the repository at this point in the history
Co-authored-by: Claus Fieker <fieker@mathematik.uni-kl.de>
  • Loading branch information
thofma and fieker authored Sep 18, 2024
1 parent 3e66d55 commit 18dfcb9
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 106 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ RandomExtensions = "fb686558-2515-59ef-acaa-46db3789a887"
SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[compat]
AbstractAlgebra = "0.42.0"
AbstractAlgebra = "0.42.7"
FLINT_jll = "^300.100.100"
Libdl = "1.6"
LinearAlgebra = "1.6"
Expand Down
2 changes: 2 additions & 0 deletions src/Rings.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

include("flint/fmpz.jl")

include("flint/fmpz_factor.jl")

include("flint/fmpz_poly.jl")

include("flint/nmod_poly.jl")
Expand Down
105 changes: 0 additions & 105 deletions src/flint/fmpz.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1591,83 +1591,6 @@ function is_squarefree(n::Union{Int, ZZRingElem})
end
return isone(maximum(values(factor(n).fac); init = 1))
end

###############################################################################
#
# Factorization
#
###############################################################################

function _factor(a::ZZRingElem)
F = fmpz_factor()
ccall((:fmpz_factor, libflint), Nothing, (Ref{fmpz_factor}, Ref{ZZRingElem}), F, a)
res = Dict{ZZRingElem, Int}()
for i in 1:F.num
z = ZZRingElem()
ccall((:fmpz_factor_get_fmpz, libflint), Nothing,
(Ref{ZZRingElem}, Ref{fmpz_factor}, Int), z, F, i - 1)
res[z] = unsafe_load(F.exp, i)
end
return res, canonical_unit(a)
end

function factor(a::T) where T <: Union{Int, UInt}
iszero(a) && throw(ArgumentError("Argument must be non-zero"))
u = sign(a)
a = u < 0 ? -a : a
F = n_factor()
ccall((:n_factor, libflint), Nothing, (Ref{n_factor}, UInt), F, a)
res = Dict{T, Int}()
for i in 1:F.num
z = F.p[i]
res[z] = F.exp[i]
end
return Fac(u, res)
end

################################################################################
#
# ECM
#
################################################################################

function _ecm(a::ZZRingElem, B1::UInt, B2::UInt, ncrv::UInt,
rnd = _flint_rand_states[Threads.threadid()])
f = ZZRingElem()
r = ccall((:fmpz_factor_ecm, libflint), Int32,
(Ref{ZZRingElem}, UInt, UInt, UInt, Ref{rand_ctx}, Ref{ZZRingElem}),
f, ncrv, B1, B2, rnd, a)
return r, f
end

function _ecm(a::ZZRingElem, B1::Int, B2::Int, ncrv::Int,
rnd = _flint_rand_states[Threads.threadid()])
return _ecm(a, UInt(B1), UInt(B2), UInt(ncrv), rnd)
end

function ecm(a::ZZRingElem, max_digits::Int = div(ndigits(a), 2) + 1,
rnd = _flint_rand_states[Threads.threadid()],
B1 = _ecm_B1s[Threads.threadid()],
nC = _ecm_nCs[Threads.threadid()])
n = ndigits(a, 10)
B1s = 15

i = 1
s = div(max_digits-15, 5) + 2
s = max(i, s)
while i <= s
e, f = _ecm(a, B1[i]*1000, B1[i]*1000*100, nC[i], rnd)
if e != 0
return (e,f)
end
i += 1
if i > length(B1)
return (e, f)
end
end
return (Int32(0), a)
end

################################################################################
#
# Factor trial range
Expand All @@ -1688,34 +1611,6 @@ function _factor_trial_range(N::ZZRingElem, start::Int = 0, np::Int = 10^5)
return res, canonical_unit(N)
end

@doc raw"""
factor(a::ZZRingElem)
factor(a::UInt)
factor(a::Int)
Return a factorisation of $a$ using a `Fac` struct (see the documentation on
factorisation in Nemo).
# Examples
```jldoctest
julia> factor(ZZ(12))
1 * 2^2 * 3
julia> factor(UInt(12))
1 * 2^2 * 3
julia> factor(12)
1 * 2^2 * 3
```
"""
function factor(a::ZZRingElem)
iszero(a) && throw(ArgumentError("Argument must be non-zero"))
fac, z = _factor(a)
return Fac(z, fac)
end

###############################################################################
#
# Number theoretic/combinatorial
Expand Down
196 changes: 196 additions & 0 deletions src/flint/fmpz_factor.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
###############################################################################
#
# Flint factor(s) functiosn
#
###############################################################################

# raw fmpz_factor version
function _factor(a::ZZRingElem)
F = fmpz_factor()
ccall((:fmpz_factor, libflint), Nothing, (Ref{fmpz_factor}, Ref{ZZRingElem}), F, a)
res = Dict{ZZRingElem, Int}()
for i in 1:F.num
z = ZZRingElem()
ccall((:fmpz_factor_get_fmpz, libflint), Nothing,
(Ref{ZZRingElem}, Ref{fmpz_factor}, Int), z, F, i - 1)
res[z] = unsafe_load(F.exp, i)
end
return res, canonical_unit(a)
end

function factor(a::T) where T <: Union{Int, UInt}
iszero(a) && throw(ArgumentError("Argument must be non-zero"))
u = sign(a)
a = u < 0 ? -a : a
F = n_factor()
ccall((:n_factor, libflint), Nothing, (Ref{n_factor}, UInt), F, a)
res = Dict{T, Int}()
for i in 1:F.num
z = F.p[i]
res[z] = F.exp[i]
end
return Fac(u, res)
end

################################################################################
#
# ECM
#
################################################################################

function _ecm(a::ZZRingElem, B1::UInt, B2::UInt, ncrv::UInt,
rnd = _flint_rand_states[Threads.threadid()])
f = ZZRingElem()
r = ccall((:fmpz_factor_ecm, libflint), Int32,
(Ref{ZZRingElem}, UInt, UInt, UInt, Ref{rand_ctx}, Ref{ZZRingElem}),
f, ncrv, B1, B2, rnd, a)
return r, f
end

function _ecm(a::ZZRingElem, B1::Int, B2::Int, ncrv::Int,
rnd = _flint_rand_states[Threads.threadid()])
return _ecm(a, UInt(B1), UInt(B2), UInt(ncrv), rnd)
end

function ecm(a::ZZRingElem, max_digits::Int = div(ndigits(a), 2) + 1,
rnd = _flint_rand_states[Threads.threadid()],
B1 = _ecm_B1s[Threads.threadid()],
nC = _ecm_nCs[Threads.threadid()])
n = ndigits(a, 10)
B1s = 15

i = 1
s = div(max_digits-15, 5) + 2
s = max(i, s)
while i <= s
e, f = _ecm(a, B1[i]*1000, B1[i]*1000*100, nC[i], rnd)
if e != 0
return (e,f)
end
i += 1
if i > length(B1)
return (e, f)
end
end
return (Int32(0), a)
end

################################################################################
#
# Main work horse
#
################################################################################

# TODO: problem(s)
# - flint factor = mpqs is hopeless if > n digits, but asymptotically and
# practically faster than ecm.
# - ecm is much better if there are "small" factors.
# - p-1 and p+1 methods are missing
#
# so probably
# - if n is small enough -> Nemo
# - if n is too large: ecm
# otherwise
# - need ecm to find small factors
# then recurse...

const big_primes = ZZRingElem[]

function factor(N::ZZRingElem)
if iszero(N)
throw(ArgumentError("Argument is not non-zero"))
end
N_in = N
global big_primes
r, c = factor_trial_range(N)
for (p, v) = r
N = divexact(N, p^v)
end
if is_unit(N)
@assert N == c
return Fac(c, r)
end
N *= c
@assert N > 0

for p = big_primes
v, N = remove(N, p)
if v > 0
@assert !haskey(r, p)
r[p] = v
end
end
factor_insert!(r, N)
for p = keys(r)
if nbits(p) > 60 && !(p in big_primes)
push!(big_primes, p)
end
end
return Fac(c, r)
end

function factor_insert!(r::Dict{ZZRingElem,Int}, N::ZZRingElem, scale::Int=1)
#assumes N to be positive
# no small divisors
# no big_primes
if isone(N)
return r
end
fac, N = is_perfect_power_with_data(N)
if fac > 1
return factor_insert!(r, N, fac * scale)
end
if is_prime(N)
@assert !haskey(r, N)
r[N] = scale
return r
end
if ndigits(N) < 60
s, = _factor(N) #MPQS
for (p, k) in s
if haskey(r, p)
r[p] += k * scale
else
r[p] = k * scale
end
end
return r
end

e, f = ecm(N)
if e == 0
s = factor(N)
for (p, k) in s
if haskey(r, p)
r[p] += k * scale
else
r[p] = k * scale
end
end
return r
end
cp = coprime_base([N, f])
for i = cp
factor_insert!(r, i, scale * valuation(N, i))
end
return r
end

################################################################################
#
# Trial factorization
#
################################################################################

function factor_trial_range(N::ZZRingElem, start::Int=0, np::Int=10^5)
F = fmpz_factor()
ccall((:fmpz_factor_trial_range, libflint), Nothing, (Ref{fmpz_factor}, Ref{ZZRingElem}, UInt, UInt), F, N, start, np)
res = Dict{ZZRingElem,Int}()
for i in 1:F.num
z = ZZRingElem()
ccall((:fmpz_factor_get_fmpz, libflint), Nothing,
(Ref{ZZRingElem}, Ref{fmpz_factor}, Int), z, F, i - 1)
res[z] = unsafe_load(F.exp, i)
end
return res, canonical_unit(N)
end
6 changes: 6 additions & 0 deletions test/flint/fmpz-test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,12 @@ end
d, u = Nemo._factor_trial_range(n, 0, 50)
@test isone(u)
@test prod(p^e for (p, e) in d) == n

let
a = ZZ(65261404486168272596469055321032207458900347428758158658012236487063743080177113810110282680249226184903475017526897956542827985941267684646505051975441057446672916412353515625)
fact = factor(a)
@test a == prod(p^e for (p, e) in fact)
end
end

@testset "ZZRingElem.number_theoretic" begin
Expand Down

0 comments on commit 18dfcb9

Please sign in to comment.