# $Source: /u/maple/research/lib/mod/src/RCS/ReduceField,v $
# $Notify: mvanhoei@daisy.uwaterloo.ca
#
# Calling sequence:  ReduceField(A,vars) mod p
#
# Purpose: Reduction to a finite field.
#
#          If A is an element of the field K(vars) of rational functions in
#          vars with coefficients in K, where K is a field with characteristic
#          zero, then this procedure computes a modular image of A in F(vars)
#          where F is a finite field of characteristic p.
#
#          The input A may also be another expression over K, as long
#          as "A mod p" and indets(A,..) work. So lists and sets are OK, but
#          not tables or matrices.
#
#          The optional argument vars must be set of variables. If omitted
#          then vars={}. All indeterminates appearing in A, except the elements
#          of vars, will be replaced by random elements of Fp. Take p big in
#          order to avoid "division by zero" and such errors (20 digits should
#          be safe enough). If indets(A,'name') minus vars = {} then the only
#          randomness in this procedure is the choice of a factor in case a
#          RootOf factors in the reduced field. This will happen for example if
#          A has both RootOf(x^2-2) and RootOf(x^2-3), because the splitting
#          field of those two RootOf's over Fp has degree <= 2, so at least one
#          of them will factor over the other.
#
#          If there are several RootOf's, this procedure will write all of them
#          in terms of just 1 RootOf, so that the result is usable in other
#          Maple procedures (right now Maple only supports 1 RootOf in char p).
#          This can also be used to support multiple RootOf's mod p in Maple;
#          instead of "ERROR, not supported" if RootOf's [R1,R2,..] occur one
#          could also use ReduceField([R1,R2,...]) mod p instead of [R1,R2,..]
#          and continue the computation (although the reverse transformation is
#          not yet given in the output of this procedure).
#
# Author: Mark van Hoeij, May 1998.
#
#          Note: I wrote a procedure mod/Primfield that is the mod p
#          equivalent of evala/Primfield. To keep the syntax consistent with
#          evala, users should not use mod/ReduceField but use mod/Primfield
#          instead. Therefore only mod/Primfield will be documented by a
#          help page.


# Example application: compute the rank (not savelib'ed).
# Input: matrix. Nested RootOf's, radicals, and variables and
# such are allowed in the input, but not functions.
# Output: the rank
# Timings:
#   A := matrix(10,10,[seq(randpoly(x,degree=5),i=1..100)]):
#   time(); `linalg/rank_heu`(A); time(); linalg[rank](A); time();
`linalg/rank_heu`:=proc(A)
	local n,m,p,B,r,i,j;
	option `Copyright (c) 1998 Waterloo Maple Inc. All rights reserved.`;
	n:=linalg[rowdim](A);
	m:=linalg[coldim](A);
	p:=nextprime(rand(10^10..10^11)());
	# chance on wrong answer: approximately 1/p
	B:=[seq(seq(A[i,j],j=1..m),i=1..n)];
	Gausselim(matrix(n,m,ReduceField(B) mod p),'r') mod p;
	eval(r)
end:

# Reduce not necessarily irreducible RootOf's to one single
# irreducible RootOf by factorization.
#
# Remove parameters by random substitution in Fp. This is
# probabilistic. For this to work with high probability, the prime p
# must be big.
#
# vars=do not substitute values for these variables. They should
# not occur within the RootOfs.
#
`mod/ReduceField` := proc(A, vars, p)
local B, C, Bnew, sub, Br, F, f, x, i, roots, subC;
option `Copyright (c) 1998 Waterloo Maple Inc. All rights reserved.`;
    if nargs = 2 then return procname(A, {}, vars)
    elif not (type(p, posint) and vars=indets(vars,'name')) or
    has(indets(indets(A, 'RootOf'), 'name'), vars) then
        error "wrong input"
    end if;
    B := indets(A, 'name') minus vars;
    if B <> {} then
        return procname(subs(B[1] = rand(1 .. p)(), A), vars, p)
    end if;
    B := indets(A, {'RootOf', 'radical', 'nonreal'});
    if B = {} then A mod p
    elif member('radfielded', vars) or
    indets(A, {'radical', 'nonreal'}) = {} and
    mul(nops([op(i)]), i = B) = 1 then
		# B is set of RootOf's which are radfielded or which have
		# no indices.
        C := B minus `union`(seq(indets(op(1, i), 'RootOf'), i = B));
        C := C[1];
		# Find all RootOf's with the same minpoly (so the
		# only thing that differs is the index).
        C := [seq(`if`(op(1, i) = op(1, C), i, NULL), i = B)];
        B := `mod/ReduceField/sort_B`(B minus {op(C)});
        Bnew := procname(B, vars, p);
        sub := seq(B[i] = Bnew[i], i = 1 .. nops(B)), _Z = x;
        Br := op(indets(Bnew, 'RootOf')); # either NULL or just 1 RootOf.
        F := eval(subs(sub, op(1, C[1])));
        F := Factors(F, Br) mod p;
        subC := NULL;
        do
            F := {seq(i[1], i = F[2])};
		# pick factor of lowest degree.
            f := min(seq(degree(i, x), i = F));
            for i in F do if degree(i, x) = f then f := i; break end if
            end do;
            F := F minus {f};
            roots, sub :=
                `mod/ReduceField/roots_field`(f, {Br}, x, p, nops(C));
            Br := op(indets([roots, subs(sub, {Br})], 'RootOf'));
		# New field = Fp(Br)
            Bnew := subs(sub, Bnew); # This is B translated to the new field.
            f := min(nops(C), nops(roots));
            subC := seq(C[i] = roots[i], i = 1 .. f),
                seq(lhs(i) = subs(sub, rhs(i)), i = [subC]);
            C := C[f + 1 .. -1];
            if C = [] then return
                subs(subC, seq(B[i] = Bnew[i], i = 1 .. nops(B)), A) mod p
            elif F = {} then error "reduction failed"
		# Note that by trying a bit harder it would still be possible
		# to do the reduction.
            end if;
		# There are still some not-yet-assigned indexed RootOf's. Factor
		# the remaining factors of the polynomial in that RootOf. These
		# need not be irreducible anymore because the field Fp(Br) may
		# have changed.
            F := Factors(`*`(op(subs(sub, F))), Br) mod p
        end do
    else procname(subs(radfield([args])[1], A), vars union {'radfielded'},
        p)
    end if
end proc:


# Sort the RootOf's. The nested RootOf's come first,
# so that they will be the first in the substitution.
`mod/ReduceField/sort_B` := proc(B)
local C, i;
option `Copyright (c) 1998 Waterloo Maple Inc. All rights reserved.`;
    if B = {} then []
    else
        C := `union`(seq(indets(op(1, i), 'RootOf'), i = B));
        [op(B minus C), op(procname(C))]
    end if
end proc:


# Determine all roots of an irreducible polynomial f in the splitting
# field over the field given by F.  F={} or F={one RootOf}
`mod/ReduceField/roots_field` := proc(f, F, x, p, m)
local i, R, k, j;
option `Copyright (c) 1998 Waterloo Maple Inc. All rights reserved.`;
    if degree(f, x) = 1 or F = {} then
        R := RootOf(f, x) mod p;
        return `if`(degree(f, x) = 1 or m = 1, [R],
            [seq(i[1], i = Roots(f, R) mod p)]), 0 = 0
    elif m = 1 then
        i := `mod/ReduceField/generator`(f, x, F[1], p);
        if i <> FAIL then return i end if
    end if;
    do
        k := Randprime(degree(f, x)*degree(op(1, op(F)), _Z), x) mod p;
        R := RootOf(k, x) mod p;
        k := Resultant(subs(_Z = x + _Z, op(1, op(F))), op(R), _Z) mod p;
        k := Factors(k) mod p;
        if 1 < k[2][1][2] then next end if;
        k := Gcd(subs(x = x - R, k[2][1][1]), subs(_Z = x, op(1, op(F))))
            mod p;
        if degree(k, x) <> 1 then error  end if;
        k := op(F) = (-coeff(k, x, 0)/coeff(k, x, 1)) mod p;
        i := Expand(subs(x = x + R, subs(k, f))) mod p;
        i := Resultant(subs(R = _Z, i), op(R), _Z) mod p;
        i := Factors(i) mod p;
        if i[2][1][2] = 1 then return [seq(subs(`solve/linear`(
            {Gcd(subs(x = x - R, i[2][j][1]), subs(k, f)) mod p}, {x}), x)
            , j = 1 .. min(m, nops(i[2])))], k
        end if
    end do
end proc:


`mod/ReduceField/generator` := proc(f, x, R, p)
local n, r, g, y, A, q, AA, S, i;
    n := degree(f, x);
    if irem(n, p) = 0 then return FAIL end if;
    r := rand(1 .. p - 1)();
    g := subs(_Z = y, op(R));
    A := Resultant(subs(x = x - r*y, R = y, f), g, y) mod p;
    if 0 = Discrim(A, x) mod p then return FAIL end if;
    A := RootOf(A) mod p;
    q := p^degree(g, y);
    AA := A;
    S := A;
    for i to n - 1 do AA := Power(AA, q) mod p; S := S + AA end do;
    i := Expand((S + coeff(f, x, n - 1)/coeff(f, x, n))/(n*r)) mod p;
    [(A - r*i) mod p], R = i
end proc:

#savelib('`mod/ReduceField`',\
	 '`mod/ReduceField/sort_B`',\
	 '`mod/ReduceField/roots_field`',\
	 '`mod/ReduceField/generator`'):
