SetVerbose("RoundFour", 2);
SetEchoInput(true);

function MultPolFacts(f) 
//Multiplies the factors with multiplicities
    if #f eq 0 then
	return Universe(f)[1]!1;
    end if;

    return &*[T[1]^(T[2]) : T in f];
end function;

procedure check_fact(f)
F, c := Factorization(f);
g := c*MultPolFacts(F) - f;
assert IsZero(g) or Maximum([RelativePrecision(c) : c in Coefficients(g)]) eq 0;
end procedure;

k<t>:= PowerSeriesRing(GF(2),50);
P<x>:= PolynomialRing(k);
f:=x^6+t*x+t;
Factorization(f:Extensions:=true);
check_fact(f);

k<t>:= LaurentSeriesRing(GF(2),50);
P<x>:= PolynomialRing(k);
f:=x^6+t*x+t;
check_fact(f);

k<t>:= LaurentSeriesRing(GF(3),150);
P<x>:= PolynomialRing(k);
f:=x^3+t^2*x^2+t^4*x;
check_fact(f);

k<t>:= LaurentSeriesRing(GF(2^52),50);
P<x>:= PolynomialRing(k);
f:=x^52 + x^51 + x^50 + x^49 + x^48 + x^47 + x^46 + x^45 + x^44 + x^43 + 
x^42 + x^41 + x^40 + x^39 + x^38 + x^37 + x^36 + x^35 + x^34 + x^33 + 
x^32 + x^31 + x^30 + x^29 + x^28 + x^27 + x^26 + x^25 + x^24 + x^23 + 
x^22 + x^21 + x^20 + x^19 + x^18 + x^17 + x^16 + x^15 + x^14 + x^13 + 
x^12 + x^11 + x^10 + x^9 + x^8 + x^7 + x^6 + x^5 + x^4 + x^3 + x^2 + x + 
1 + t + t^2 + t^3;
g:=Evaluate(f,x^2+x+1);
time Fact,e,Ext:=Factorization(g:Extensions);
assert MultPolFacts(Fact) eq g;

k<t>:= LaurentSeriesRing(GF(2^52),50);
P<x>:= PolynomialRing(k);
f:=x^52 + x^51 + x^50 + x^49 + x^48 + x^47 + x^46 + x^45 + x^44 + x^43 +
x^42 + x^41 + x^40 + x^39 + x^38 + x^37 + x^36 + x^35 + x^34 + x^33 + x^32 +
x^31 + x^30 + x^29 + x^28 + x^27 + x^26 + x^25 + x^24 + x^23 + x^22 + x^21 +
x^20 + x^19 + x^18 + x^17 + x^16 + x^15 + x^14 + x^13 + x^12 + x^11 + x^10 +
x^9 + x^8 + x^7 + x^6 + x^5 + x^4 + x^3 + x^2 + x + 1 + t + t^2 + t^3;
time Fact,e,Ext:=Factorization(f:Certificates);
assert MultPolFacts(Fact) eq f;

k<t>:= LaurentSeriesRing(GF(5),100);
P<x>:= PolynomialRing(k);
f:=x^35+(t^3+t^2+t)*x+t^3+t^2+t+1;
Factorization(f:Extensions);
check_fact(f);

k<t>:= LaurentSeriesRing(GF(5),100);
P<x>:= PolynomialRing(k);
f:=x^20+(t^3+t^2+t)*x+t^3+t^2+t+1;
Factorization(f:Extensions);
check_fact(f);

k<t>:= LaurentSeriesRing(GF(2),50);
P<x>:= PolynomialRing(k);
f:=x^4+t*x^2+t^6*x+t^2;
Factorization(f:Extensions:=true);
check_fact(f);

k<t>:= LaurentSeriesRing(GF(2),20);
P<x>:= PolynomialRing(k);
f:=Polynomial([t, -t, 1]);//x^2- t*x +t;
K<u1>:=TotallyRamifiedExtension(k, f);

k<t>:= LaurentSeriesRing(GF(2), 50);
P<x>:= PolynomialRing(k);
f := x^2-t;
Factorization(f);
T<u>:=TotallyRamifiedExtension(k,f);
P<x>:= PolynomialRing(T);
ff:=P!f;
Fact:=Factorization(ff);
MultPolFacts(Fact)-ff;

k<t>:= LaurentSeriesRing(GF(2), 50);
P<x>:= PolynomialRing(k);
f := x^2-t + 1;
Factorization(f);
T<u>:=TotallyRamifiedExtension(k,x^2-t);
P<x>:= PolynomialRing(T);
ff:=P!f;
Fact:=Factorization(ff);
MultPolFacts(Fact)-ff;

k<t>:= LaurentSeriesRing(GF(2), 50);
P<x>:= PolynomialRing(k);
f := x^4-t;
Factorization(f);
T<u>:=TotallyRamifiedExtension(k,x^2-t);
P<x>:= PolynomialRing(T);
ff:=P!f;
Fact:=Factorization(ff);
Fact;
MultPolFacts(Fact)-ff;

P<x>:= PolynomialRing(k);
T<u>:=TotallyRamifiedExtension(k,x^2-t+ t^2);
P<x>:= PolynomialRing(T);
ff:=P!f;
Fact:=Factorization(ff);
Fact;
MultPolFacts(Fact) - ff;


k<t>:= LaurentSeriesRing(GF(2),200);
P<x>:= PolynomialRing(k);
f:=x^4-t*x^2-t;
T<u>:=TotallyRamifiedExtension(k,f);
P<x>:= PolynomialRing(T);
ff:=P!f;
//Fact:=Factorization(ff);		// far too long
//assert MultPolFacts(Fact) eq ff;

k<t>:= LaurentSeriesRing(GF(2),200);
P<x>:= PolynomialRing(k);
f:=x^4-t*x^2-t;
T<u>:=TotallyRamifiedExtension(k,f:MaxPrecision:=100000*Precision(k));
P<x>:= PolynomialRing(T);
ff:=P!f;
//Fact:=Factorization(ff);		// far too long
//assert MultPolFacts(Fact) eq ff;

function build_coeff_seqs(coeffs)
if Type(Universe(coeffs)) in {RngSerExt} then
return [build_coeff_seqs(Eltseq(c)) : c in coeffs];
end if;
return [[0 : i in [1 .. Minimum(Valuation(c), Precision(Parent(c)))]] cat
Eltseq(c) : c in coeffs];
end function;

function increase_precision(f, R)
// increase the precision of the coefficients of f so they have the full
// precision of R and return as a polynomial over R
if Type(R) in {RngPadRes, RngPadResExt, RngPad, FldPad} then
return Polynomial(R, f);
end if;
coeffs := Coefficients(f);
coeffs := build_coeff_seqs(coeffs);
return Polynomial(R, ChangeUniverse(coeffs, R));
end function;

k<t>:= LaurentSeriesRing(GF(2),200);
P<x>:= PolynomialRing(k);
f:=x^4-t*x^2+t^3*x-t;
T<u>:=TotallyRamifiedExtension(k,increase_precision(f,k):MaxPrecision:=900*
Precision(k));
P<x>:= PolynomialRing(T);
ff:=P!f;
check_fact(ff);


P<t> := PowerSeriesRing(GF(101), 50);
R<x> := PolynomialRing(P);
f := Factorization((x^2 + 2)*(x - 1));
assert MultPolFacts(f) eq (x^2 + 2)*(x - 1);
Factorization((x^2 + 2)*(x - 1) : Certificates);
Factorization((x^2 + 2)*(x - 1) : Certificates, Extensions);
f := Factorization((x - t)*(x + t));
assert IsZero(MultPolFacts(f) - (x^2 - t^2));
Factorization((x - t)*(x + t) : Extensions);
f := Factorization(x^5 + t*x^4 - t^2*x^3 + (1 + t^20)*x^2 + t*x + t^6 : 
Extensions);
assert IsZero(MultPolFacts(f) - (x^5 + t*x^4 - t^2*x^3 + (1 + t^20)*x^2 + t*x + t^6));
f := Factorization(x^2 + t*x + t : Extensions);
assert MultPolFacts(f) eq x^2 + t*x + t;

U<u> := UnramifiedExtension(P, x^2 + 2);
Y<y> := PolynomialRing(U);
f := Factorization((y - 1)*(y - 3));
assert MultPolFacts(f) eq (y - 1)*(y - 3);
Factorization((y - 1)*(y - 3) : Certificates, Extensions);
f := Factorization((y - u)*(y^3 + 3));
assert MultPolFacts(f) eq (y - u)*(y^3 + 3);
f := Factorization((y^4 + 3) : Certificates, Extensions);
assert MultPolFacts(f) eq y^4 + 3;
f := Factorization((y^4 + u) : Certificates, Extensions);
assert MultPolFacts(f) eq y^4 + u;
f := Factorization(y^2 + t*y + t : Certificates, Extensions);
assert MultPolFacts(f) eq y^2 + t*y + t;
f := Factorization(y^6 + t*y^4 + (1 + t)*y^3 + t^5*y^2 + t : Certificates, 
Extensions);
assert MultPolFacts(f) eq y^6 + t*y^4 + (1 + t)*y^3 + t^5*y^2 + t;

P<t> := PowerSeriesRing(GF(101), 50);
PP<pt> := PowerSeriesRing(GF(101));
R<x> := PolynomialRing(PP);
T<tt> := TotallyRamifiedExtension(P, x^2 + pt*x + pt : MaxPrecision := 100);
Y<y> := PolynomialRing(T);
f := Factorization((y - 1)*(y - 2));
assert MultPolFacts(f) eq (y - 1)*(y - 2);
f := Factorization((y - 1)*(y - t));
assert MultPolFacts(f) eq (y - 1)*(y - t);
f := Factorization((y - 1)*(y - T.1));
assert MultPolFacts(f) eq (y - 1)*(y - T.1);
f := Factorization(y^2 + t*y + t);
assert MultPolFacts(f) eq y^2 + t*y + t;
f := Factorization(y^5 + (tt + t)*y^4 + t*tt*y^3 + t*y^2 + tt*y + tt^10 : 
Certificates);
assert IsZero(MultPolFacts(f) - (y^5 + (tt + t)*y^4 + t*tt*y^3 + t*y^2 + tt*y + tt^10));
Factorization(y^5 + (tt + t)*y^4 + t*tt*y^3 + t*y^2 + tt*y + tt^10 : 
Extensions);

Y<y> := PolynomialRing(U);
T<tt> := TotallyRamifiedExtension(U, y^2 + t*y + t : MaxPrecision := 200);
Y<y> := PolynomialRing(T);
f := Factorization((y - 1)*(y - 2));
assert MultPolFacts(f) eq (y - 1)*(y - 2);
f := Factorization((y - 1)*(y - t));
assert MultPolFacts(f) eq (y - 1)*(y - t);
f := Factorization((y - 1)*(y - tt));
assert MultPolFacts(f) eq (y - 1)*(y - tt);
f := Factorization((y - u)*(y - tt));
assert MultPolFacts(f) eq (y - u)*(y - tt);
f := Factorization(y^2 + t*y + t);
assert MultPolFacts(f) eq y^2 + t*y + t;
f := Factorization(y^5 + (tt + t + u)*y^4 + t*tt*u*y^3 + t*y^2 + tt^10*y + u^10 : 
Certificates);
assert MultPolFacts(f) eq y^5 + (tt + t + u)*y^4 + t*tt*u*y^3 + t*y^2 + tt^10*y + u^10;
Factorization(y^5 + (tt + t + u)*y^4 + t*tt*u*y^3 + t*y^2 + tt^10*y + u^10 : 
Extensions);

//Test inseparable polynomials
k<a> := GF(4);
K<t> :=  PowerSeriesRing(k, 300);
R<x> := PolynomialRing(K);
f := (1 + t^2)*x^2 + 1;
g := (1 + t^3)*x^2 + 1;
h := (t^2 + t^3 + t^4 + t^6 + t^7 + t^8)*x^4 + (t^3 + t^4 + t^5 + t^6 + t^7 +
t^8)*x^3 + (1 + t + t^2 + t^3 + t^4 + t^5)*x^2 + t^3*x + 1;
H, c := Factorization(h);
assert IsZero(c*MultPolFacts(H) - h);
Factorization(h : Extensions);
F, c := Factorization(f);
assert IsZero(c*MultPolFacts(F) - f);
Factorization(f : Extensions);
G, c := Factorization(g);
assert IsZero(c*MultPolFacts(G) - g);
//Factorization(g : Extensions);
FG, c := Factorization(f*g);
assert IsZero(c*MultPolFacts(FG) - f*g);
//Factorization(f*g : Extensions);
assert MultPolFacts(Factorization((x^2 + 1)*(x^2 + x+ 1))) - (x^2 + 1)*(x^2 + x+ 1) eq 0;
Factorization((x^2 + 1)*(x^2 + x+ 1) : Extensions);
assert MultPolFacts(Factorization((x^2 + 1)^2*(x^2 + x+ 1))) - (x^2 + 1)^2*(x^2 + x+ 1) eq 0;
assert MultPolFacts(Factorization((x^2 + 1)^3*(x^2 + x+ 1))) - (x^2 + 1)^3*(x^2 + x+ 1) eq 0;
assert MultPolFacts(Factorization((x^2 + 1)*(x^2 + x + 1)^2)) - (x^2 + 1)*(x^2 + x + 1)^2 eq 0;
assert MultPolFacts(Factorization((x^2 + 1)*(x^2 + x + 1)^3)) - (x^2 + 1)*(x^2 + x + 1)^3 eq 0;
assert MultPolFacts(Factorization((x^2 + 1)^5*(x^2 + x + 1)^3)) - (x^2 + 1)^5*(x^2 + x + 1)^3 eq 0;

assert MultPolFacts(Factorization((x^2 + t)*(x^2 + t^2))) - (x^2 + t)*(x^2 + t^2) eq 0;
//Factorization((x^2 + t)*(x^2 + t^2) : Extensions);

U := UnramifiedExtension(K, x^2 + (t + a)*x + 1);
T := TotallyRamifiedExtension(U, x^2 + t^2*x + t);
R<x> := PolynomialRing(T);


k<a> := GF(3);
K<t> := PowerSeriesRing(k, 100);
R<x> := PolynomialRing(K);

assert MultPolFacts(Factorization((x^3 + t)*(x^3 + t^3))) - (x^3 + t)*(x^3 + t^3) eq 0;
//Factorization((x^3 + t)*(x^3 + t^3) : Extensions);

k<t>:= PowerSeriesRing(GF(2),50);
P<x>:= PolynomialRing(k);
f:=x^6+t*x+t;
assert MultPolFacts(Factorization(f:Extensions:=true)) - f eq 0;

k<t>:= LaurentSeriesRing(GF(2),50);
P<x>:= PolynomialRing(k);
f:=x^6+t*x+t;
assert Maximum([RelativePrecision(c) : c in Coefficients(MultPolFacts(Factorization(f:Extensions:=true)) - f)]) eq 0;


time for p in [RandomPrime(i) : i in [3 .. 7]] do
p;
k<t>:= PowerSeriesRing(GF(p),50);
P<x>:= PolynomialRing(k);
f:=x^6+t*x+t;
try
assert MultPolFacts(Factorization(f:Extensions:=true)) - f eq 0;
catch e
assert MultPolFacts(Factorization(f)) - f eq 0;
end try;
f:=x^6+t*x+t+k![Random(CoefficientRing(k)) : i in [1 .. Random(3, 10)]];
try
assert MultPolFacts(Factorization(f:Extensions:=true)) - f eq 0;
catch e
assert MultPolFacts(Factorization(f)) - f eq 0;
end try;
f:=x^6+(t + k![Random(CoefficientRing(k)) : i in [1 .. Random(3, 10)]])*x+t+k![Random(CoefficientRing(k)) : i in [1 .. Random(3, 10)]];
try
assert MultPolFacts(Factorization(f:Extensions:=true)) - f eq 0;
catch e
assert MultPolFacts(Factorization(f)) - f eq 0;
end try;
end for;

k<t>:= LaurentSeriesRing(GF(2),200);
P<x>:= PolynomialRing(k);
f:=x^4-t*x^2-t;
T<u>:=TotallyRamifiedExtension(k,f);

assert not IsPower(t, 2);
_, s := IsPower(T!t, 2);
assert IsWeaklyZero(s^2-t);
_, s := IsPower(t/u^4, 2);
assert IsWeaklyZero(s^2 - t/u^4);


for i in [1 .. 5] do
    s := k![Random(GF(2)) : j in [1 .. 20]];
    b, ss := IsPower(s, 2);
    if b then
	assert IsWeaklyZero(ss^2 - s);
    end if;
    b, ss := IsPower(s^2, 2);
    assert b;
    assert IsWeaklyZero(ss - s);

    s := T![k![Random(GF(2)) : j in [1 .. 20]] : m in [1 .. Degree(DefiningPolynomial(T))]];
    b, ss := IsPower(s, 2);
    if b then
	assert IsWeaklyZero(ss^2 - s);
    end if;
    b, ss := IsPower(s^2, 2);
    assert b;
    assert RelativePrecision(ss-s) eq 0 or Valuation(ss - s) ge Precision(T)/2;
end for;

k<t>:= LaurentSeriesRing(GF(4),200);
P<x>:= PolynomialRing(k);
f:=x^4-t*x^2-t;
T<u>:=TotallyRamifiedExtension(k,f);

assert not IsPower(t, 2);
_, s := IsPower(T!t, 2);
assert IsWeaklyZero(s^2-t);
_, s := IsPower(t/u^4, 2);
assert IsWeaklyZero(s^2 - t/u^4);


for i in [1 .. 5] do
    s := k![Random(GF(2)) : j in [1 .. 20]];
    b, ss := IsPower(s, 2);
    if b then
	assert IsWeaklyZero(ss^2 - s);
    end if;
    b, ss := IsPower(s^2, 2);
    assert b;
    assert IsWeaklyZero(ss - s);

    s := T![k![Random(GF(2)) : j in [1 .. 20]] : m in [1 .. Degree(DefiningPolynomial(T))]];
    b, ss := IsPower(s, 2);
    if b then
	assert IsWeaklyZero(ss^2 - s);
    end if;
    b, ss := IsPower(s^2, 2);
    assert b;
    assert RelativePrecision(ss-s) eq 0 or Valuation(ss - s) ge Precision(T)/2;
end for;

for p in [2, 3, 5, 7] do
k<t>:= LaurentSeriesRing(GF(p),50);
P<x>:= PolynomialRing(k);
f:=x^(p^2)-t*x^p-t;
T<u>:=TotallyRamifiedExtension(k,f);

assert not IsPower(t, p);
_, s := IsPower(T!t, p);
assert IsWeaklyZero(s^p-t);
_, s := IsPower(t/u^(p^2), p);
assert IsWeaklyZero(s^p - t/u^(p^2));


for i in [1 .. 2] do
    s := k![Random(GF(p)) : j in [1 .. 20]];
    b, ss := IsPower(s, p);
    if b then
	assert IsWeaklyZero(ss^p - s);
    end if;
    b, ss := IsPower(s^p, p);
    assert b;
    assert IsWeaklyZero(ss - s);

    s := T![k![Random(GF(p)) : j in [1 .. 20]] : m in [1 .. Degree(DefiningPolynomial(T))]];
    b, ss := IsPower(s, p);
    if b then
	assert IsWeaklyZero(ss^p - s);
    end if;
    b, ss := IsPower(s^p, p);
    assert b;
    assert RelativePrecision(ss-s) eq 0 or Valuation(ss - s) ge Precision(T)/p;
end for;

k<t>:= LaurentSeriesRing(GF(p^2),50);
P<x>:= PolynomialRing(k);
f:=x^(p^2)-t*x^p-t;
T<u>:=TotallyRamifiedExtension(k,f);

assert not IsPower(t, p);
_, s := IsPower(T!t, p);
assert IsWeaklyZero(s^p-t);
_, s := IsPower(t/u^(p^2), p);
assert IsWeaklyZero(s^p - t/u^(p^2));


for i in [1 .. 2] do
    s := k![Random(GF(p)) : j in [1 .. 20]];
    b, ss := IsPower(s, p);
    if b then
	assert IsWeaklyZero(ss^p - s);
    end if;
    b, ss := IsPower(s^p, p);
    assert b;
    assert IsWeaklyZero(ss - s);

    s := T![k![Random(GF(p)) : j in [1 .. 20]] : m in [1 .. Degree(DefiningPolynomial(T))]];
    b, ss := IsPower(s, p);
    if b then
	assert IsWeaklyZero(ss^p - s);
    end if;
    b, ss := IsPower(s^p, p);
    assert b;
    assert RelativePrecision(ss-s) eq 0 or Valuation(ss - s) ge Precision(T)/p;
end for;
end for;
