/*
Basic coding theory test.
AKS April 2008.
*/

SetShowRealTime(true);
RT := Realtime();

SetVerbose("Code", 1);

/******************************************************************************/

LARGE := false;

NTHREADS := 6;
NTHREADS := 12;

/******************************************************************************/

function rand_nz(K)
    repeat r := Random(K); until r ne 0;
    return r;
end function;

/******************************************************************************/

procedure test_code(C: GMW := "undef", GWD := "undef", Dump := false)

//return;
    procedure test_given(G, V)
	if G cmpne "undef" then
	    assert G eq V;
	end if;
    end procedure;

    if GMW cmpeq "undef" and GWD cmpne "undef" then
	GMW := GWD[2, 1];
    end if;

    printf "%o%o\n",
	Sprint(C, "Minimal"), IsCyclic(C) select ", cyclic" else "";
    IndentPush();

    n := Length(C);
    k := Dimension(C);
    K := BaseRing(C);
    p := Characteristic(K);

    if NTHREADS gt 1 then
	perm := Random(Sym(n));
	CC := sub<Generic(C) | Rowspace(BasisMatrix(C^perm))>;
    end if;

    z := Cputime();
    MW := MinimumWeight(C);
    printf "MW: %o (%os)\n", MW, Cputime(z);
    test_given(GMW, MW);

    //SetVerbose("Code",1);
    if NTHREADS gt 1 then
	z := Realtime();
	assert MinimumWeight(CC: Nthreads := NTHREADS) eq MW;
	printf "MW threads: %o (%ors)\n", MW, Realtime(z);
    end if;

WD_LIMIT := p eq 2 select 2^45 else 2^45;
    if Min(#C, #Dual(C)) le WD_LIMIT then
	z := Cputime();
	WD := WeightDistribution(C);
	printf "WD: %o (%os)\n", WD[1..3], Cputime(z);
	test_given(GWD, WD);

	if NTHREADS gt 1 then
	    z := Realtime();
	    assert WeightDistribution(CC: Nthreads := NTHREADS) eq WD;
	    printf "WD threads: %o (%ors)\n", WD[1..3], Realtime(z);
	end if;

	if Dump then WD; end if;

	assert WD[1] eq <0, 1>;
	assert WD[2, 1] eq MW;

	WD[2, 2], "minimum word(s)";
MW_LIMIT := p eq 2 select 2^42 else 2^42;
	//if WD[2, 2] le MW_LIMIT then
	if #C le MW_LIMIT then
	    z := Cputime();
	    W := MinimumWords(C);
	    printf "MinimumWords: %o (%os)\n", #W, Cputime(z);
if #W ne WD[2, 2] then "BAD"; C: Magma; end if;
	    assert #W eq WD[2, 2];
	    assert forall{v: v in W | Weight(v) eq MW};

	    if NTHREADS gt 1 then
		z := Realtime();
//SetVerbose("Code",1);
		WW := MinimumWords(CC: Nthreads := NTHREADS);
SetVerbose("Code",0);
		printf "MinimumWords threads: %o (%ors)\n", #WW, Realtime(z);
		if #WW ne #W then
		    error "*** FAIL";
		end if;
//"#WW:", #WW;
		if #WW le 10^5 then
		    if WW ne {v^perm: v in W} then "*** FAIL"; end if;
		    //assert WW eq {v^perm: v in W};
		end if;
	    end if;
	end if;
    end if;
    IndentPop();

end procedure;

for q in [q: q in [2 .. 17] | IsPrimePower(q)] do
//for q in [11] do

    "*** q:", q;
    IndentPush();

    repeat
	n := Random(15, 25);
    until GCD(n, q) eq 1;
    // test_code(BCHCode(GF(q), n, n div 4));
    test_code(HammingCode(GF(q), 3));

    if q eq 2 then
	range := [40 .. 60 by 10];
	if LARGE then range := [40 .. 80 by 10]; end if;
    else
	m := Floor(Log(q, 2^60));
	if LARGE then m := Floor(Log(q, 2^100)); end if;
	range := [m];
    end if;
    //for n in q le 2 select [40 .. 60 by 10] else [20, 30] do
    for n in range do
	k := n div 2;

	printf "RandomLinearCode(GF(%o), %o, %o)\n", q, n, k;
	C := RandomLinearCode(GF(q), n, k);
	test_code(C);

	if q lt 11 then
	    printf "BestKnownLinearCode(GF(%o), %o, %o)\n", q, n, k;
	    C := BestKnownLinearCode(GF(q), n, k);
	    perm := Random(Sym(n));
	    C := sub<Generic(C) | Rowspace(BasisMatrix(C^perm))>;
	    test_code(C);
	end if;
    end for;

    IndentPop();
end for;

////

SetNthreads(1);

C1 := ExtendCode(BCHCode(GF(2), 63, 7));
C2 := ExtendCode(BCHCode(GF(2), 63, 9));
C3 := ExtendCode(BCHCode(GF(2), 63, 11));
CC := SubcodeBetweenCode(C1, C2, 43);
CX3 := ConstructionX3(CC, C2, C3, BKLC(GF(2), 7, 4), BKLC(GF(2), 3, 3));
test_code(CX3);

////

RM_WD := 
[
    [ <0, 1>, <2, 6>, <4, 1> ],
    [ <0, 1>, <4, 14>, <8, 1> ],
    [ <0, 1>, <2, 28>, <4, 70>, <6, 28>, <8, 1> ],
    [ <0, 1>, <8, 30>, <16, 1> ],
    [ <0, 1>, <4, 140>, <6, 448>, <8, 870>, <10, 448>, <12, 140>, <16, 1> ],
    [ <0, 1>, <2, 120>, <4, 1820>, <6, 8008>, <8, 12870>, <10, 8008>, <12, 
    1820>, <14, 120>, <16, 1> ],
    [ <0, 1>, <16, 62>, <32, 1> ],
    [ <0, 1>, <8, 620>, <12, 13888>, <16, 36518>, <20, 13888>, <24, 620>, <32, 
    1> ],
    [ <0, 1>, <4, 1240>, <6, 27776>, <8, 330460>, <10, 2011776>, <12, 7063784>, 
    <14, 14721280>, <16, 18796230>, <18, 14721280>, <20, 7063784>, <22, 
    2011776>, <24, 330460>, <26, 27776>, <28, 1240>, <32, 1> ],
    [ <0, 1>, <2, 496>, <4, 35960>, <6, 906192>, <8, 10518300>, <10, 64512240>, 
    <12, 225792840>, <14, 471435600>, <16, 601080390>, <18, 471435600>, <20, 
    225792840>, <22, 64512240>, <24, 10518300>, <26, 906192>, <28, 35960>, <30, 
    496>, <32, 1> ],
    [ <0, 1>, <32, 126>, <64, 1> ],
    [ <0, 1>, <16, 2604>, <24, 291648>, <28, 888832>, <32, 1828134>, <36, 
    888832>, <40, 291648>, <48, 2604>, <64, 1> ],
    [ <0, 1>, <8, 11160>, <12, 1749888>, <14, 22855680>, <16, 232081500>, <18, 
    1717223424>, <20, 9366150528>, <22, 38269550592>, <24, 119637587496>, <26, 
    286573658112>, <28, 533982211840>, <30, 771854598144>, <32, 874731154374>, 
    <34, 771854598144>, <36, 533982211840>, <38, 286573658112>, <40, 
    119637587496>, <42, 38269550592>, <44, 9366150528>, <46, 1717223424>, <48, 
    232081500>, <50, 22855680>, <52, 1749888>, <56, 11160>, <64, 1> ],
    [ <0, 1>, <4, 10416>, <6, 1166592>, <8, 69194232>, <10, 2366570752>, <12, 
    51316746768>, <14, 747741998592>, <16, 7633243745820>, <18, 56276359749120>,
    <20, 306558278858160>, <22, 1255428754917120>, <24, 3916392495228360>, <26, 
    9399341113166592>, <28, 17480786291963792>, <30, 25316999607653376>, <32, 
    28634752793916486>, <34, 25316999607653376>, <36, 17480786291963792>, <38, 
    9399341113166592>, <40, 3916392495228360>, <42, 1255428754917120>, <44, 
    306558278858160>, <46, 56276359749120>, <48, 7633243745820>, <50, 
    747741998592>, <52, 51316746768>, <54, 2366570752>, <56, 69194232>, <58, 
    1166592>, <60, 10416>, <64, 1> ],
    [ <0, 1>, <2, 2016>, <4, 635376>, <6, 74974368>, <8, 4426165368>, <10, 
    151473214816>, <12, 3284214703056>, <14, 47855699958816>, <16, 
    488526937079580>, <18, 3601688791018080>, <20, 19619725782651120>, <22, 
    80347448443237920>, <24, 250649105469666120>, <26, 601557853127198688>, <28,
    1118770292985239888>, <30, 1620288010530347424>, <32, 1832624140942590534>, 
    <34, 1620288010530347424>, <36, 1118770292985239888>, <38, 
    601557853127198688>, <40, 250649105469666120>, <42, 80347448443237920>, <44,
    19619725782651120>, <46, 3601688791018080>, <48, 488526937079580>, <50, 
    47855699958816>, <52, 3284214703056>, <54, 151473214816>, <56, 4426165368>, 
    <58, 74974368>, <60, 635376>, <62, 2016>, <64, 1> ]
];

i := 1;
for m := 1 to 6 do
    for r := 1 to m - 1 do
	C := ReedMullerCode(r, m);
	test_code(ReedMullerCode(r, m): GWD := RM_WD[i]);
	i +:= 1;
	//Append(~q, WeightDistribution(C));
    end for;
end for;

/******************************************************************************/

SetVerbose("Code", 0);

procedure decode_test(C, t: I := 0, N := 10)
    printf "decode test: t = %o, C: ", t; C: Minimal;

    n := Length(C);
    K := BaseRing(C);

    for i := 1 to N do
	if I gt 0 then
	    S := {I};
	else
	    S := {};
	end if;
	while #S lt t do
	    Include(~S, Random(1, n));
	end while;

	v := Random(C);
	ve := v;
	for j in S do
	    r := rand_nz(K);
	    ve[j] +:= r;
	end for;
//"S:", S; "v:", v; "ve:", ve; "diff:", v-ve;
	assert Weight(v - ve) eq t;

	l, w := EuclideanDecoding(C, ve);
//"Got w:", w;
	assert l;
	assert w eq v;
    end for;
end procedure;

procedure alternant_test(q, r: I := 0)

    K<w> := GF(q);

    if I eq 0 then
	A := [w^i : i in [0 .. q-1]];
    else
	A0 := [w^i : i in [0 .. q-2]]; // w is primitive
	A0 := Eltseq(Vector(A0)^Random(Sym(#A0)));
	A := [A0[i]: i in [1 .. I - 1]] cat [0] cat [A0[i]: i in [I .. #A0]];
    end if;
//"A:", A;

    Y := [rand_nz(K): i in [0 .. q-1]];

    C := AlternantCode(A, Y, r);
    decode_test(C, r div 2: I := I);

end procedure;

alternant_test(2^4, 2: I := 1);
alternant_test(2^4, 4: I := 1);
alternant_test(2^5, 4: I := 1);
alternant_test(2^6, 6: I := 1);
alternant_test(5^2, 4: I := 1);
alternant_test(3^3, 4: I := 1);
alternant_test(3^4, 4: I := 1);
alternant_test(3^4, 6: I := 1);


decode_test(BCHCode(GF(3), 13, 3), 1);
decode_test(BCHCode(GF(2), 31, 7), 3);


//error "done";

/******************************************************************************/

C := BCHCode(GF(3), 13, 3);
v := Random(C);
b, w := EuclideanDecoding(C, v);
assert b;
assert w eq v;

/******************************************************************************/

function APNCode(K, f)
    n := Degree(K, GF(2));
    w := PrimitiveElement(K);
    GBase := [w^i : i in [0 .. n-1]];
    CL := [1: x in K] cat
	[Trace(x*(GBase[i])): x in K, i in [1..n]] cat
	[Trace(f(x)*(GBase[i])): x in K, i in [1..n]];
    CM := Matrix(2*n+1, 2^n, CL);
    return LinearCode(CM);
end function;

base := 4;
APN_WD := [
    [ <0, 1>, <4, 20>, <6, 160>, <8, 150>, <10, 160>, <12, 20>, <16, 1> ],
    [ <0, 1>, <12, 496>, <16, 1054>, <20, 496>, <32, 1> ],
    [ <0, 1>, <24, 336>, <28, 2688>, <32, 2142>, <36, 2688>, <40, 336>,
	<64, 1> ],
    [ <0, 1>, <56, 8128>, <64, 16510>, <72, 8128>, <128, 1> ],
    [ <0, 1>, <112, 5440>, <120, 43520>, <128, 33150>, <136, 43520>,
	<144, 5440>, <256, 1> ],
    [ <0, 1>, <240, 130816>, <256, 262654>, <272, 130816>, <512, 1> ],
    [ <0, 1>, <480, 87296>, <496, 698368>, <512, 525822>, <528, 698368>, <544, 
	87296>, <1024, 1> ],
    [ <0, 1>, <992, 2096128>, <1024, 4196350>, <1056, 2096128>, <2048, 1> ],
    [ <0, 1>, <1984, 1397760>, <2016, 11182080>, <2048, 8394750>,
	<2080, 11182080>, <2112, 1397760>, <4096, 1> ]
];

"\nAPN";
IndentPush();
for n := base to #APN_WD + base - 1 do
    test_code(APNCode(GF(2^n), func<x | x^3>): GWD := APN_WD[n - (base - 1)]);
end for;
IndentPop();

/******************************************************************************/

// Regression tests for coding theory bugs
Z4 := Integers(4);

// Markus Grassl:
c := LinearCode<Z4, 4 | [ 0, 0, 1, 1 ] >;
w := MinimumLeeWeight(c);	// crashes prior to 9482
assert w eq 2;

// Markus Grassl:
c := LinearCode<Z4, 5 | [2, 2, 2, 2, 2 ]>;
ok,bound,exact := VerifyMinimumLeeWeightLowerBound(c, 10);
// Should return true 10 true, returns false 1 false prior to 9482
assert ok;
assert bound eq 10;
assert exact;

// This next one only failed on 32-bit machines, and takes to long to
// run by default. :(
/*
seq := func<v,n | [ v : k in [1..n] ]>;
N := 32;
first := &cat[ seq(1,1), seq(0, N-1), seq(1,1), seq(0,N-1), seq(2,1) ];
middle := [ &cat[seq(0,k), seq(2,1), seq(0,N-1), seq(2,1), seq(0,N-k)]
	    : k in [1..N] ];
last := &cat[ seq(0,N+1), seq(2,N) ];
rows := [ first ] cat middle cat [ last ];
C := LinearCode<Z4, 2*N + 1 | rows>;
w := MinimumLeeWeight(C);	// would get -1 prior to 9448
assert w eq 2;
*/

gens:=[PolynomialRing(GF(5))|
[1,0,0,0,0,0,0,4,1,4,1,4],
[0,0,2,2,0,4,2,2,1,1,3,3],
[0,3,0,0,4,2,0,4,0,0,4,3],
[0,2,3,2,1,1,0,2,2,3,0,4],
[4,3,3,3,1,3,0,3,2,3,3,2],
[4,4,1,3,3,3,3,1,2,0,2,4],
[4,4,0,3,1,0,0,4,0,3,1],
[0,4,0,1,2,1,0,3,1,4,1,3]];
C:=QuasiCyclicCode(96,gens);
assert WeightDistribution(C) eq
[ <0, 1>, <58, 24>, <60, 48>, <61, 48>, <62, 312>, <63, 832>, <64, 1728>, <65, 
2880>, <66, 5232>, <67, 9792>, <68, 20124>, <69, 28704>, <70, 45192>, <71, 
64128>, <72, 88316>, <73, 116256>, <74, 148416>, <75, 173136>, <76, 191916>, 
<77, 199824>, <78, 192032>, <79, 177408>, <80, 148752>, <81, 118656>, <82, 
86160>, <83, 57504>, <84, 36936>, <85, 21360>, <86, 10176>, <87, 4448>, <88, 
1128>, <89, 1008>, <90, 456>, <91, 144>, <92, 48> ]
;
assert not IsMDS(C);

// Sendai, failed on Intel64-OSX 2.16-10 with crash on MinimumWeight()
Z2 := Integers(2);
C := CyclicCode(Vector(Z2,23,
[1,1,0,0,0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0]));
assert MinimumWeight(C) eq 7;

"Cpu time:", Cputime();
"Real time:", Realtime(RT);
