/**********************************************************
 * 
 * This is a benchmark helper file for testing 
 * local_square_classes.m It contains the testing function 
 * `runtests' that is loaded by the individual tests.
 * 
 * The function performs four speed-tests on the field 
 * given by a generating polynomial:
 * (1) Times the generation of a basis of a local dyadic
 *     square-class group. The field mus contain a SINGLE
 *     dyadic place. Since this times is most vulnerable
 *     to caching, it performs an additional consistence 
 *     check.
 * (2) Times the generation of a set of representatives
 *     of uniformly distributed random local square-classes.
 * (3) Time the computation of the coordinates (w.r.t the
 *     basis constructed in 1) of random nonzero elements 
 *     of the field.
 * 
 *********************************************************/

// size needed by Random function
SIZE := 20;

RR := RealField(7);

procedure runtests(f, Fdat, ~N1, ~M1, ~N2, ~N3)
// INPUT:
// f - a generating polynomial of a number field
// Fdat - file handler (the file must be opened prior to calling the function)
// N1 - half of the number of times the built-in function LocalMultiplicativeGroupModSquares
//      should be executed
// M1 - as above but for the new functions
// N2 - the number of uniformly distributed random square-class to create using the
//      BOTH method  (we use the same set of sample points)
// N3 - the number of random non-zero els, whose coordinates are to be computed
//      using the BOTH algorithms (we use the same set of sample points)
//
// The counters Ni and Mi are adjusted up/down by the function with the
// intention to keep the execution time of each single test between 1 and 4 second,
// if possible.


    // write to the file
    fprintf Fdat, "%o\t", Degree(f);
    
    ///////////////////////////////////////////////////////
    // CONSTRUCTION
    
    // built-in command
    printf "\n%o: ", Degree(f);
    consistent := false;
    while not consistent do
        // construct enough INDEPENDENT copies of the number field
        // to avoid caching we must use a list NOT a seq;
        P := [* *];
        for j in [1..8*N1] do
            K := NumberField(f);
            fct := PrimeFactorization(K, 2);
            assert IsOne(#fct);
            p := fct[1][1];
            Append(~P, p);
        end for;

        times1 := [ RR| 0, 0, 0, 0, 0, 0, 0, 0 ]; 
        for j in [0..7] do
            printf ".";
            t := Cputime();
            for p in P[ [j*N1+1..(j+1)*N1] ] do
                G, mG := LocalMultiplicativeGroupModSquares(p);
            end for;
            t :=  Cputime(t);
            times1[j + 1] := t;
        end for;
        // check consistency, to ensure that no caching occurred
        // we allow for max 30% difference
        consistent := Max(times1) lt 1.3*Min(times1);
        if not consistent then
            printf "\nConsistency test failed! Repeating.\n";
        end if;
    end while;
    t1 := &+ times1;
    
    // the new algorithm
    consistent := false;
    while not consistent do
        // we SHOULD construct the places in the same way
        // as in the previous test, but for some reasons
        // this consistently FAILS the consistency check!
        // successive test are slower and slower, 
        // it has probably something to do with Magma's
        // memory management, hence we do it another way
        K := NumberField(f);
        fct := PrimeFactorization(K, 2);
        assert IsOne(#fct);
        p := fct[1][1];
        Q := [* p : j in [1..8*M1] *];
        //     Q := [* *];
        //     for j in [1..2*M1] do
        //         K := NumberField(f);
        //         fct := PrimeFactorization(K, 2);
        //         assert IsOne(#fct);
        //         p := fct[1][1];
        //         Append(~Q, p);
        //     end for;
        
        times2 := [ RR| 0, 0, 0, 0, 0, 0, 0, 0 ]; 
        for j in [0..7] do
            printf ".";
            t := Cputime();
            for p in Q[ [j*M1+1..(j+1)*M1] ] do
                H, mH := LocalGroupOfSquaresClasses(p);
            end for;
            t :=  Cputime(t);
            times2[j + 1] := t;
        end for;
        // check consistency, to ensure that no caching occurred
        // we allow for max 30% difference
        consistent := Max(times1) lt 1.3*Min(times1);
        if not consistent then
            printf "\nConsistency test failed! Repeating.\n";
        end if;
    end while;
    u1 := &+ times2;
    
    // write to the file
    fprintf Fdat, "%o\t%o\t%o\t%o\t", (RR!t1)/(8*N1), N1, (RR!u1)/(8*M1), M1;

    // adjust N1 and M1 if needed
    if Min(times1) gt 4 and N1 gt 1 then N1 div:= 2; end if;
    if Max(times1) lt 1 then N1 *:= 2; end if;
    if Min(times2) gt 4 and M1 gt 1 then M1 div:= 2; end if;
    if Max(times2) lt 1 then M1 *:= 2; end if;
    
    
    ///////////////////////////////////////////////////////
    // UNIFORMLY DISTRIBUTED RANDOM SQ-CLASSES
    
    // we have to recompute G, otherwise Magma "thinks"
    // that the field has changed
    G, mG := LocalMultiplicativeGroupModSquares(p);
    
    // built-in method
    printf ".";
    samples := [ Random(G) : j in [1..N2] ];
    t2 := Cputime();
    _ := [ v @ mG : v in samples ];
    t2 := Cputime(t2);
    
    // the new algorithm
    printf ".";
    samples := [ H!Eltseq(v) : v in samples ];
    u2 := Cputime();
    _ := [ v @ mH : v in samples ];
    u2 := Cputime(u2);
    
    // write to the file
    fprintf Fdat, "%o\t%o\t%o\t", (RR!t2)/N2, (RR!u2)/N2, N2;
    
    // adjust N2 if needed
    if t2 gt 4 and u2 gt 4 and N2 gt 8 then N2 div:= 2; end if;
    if t2 lt 1 or u2 lt 1 then N2 *:= 2; end if;
    
    
    ///////////////////////////////////////////////////////
    // COORDINATES OF RANDOM ELS
    
    // built-in method
    printf ".";
    samples := [];
    while #samples lt N3 do
        a := Random(K, SIZE);
        if not IsZero(a) then Append(~samples, a); end if;
    end while;
    printf ".";
    t3 := Cputime();
    _ := [ v @@ mG : v in samples ];
    t3 := Cputime(t3);
    
    // the new algorithm
    printf ".";
    u3 := Cputime();
    _ := [ v @@ mH : v in samples ];
    u3 := Cputime(u3);
    
    // write to the file
    fprintf Fdat, "%o\t%o\t%o\n", (RR!t3)/N3, (RR!u3)/N3, N3;
    
    // adjust N3 if needed
    if t3 gt 4 and t3 gt 4 and N3 gt 8 then N3 div:= 2; end if;
    if t3 lt 1 or u3 lt 1 then N3 *:= 2; end if;
    
end procedure;
