/*
 * mfactor - return the lowest factor of 2^n-1, for n > 0
 *
 * Copyright (C) 1999,2021  Landon Curt Noll
 *
 * 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/07/06 06:09:40
 * File existed as early as:    1996
 *
 * chongo <was here> /\oo/\     http://www.isthe.com/chongo/
 * Share and enjoy!  :-)        http://www.isthe.com/chongo/tech/comp/calc/
 */

/*
 * hset method
 *
 * We will assume that mfactor is called with p_elim == 17.
 *
 *      n = (the Mersenne exponent we are testing)
 *      Q = 4*2*3*5*7*11*13*17 (4 * pfact(of some reasonable integer))
 *
 * We first determine all values of h mod Q such that:
 *
 *      gcd(h*n+1, Q) == 1   and   h*n+1 == +/-1 mod 8
 *
 * There will be 2*1*2*4*6*10*12*16 such values of h.
 *
 * For efficiency, we keep the difference between consecutive h values
 * in the hset[] difference array with hset[0] being the first h value.
 * Last, we multiply the hset[] values by n so that we only need
 * to add sequential values of hset[] to get factor candidates.
 *
 * We need only test factors of the form:
 *
 *      (Q*g*n + hx) + 1
 *
 * where:
 *
 *      g is an integer >= 0
 *      hx is computed from hset[] difference value described above
 *
 * Note that (Q*g*n + hx) is always even and that hx is a multiple
 * of n.  Thus the typical factor form:
 *
 *      2*k*n + 1
 *
 * implies that:
 *
 *      k = (Q*g + hx/n)/2
 *
 * This allows us to quickly eliminate factor values that are divisible
 * by 2, 3, 5, 7, 11, 13 or 17.  (well <= p value found below)
 *
 * The following loop shows how test_factor is advanced to higher test
 * values using hset[].  Here, hcount is the number of elements in hset[].
 * It can be shown that hset[0] == 0.  We add hset[hcount] to the hset[]
 * array for looping control convenience.
 *
 *      (* increase test_factor thru other possible test values *)
 *      test_factor = 0;
 *      hindx = 0;
 *      do {
 *              while (++hindx <= hcount) {
 *                      test_factor += hset[hindx];
 *              }
 *              hindx = 0;
 *      } while (test_factor < some_limit);
 *
 * The test, mfactor(67, 1, 10000) took on an 200 MHz r4k (user CPU seconds):
 *
 *      210.83  (prior to use of hset[])
 *       78.35  (hset[] for p_elim = 7)
 *       73.87  (hset[] for p_elim = 11)
 *       73.92  (hset[] for p_elim = 13)
 *      234.16  (hset[] for p_elim = 17)
 *      p_elim == 19 requires over 190 Megs of memory
 *
 * Over a long period of time, the call to load_hset() becomes insignificant.
 * If we look at the user CPU seconds from the first 10000 cycle to the
 * end of the test we find:
 *
 *      205.00  (prior to use of hset[])
 *       75.89  (hset[] for p_elim = 7)
 *       73.74  (hset[] for p_elim = 11)
 *       70.61  (hset[] for p_elim = 13)
 *       57.78  (hset[] for p_elim = 17)
 *       p_elim == 19 rejected because of memory size
 *
 * The p_elim == 17 overhead takes ~3 minutes on an 200 MHz r4k CPU and
 * requires about ~13 Megs of memory.  The p_elim == 13 overhead
 * takes about 3 seconds and requires ~1.5 Megs of memory.
 *
 * The value p_elim == 17 is best for long factorizations.  It is the
 * fastest even thought the initial startup overhead is larger than
 * for p_elim == 13.
 *
 * NOTE: The values above are prior to optimizations where hset[] was
 *       multiplied by n plus other optimizations.  Thus, the CPU
 *       times you may get will not likely match the above values.
 */


/*
 * mfactor - find a factor of a Mersenne Number
 *
 * Mersenne numbers are numbers of the form:
 *
 *      2^n-1   for integer n > 0
 *
 * We know that factors of a Mersenne number are of the form:
 *
 *      2*k*n+1   and   +/- 1 mod 8
 *
 * We make use of the hset[] difference array to eliminate factor
 * candidates that would otherwise be divisible by 2, 3, 5, 7 ... p_elim.
 *
 * given:
 *      n               attempt to factor M(n) = 2^n-1
 *      start_k         the value k in 2*k*n+1 to start the search (def: 1)
 *      rept_loop       loop cycle reporting (def: 10000)
 *      p_elim          largest prime to eliminate from test factors (def: 17)
 *
 * returns:
 *      factor of (2^n)-1
 *
 * NOTE: The p_elim argument is optional and defaults to 17.  A p_elim value
 *       of 17 is faster than 13 for even medium length runs.  However 13
 *       uses less memory and has a shorter startup time.
 */
define mfactor(n, start_k, rept_loop, p_elim)
{
        local Q;        /* 4*pfact(p_elim), hset[] cycle size */
        local hcount;   /* elements in the hset[] difference array */
        local loop;     /* report loop count */
        local q;        /* test factor of 2^n-1 */
        local g;        /* g as in test candidate form: (Q*g*hset[i])*n + 1 */
        local hindx;    /* hset[] index */
        local i;
        local tmp;
        local tmp2;

        /*
         * firewall
         */
        if (!isint(n) || n <= 0) {
                quit "n must be an integer > 0";
        }
        if (!isint(start_k)) {
                start_k = 1;
        } else if (!isint(start_k) || start_k <= 0) {
                quit "start_k must be an integer > 0";
        }
        if (!isint(rept_loop)) {
                rept_loop = 10000;
        }
        if (rept_loop < 1) {
                quit "rept_loop must be an integer > 0";
        }
        if (!isint(p_elim)) {
                p_elim = 17;
        }
        if (p_elim < 3) {
                quit "p_elim must be an integer > 2 (try 13 or 17)";
        }

        /*
         * declare our global values
         */
        Q = 4*pfact(p_elim);
        hcount = 2;
        /* allocate the h difference array */
        for (i=2; i <= p_elim; i = nextcand(i)) {
                hcount *= (i-1);
        }
        local mat hset[hcount+1];

        /*
         * load the hset[] difference array
         */
        {
                local x;        /* h*n+1 mod 8 */
                local h;        /* potential h value */
                local last_h;   /* previous valid h value */

                last_h = 0;
                for (i=0,h=0; h < Q; ++h) {
                        if (gcd(h*n+1,Q) == 1) {
                                x = (h*n+1) % 8;
                                if (x == 1 || x == 7) {
                                        hset[i++] = (h-last_h) * n;
                                        last_h = h;
                                }
                        }
                }
                hset[hcount] = Q*n - last_h*n;
        }

        /*
         * setup
         *
         * determine the next g and hset[] index (hindx) values such that:
         *
         *      2*start_k <= (Q*g + hset[hindx])
         *
         * and (Q*g + hset[hindx]) is a minimum and where:
         *
         *      Q = (4 * pfact(of some reasonable integer))
         *      g = (some integer) (hset[] cycle number)
         *
         * We also compute 'q', the next test candidate.
         */
        g = (2*start_k) // Q;
        tmp = 2*start_k - Q*g;
        for (tmp2=0, hindx=0;
             hindx < hcount && (tmp2 += hset[hindx]/n) < tmp;
             ++hindx) {
        }
        if (hindx == hcount) {
                /* we are beyond the end of a hset[] cycle, start at the next */
                ++g;
                hindx = 0;
                tmp2 = hset[0]/n;
        }
        q = (Q*g + tmp2)*n + 1;

        /*
         * look for a factor
         *
         * We ignore factors that themselves are divisible by a prime <=
         * some small prime p.
         *
         * This process is guaranteed to find the smallest factor
         * of 2^n-1.  A smallest factor of 2^n-1 must be prime, otherwise
         * the divisors of that factor would also be factors of 2^n-1.
         * Thus we know that if a test factor itself is not prime, it
         * cannot be the smallest factor of 2^n-1.
         *
         * Eliminating all non-prime test factors would take too long.
         * However we can eliminate 80.81% of the test factors
         * by not using test factors that are divisible by a prime <= 17.
         */
        if (pmod(2,n,q) == 1) {
                return q;
        } else {
                /* report this loop */
                printf("at 2*%d*%d+1, CPU: %f\n",
                        (q-1)/(2*n), n, usertime());
                fflush(files(1));
                loop = 0;
        }
        do {
                /*
                 * determine if we need to report
                 *
                 * NOTE: (q-1)/(2*n) is the k value from 2*k*n + 1.
                 */
                if (rept_loop <= ++loop) {
                        /* report this loop */
                        printf("at 2*%d*%d+1, CPU: %f\n",
                                (q-1)/(2*n), n, usertime());
                        fflush(files(1));
                        loop = 0;
                }

                /*
                 * skip if divisible by a prime <= 449
                 *
                 * The value 281 was determined by timing loops
                 * which found that 281 was at or near the
                 * minimum time to factor 2^(2^127-1)-1.
                 *
                 * The addition of the do { ... } while (factor(q, 449)>1);
                 * loop reduced the factoring loop time (36504 k values with
                 * the hset[] initialization time removed) from 25.69 sec to
                 * 15.62 sec of CPU time on a 200MHz r4k.
                 */
                do {
                        /*
                         * determine the next factor candidate
                         */
                        q += hset[++hindx];
                        if (hindx >= hcount) {
                                hindx = 0;
                                /*
                                 * if we cared about g,
                                 * then we wound ++g here too
                                 */
                        }
                } while (factor(q, 449) > 1);
        } while (pmod(2,n,q) != 1);

        /*
         * return the factor found
         *
         * q is a factor of (2^n)-1
         */
        return q;
}

if (config("resource_debug") & 3) {
        print "mfactor(n [, start_k=1 [, rept_loop=10000 [, p_elim=17]]])"
}
