/*
 * test4100.redc - test REDC operations for test 41dd
 *
 * Copyright (C) 1999,2023  Ernest Bowen and Landon Curt Noll
 *
 * Primary author:  Ernest Bowen
 *
 * Calc is open software; you can redistribute it and/or modify it under
 * the terms of the version 2.1 of the GNU Lesser General Public License
 * as published by the Free Software Foundation.
 *
 * Calc is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
 * Public License for more details.
 *
 * A copy of version 2.1 of the GNU Lesser General Public License is
 * distributed with calc under the filename COPYING-LGPL.  You should have
 * received a copy with calc; if not, write to Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Under source code control:   1996/03/13 03:53:22
 * File existed as early as:    1996
 *
 * Share and enjoy!  :-)        http://www.isthe.com/chongo/tech/comp/calc/
 */

/*
 * Some severe tests and timing functions for REDC functions and pmod.
 *
 * testall(str,n,N,M,verbose)
 *      performs n tests using arguments x, y, ...
 *      randomly selected from [-N, N) or when nonnegative values are
 *      required, [0, N), and m an odd positive integer in [1,N],
 *      and where a "small" (say less than 10000) exponent k is to be
 *      used (when computing x^k % m directly) k is random in [0,M).
 *      Default values for N and M are 1e20 and 100.
 *
 * times(str,N,n,verbose)
 *      gives times for n evaluations of rcin, etc. with
 *      N-word arguments; default n is ceil(K1/power(N,1.585).
 *
 * powtimes(str, N1,N2,n, verbose)
 *      gives times for n evaluations of pmod(x,k,m)
 *      for the three algorithms "small", "normal", "bignum" that
 *      pmod may use, and equivalent functions rcpow(xr,k,m) for
 *      "small" or "bignum" cases, where xr = rcin(x,m).  The
 *      modulus m is a random positive odd N1-word number; x has
 *      random integer values in [0, m-1]; k has random N2-word values.
 *      N2 defaults to 1; n defaults to ceil(K2/power(N1,1.585)/N2).
 *
 * inittimes(str, N, n, verbose)
 *      displays the times and tests n evaluations of rcin(x,m)
 *      and rcout(x,m) where m is a random positive odd N-word integer,
 *      x is a random integer in [0, m-1]; n defaults to ceil(K1/N^2).
 *
 * rlen_4100(N)
 *      generates a random positive N-word integer.  The global
 *      BASEB should be set to the word-size for the computer being
 *      used.  The parameters K1, K2 which control the default n
 *      should be adjusted to give reasonable runtimes.
 *
 * olen(N)
 *      generates a random odd positive N-word number.
 *
 */


defaultverbose = 1;     /* default verbose value */

/*
 * test defaults
 */
global test4100_K1 = 2^17;
global test4100_K2 = 2^12;
global test4100_BASE = 2^config("baseb");

define rlen_4100(N) = rand(test4100_BASE^(N-1), test4100_BASE^N);

define olen(N)
{
        local v;

        v = rlen_4100(N);
        if (iseven(v))
                v++;
        return v;
}

define test4101(x,y,m,k,z1,z2,verbose)
{
        local xr, yr, v, w, oneone;

        if (isnull(verbose))
                verbose = defaultverbose;
        xr = rcin(x,m);
        yr = rcin(y,m);
        oneone = rcin(rcin(1,m),m);

        if (xr >= m || xr < 0) {
                if (verbose > 1)
                        printf("Failure 1 for x = %d, m = %d\n", x, m);
                return 1;
        }
        if (rcin(x * y, m) != mod(xr * y, m, 0)) {
                if (verbose > 1) {
                        printf("Failure 2 for x = %d, y = %d, m = %d\n",
                                x, y, m);
                }
                return 2;
        }
        if (rcout(xr, m) != x % m) {
                if (verbose > 1)
                        printf("Failure 3 for x = %d, m = %d\n", x, m);
                return 3;
        }
        if (rcout(rcmul(xr,yr,m),m) != mod(x * y, m, 0)) {
                if (verbose > 1)
                        printf("Failure 4 for x = %d, y = %d, m = %d\n",
                                 x, y, m);
                return 4;
        }
        if (rcmul(x,yr,m) != mod(x * y, m, 0)) {
                if (verbose > 1)
                        printf("Failure 5 for x = %d, y = %d, m = %d\n",
                                 x, y, m);
                return 5;
        }
        if (rcin(rcmul(x,y,m),m) != mod(x * y, m, 0)) {
                if (verbose > 1)
                        printf("Failure 6 for x = %d, y = %d, m = %d\n",
                                x, y, m);
                return 6;
        }
        if (rcout(rcsq(xr,m),m) != mod(x^2, m, 0)) {
                if (verbose > 1)
                        printf("Failure 7 for x = %d, m = %d\n", x, m);
                return 7;
        }
        if (rcin(rcsq(x,m),m) != mod(x^2, m, 0)) {
                if (verbose > 1)
                        printf("Failure 8 for x = %d, m = %d\n",
                                x, y, m);
                return 8;
        }
        if (rcout(rcpow(xr,k,m),m) != mod(x^k, m, 0)) {
                if (verbose > 1)
                        printf("Failure 9 for x = %d, m = %d, k = %d\n",
                                x, m, k);
                return 9;
        }
        if (rcpow(x,k,m) != rcin(rcout(x,m)^k, m)) {
                if (verbose > 1)
                        printf("Failure 10: x = %d, k = %d, m = %d\n",
                                x, k, m);
                return 10;
        }
        if (rcpow(x, z1 * z2, m) != rcpow(rcpow(x,z1,m), z2, m)) {
                if (verbose > 1)
                        printf("Failure 11: x = %d, z1 = %d, z2 = %d, m = %d\n",
                        x, z1, z2, m);
                return 11;
        }
        if (xr != rcmul(oneone, x, m)) {
                if (verbose > 1)
                        printf("Failure 12: x = %d, m = %d\n", x, m);
                return 12;
        }

        return 0;
}

define testall(str,n,N,M,verbose)
{
        local i, p, x, y, z1, z2, c, k, m;

        if (isnull(verbose))
                verbose = defaultverbose;
        if (verbose > 0) {
                print str:":",:;
        }
        m = 0;
        if (isnull(N))
                N = 1e20;
        if (isnull(M))
                M = 100;
        c = 0;
        for (i = 0; i < n; i++) {
                x = rand(-N, N);
                y = rand(-N, N);
                z1 = rand(N);
                z2 = rand(N);
                c = rand(N);
                if (iseven(c))
                        c++;
                k = rand(M);
                if (verbose > 1)
                        printf("x = %d, y = %d, c = %d, k = %d\n", x, y, c, k);
                p = test4101(x,y,c,k,z1,z2);
                if (p) {
                        m++;
                        if (verbose > 0) {
                                printf("*** Failure %d in test %d\n", p, i);
                        }
                }
        }
        if (verbose > 0) {
                if (m) {
                        printf("*** %d error(s)\n", m);
                } else {
                        printf("passed %d tests\n", n);
                }
        }
        return m;
}

define times(str,N,n,verbose)
{
        local m, m2, A, B, C, x, y, t, i, z;
        local trcin, trcout, trcmul, trcsq;
        local tmul, tsq, tmod, tquomod;

        if (isnull(verbose))
                verbose = defaultverbose;
        if (verbose > 0) {
                print str:":",:;
        }
        m = olen(N);
        m2 = m^2;
        if (isnull(n)) {
                n = ceil(test4100_K1/power(N,1.585));
                if (verbose > 1)
                        printf("n = %d\n", n);
        }
        mat A[n];
        mat B[n];
        mat C[n];
        for (i = 0; i < n; i++) {
                A[i] = rand(m);
                B[i] = rand(m);
                C[i] = rand(m2);
        }
        z = rcin(0,m);  /* to initialize redc and maybe lastmod information */
        t = usertime();
        for (i = 0; i < n; i++)
                z = rcin(A[i],m);
        trcin = round(usertime() - t, 3);
        t = usertime();
        for (i = 0; i < n; i++)
                z = rcout(A[i],m);
        trcout = round(usertime() - t, 3);
        t = usertime();
        for (i = 0; i < n; i++)
                z = rcmul(A[i],B[i],m);
        trcmul = round(usertime() - t, 3);
        t = usertime();
        for (i = 0; i < n; i++)
                z = rcsq(A[i],m);
        trcsq = round(usertime() - t, 3);
        t = usertime();
        for (i = 0; i < n; i++)
                z = A[i] * B[i];
        tmul = round(usertime() - t, 3);
        t = usertime();
        for (i = 0; i < n; i++)
                z = A[i]^2;
        tsq = round(usertime() - t, 3);
        t = usertime();
        for (i = 0; i < n; i++)
                z = C[i] % A[i];
        tmod = round(usertime() - t, 3);
        t = usertime();
        for (i = 0; i < n; i++)
                quomod(C[i], A[i], x, y);
        tquomod = round(usertime() - t,3);

        if (verbose > 1) {
                printf("rcin: %d, rcout: %d, rcmul: %d, rcsq: %d\n",
                        trcin, trcout, trcmul, trcsq);
                printf("%s: mul: %d, sq: %d, mod: %d, quomod: %d\n",
                        str, tmul, tsq, tmod, tquomod);
        } else if (verbose > 0) {
                printf("no error(s)\n");
        }
        return 0;
}


define powtimes(str, N1, N2, n, verbose)
{
        local A, Ar, B, v, i, t, z1, z2, z3, z4, z5, cp, crc;
        local tsmall, tnormal, tbignum, trcsmall, trcbig, m;

        if (isnull(verbose))
                verbose = defaultverbose;
        if (verbose > 0) {
                print str:":",:;
        }
        m = 0;

        if (isnull(N2))
                N2 = 1;

        if (isnull(n)) {
                n = ceil(test4100_K2/power(N1, 1.585)/N2);
                printf ("n  = %d\n", n);
        }
        mat A[n];
        mat Ar[n];
        mat B[n];
        v = olen(N1);

        cp = config("pow2", 2);
        crc = config("redc2", 2);

        /* initialize redc and lastmod info */

        z1 = z2 = z3 = z4 = z5 = rcin(0,v);

        for (i = 0; i < n; i++) {
                A[i] = rand(v);
                Ar[i] = rcin(A[i], v);
                B[i] = rlen_4100(N2);
        }
        t = usertime();
        for (i = 0; i < n; i++)
                z1 += pmod(A[i], B[i], v);
        tbignum = round(usertime() - t, 4);
        config("pow2", 1e6);
        t = usertime();
        for (i = 0; i < n; i++)
                z2 += pmod(A[i], B[i], v);
        tnormal = round(usertime() - t, 4);
        config("redc2",1e6);
        t = usertime();
        for (i = 0; i < n; i++)
                z3 += pmod(A[i], B[i], v);
        tsmall = round(usertime() - t, 4);
        t = usertime();
        for (i = 0; i < n; i++)
                z4 += rcpow(Ar[i], B[i], v);
        trcsmall = round(usertime() - t, 4);
        config("redc2", 2);
        t = usertime();
        for (i = 0; i < n; i++)
                z5 += rcpow(Ar[i], B[i], v);
        trcbig = round(usertime() - t, 4);

        if (z1 != z2) {
                ++m;
                if (verbose > 0) {
                        print "*** z1 != z2";
                }
        } else if (z1 != z3) {
                ++m;
                if (verbose > 0) {
                        print "*** z1 != z3";
                }
        } else if (z2 != z3) {
                ++m;
                if (verbose > 0) {
                        print "*** z2 != z3";
                }
        } else if (rcout(z4, v) != z1 % v) {
                ++m;
                if (verbose > 0) {
                        print "*** z4 != z1";
                }
        } else if (z4 != z5) {
                ++m;
                if (verbose > 0) {
                        print "*** z4 != z5";
                }
        }
        config("pow2", cp);
        config("redc2", crc);
        if (verbose > 1) {
        }
        if (verbose > 1) {
                printf("Small: %d, normal: %d, bignum: %d\n",
                        tsmall, tnormal, tbignum);
                printf("%s: rcsmall: %d, rcbig: %d\n",
                        str, trcsmall, trcbig);
        } else if (verbose > 0) {
                if (m) {
                        printf("*** %d error(s)\n", m);
                } else {
                        printf("passed\n");
                }
        }
        return 0;
}

define inittimes(str,N,n,verbose)
{
        local A, M, B, R, i, t, trcin, trcout, m;

        if (isnull(verbose))
                verbose = defaultverbose;
        if (verbose > 0) {
                print str:":",:;
        }
        m = 0;
        if (isnull(n)) {
                n = ceil(test4100_K1/N^2);
                if (verbose > 1) {
                        printf ("n = %d\n", n);
                }
        }
        mat A[n];
        mat M[n];
        mat B[n];
        mat R[n];
        for (i = 0; i < n; i++) {
                M[i] = olen(N);
                A[i] = rand(M[i]);
        }
        t = usertime();
        for (i = 0; i < n; i++)
                R[i] = rcin(A[i], M[i]);
        trcin = round(usertime() - t, 4);
        for (i = 0; i < n; i++)
                B[i] = rcout(R[i], M[i]);
        trcout = round(usertime() - t, 4);
        for (i = 0; i < n; i++) {
                if (B[i] != A[i]) {
                        ++m;
                        if (verbose > 0) {
                                print "*** Error!!!";
                        }
                        break;
                }
        }
        if (verbose > 0) {
                if (m) {
                        printf("*** %d error(s)?\n", m);
                } else {
                        if (verbose > 1) {
                            printf("%d successful tests: rcin: %d, rcout: %d\n",
                              n, trcin, trcout);
                        } else {
                            printf("%d successful tests: passed\n", n);
                        }
                }
        }
        return m;
}

/*
 * test4100 - perform all of the above tests a bunch of times
 */
define test4100(v, tnum)
{
        local n;        /* test parameter */

        /*
         * set test parameters
         */
        srand(4100e4100);

        /*
         * test a lot of stuff
         */
        err += testall(strcat(str(tnum++),": testall(10,,500)"), 10,, 500, v);
        err += testall(strcat(str(tnum++),": testall(200)"), 200,,, v);

        err += times(strcat(str(tnum++),": times(3,3000)"), 3, 3000, v);
        err += times(strcat(str(tnum++),": times(30,300)"), 30, 300, v);
        err += times(strcat(str(tnum++),": times(300,30)"), 300, 30, v);
        err += times(strcat(str(tnum++),": times(1000,3)"), 1000, 3, v);

        err += powtimes(strcat(str(tnum++),": powtimes(100)"),100,,v);
        err += powtimes(strcat(str(tnum++),": powtimes(250)"),250,,v);

        err += inittimes(strcat(str(tnum++),": inittimes(10)"),10,,v);
        err += inittimes(strcat(str(tnum++),": inittimes(100,70)"),100,70,v);
        err += inittimes(strcat(str(tnum++),": inittimes(1000,4)"),1000,4,v);

        /*
         * report results
         */
        if (v > 1) {
                if (err) {
                        print "***", err, "error(s) found in testall";
                } else {
                        print "no errors in testall";
                }
        }
        return tnum;
}
