/*
 *  $Id: math--rank.c 29061 2026-01-02 15:01:53Z yeti-dn $
 *  Copyright (C) 2003-2024 David Necas (Yeti), Petr Klapetek.
 *  E-mail: yeti@gwyddion.net, klapetek@gwyddion.net.
 *
 *  The quicksort algorithm was copied from GNU C library, Copyright (C) 1991, 1992, 1996, 1997, 1999 Free Software
 *  Foundation, Inc.  See below.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any
 *  later version.
 *
 *  This program 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 General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along with this program; if not, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include <string.h>
#include <stdlib.h>

#include "libgwyddion/macros.h"
#include "libgwyddion/math.h"

#define DSWAP(x, y) GWY_SWAP(gdouble, x, y)
#define ISWAP(x, y) GWY_SWAP(guint, x, y)

static inline void
order_3(gdouble *array)
{
    GWY_ORDER(gdouble, array[0], array[1]);
    if (array[2] < array[1]) {
        DSWAP(array[1], array[2]);
        GWY_ORDER(gdouble, array[0], array[1]);
    }
}

static gdouble
kth_rank_simple(gsize n, gdouble *array, gsize k)
{
    gdouble a, b, c, d;
    gsize i;

    if (n == 1)
        return array[0];

    if (n == 2) {
        GWY_ORDER(gdouble, array[0], array[1]);
        return array[k];
    }

    if (n == 3 && k == 1) {
        order_3(array);
        return array[1];
    }

    if (k == 0) {
        a = array[0];
        for (i = 1; i < n; i++) {
            c = array[i];
            if (c < a) {
                array[i] = a;
                array[0] = a = c;
            }
        }
        return a;
    }

    if (k == n-1) {
        a = array[n-1];
        for (i = 0; i < n-1; i++) {
            c = array[i];
            if (c > a) {
                array[i] = a;
                array[n-1] = a = c;
            }
        }
        return a;
    }

    if (k == 1) {
        GWY_ORDER(gdouble, array[0], array[1]);
        a = array[0];
        b = array[1];

        for (i = 2; i < n; i++) {
            c = array[i];
            if (c < b) {
                if (c < a) {
                    array[i] = b;
                    array[1] = b = a;
                    array[0] = a = c;
                }
                else {
                    array[i] = b;
                    array[1] = b = c;
                }
            }
        }
        return b;
    }

    if (k == n-2) {
        GWY_ORDER(gdouble, array[n-2], array[n-1]);
        a = array[n-1];
        b = array[n-2];

        for (i = 0; i < n-2; i++) {
            c = array[i];
            if (c > b) {
                if (c > a) {
                    array[i] = b;
                    array[n-2] = b = a;
                    array[n-1] = a = c;
                }
                else {
                    array[i] = b;
                    array[n-2] = b = c;
                }
            }
        }
        return b;
    }

    if (k == 2) {
        order_3(array);
        a = array[0];
        b = array[1];
        c = array[2];

        for (i = 3; i < n; i++) {
            d = array[i];
            if (d < c) {
                if (d < b) {
                    if (d < a) {
                        array[i] = c;
                        array[2] = c = b;
                        array[1] = b = a;
                        array[0] = a = d;
                    }
                    else {
                        array[i] = c;
                        array[2] = c = b;
                        array[1] = b = d;
                    }
                }
                else {
                    array[i] = c;
                    array[2] = c = d;
                }
            }
        }
        return c;
    }

    if (k == n-3) {
        order_3(array + n-3);
        a = array[n-1];
        b = array[n-2];
        c = array[n-3];

        for (i = 0; i < n-3; i++) {
            d = array[i];
            if (d > c) {
                if (d > b) {
                    if (d > a) {
                        array[i] = c;
                        array[n-3] = c = b;
                        array[n-2] = b = a;
                        array[n-1] = a = d;
                    }
                    else {
                        array[i] = c;
                        array[n-3] = c = b;
                        array[n-2] = b = d;
                    }
                }
                else {
                    array[i] = c;
                    array[n-3] = c = d;
                }
            }
        }
        return c;
    }

    g_assert_not_reached();
}

/**
 * gwy_math_kth_rank:
 * @array: (array length=n): Array of doubles.  It is shuffled by this function.
 * @n: Number of items in @array.
 * @k: Rank of the value to find (from lowest to highest).
 *
 * Finds k-th item of an array of values using Quick select algorithm.
 *
 * The value positions change as follows.  The returned value is guaranteed to be at @k-th position in the array (i.e.
 * correctly ranked).  All other values are correctly ordered with respect to this value: preceeding values are
 * smaller (or equal) and following values are larger (or equal).
 *
 * Returns: The @k-th value of @array if it was sorted.
 **/
gdouble
gwy_math_kth_rank(gdouble *array, gsize n, gsize k)
{
    gsize lo, hi;
    gsize middle, ll, hh;
    gdouble m;

    g_return_val_if_fail(k < n, 0.0);

    lo = 0;
    hi = n-1;
    while (TRUE) {
        if (hi <= lo+2 || k <= lo+2 || k+2 >= hi)
            return kth_rank_simple(hi+1 - lo, array + lo, k - lo);

        /* Find median of lo, middle and hi items; swap into position lo */
        middle = (lo + hi)/2;

        GWY_ORDER(gdouble, array[middle], array[hi]);
        GWY_ORDER(gdouble, array[lo], array[hi]);
        GWY_ORDER(gdouble, array[middle], array[lo]);

        /* Swap low item (now in position middle) into position (lo+1) */
        DSWAP(array[middle], array[lo+1]);

        /* Nibble from each end towards middle, swapping items when stuck */
        ll = lo+1;
        hh = hi;
        m = array[lo];
        while (TRUE) {
            do {
                ll++;
            } while (m > array[ll]);
            do {
                hh--;
            } while (array[hh] > m);

            if (hh < ll)
                break;

            DSWAP(array[ll], array[hh]);
        }

        /* Swap middle item (in position lo) back into correct position */
        array[lo] = array[hh];
        array[hh] = m;

        /* Re-set active partition */
        if (hh <= k)
            lo = hh;
        if (hh >= k)
            hi = hh-1;
    }
}

/**
 * gwy_math_median:
 * @array: (array length=n): Array of doubles.  It is shuffled by this function.
 * @n: Number of items in @array.
 *
 * Finds median of an array of values using Quick select algorithm.
 *
 * See gwy_math_kth_rank() for details of how the values are shuffled.
 *
 * Returns: The median value of @array.
 **/
gdouble
gwy_math_median(gdouble *array, gsize n)
{
    return gwy_math_kth_rank(array, n, n/2);
}

/* When there are many values to find, just sort the entire thing and read the values at the corresponding ranks. */
static void
kth_ranks_brute(gsize n, gdouble *array,
                guint nk, const guint *k, gdouble *values)
{
    gwy_math_sort(array, n);
    for (guint j = 0; j < nk; j++)
        values[j] = array[k[j]];
}

/* We assume k[] is uniq-sorted which eliminates a bunch of cases. */
static void
kth_ranks_small(gsize n, gdouble *array,
                guint nk, const guint *k, gdouble *values)
{
    guint k0, k1, d0, d1;

    if (!nk)
        return;

    k0 = k[0];
    if (nk == 1) {
        values[0] = gwy_math_kth_rank(array, n, k0);
        return;
    }

    k1 = k[1];
    d0 = (k0 <= n/2) ? n/2 - k0 : k0 - n/2;
    d1 = (k1 <= n/2) ? n/2 - k1 : k1 - n/2;
    if (d0 <= d1) {
        values[0] = gwy_math_kth_rank(array, n, k0);
        k0++;
        values[1] = gwy_math_kth_rank(array+k0, n-k0, k1-k0);
    }
    else {
        values[1] = gwy_math_kth_rank(array, n, k1);
        values[0] = gwy_math_kth_rank(array, k1, k0);
    }
}

static void
kth_ranks_recurse(gsize n, gdouble *array,
                  guint nk, guint *k, gdouble *values)
{
    guint jmid, kmid, j;

    if (nk <= 2) {
        kth_ranks_small(n, array, nk, k, values);
        return;
    }

    jmid = nk/2;
    kmid = k[jmid];
    values[jmid] = gwy_math_kth_rank(array, n, kmid);

    /* Now recurse into the halfs.  Both are non-empty because nk >= 3. */
    kth_ranks_recurse(kmid, array, jmid, k, values);

    jmid++;
    for (j = jmid; j < nk; j++)
        k[j] -= kmid+1;
    kth_ranks_recurse(n-1-kmid, array+kmid+1, nk-jmid, k+jmid, values+jmid);
    for (j = jmid; j < nk; j++)
        k[j] += kmid+1;
}

static guint
bisect_lower_guint(const guint *a, guint n, guint x)
{
    guint lo = 0, hi = n-1;

    if (G_UNLIKELY(x < a[lo]))
        return 0;
    if (G_UNLIKELY(x >= a[hi]))
        return n-1;

    while (hi - lo > 1) {
        guint mid = (hi + lo)/2;

        if (x < a[mid])
            hi = mid;
        else
            lo = mid;
    }

    return lo;
}

static void
kth_ranks_fastpath(gsize n, gdouble *array,
                   guint nk, const guint *k, gdouble *values)
{
    if (nk < 2 || k[0] < k[1])
        kth_ranks_small(n, array, nk, k, values);
    else if (k[0] == k[1])
        values[0] = values[1] = gwy_math_kth_rank(array, n, k[0]);
    else {
        guint ksorted[2];

        ksorted[0] = k[1];
        ksorted[1] = k[0];
        kth_ranks_small(n, array, nk, ksorted, values);
        DSWAP(values[0], values[1]);
    }
}

/**
 * gwy_math_kth_ranks:
 * @array: (array length=n): Array of doubles.  It is shuffled by this function.
 * @n: Number of items in @array.
 * @k: Ranks of the value to find.
 * @nk: Number of ranked values to find (sizes of arrays @k and @values).
 * @values: Array where to store values with ranks (from smallest to highest) given in @k.
 *
 * Finds simultaneously several k-th items of an array of values.
 *
 * The values are shuffled similarly to gwy_math_kth_rank(), except that the guarantee holds for all given ranks
 * simultaneously.  All values with explicitly requested ranks are at their correct positions and all values lying
 * between them in the array are also between them numerically.
 **/
void
gwy_math_kth_ranks(gdouble *array, gsize n,
                   const guint *k, guint nk, gdouble *values)
{
    for (guint j = 0; j < nk; j++) {
        g_return_if_fail(k[j] < n);
    }

    if (nk <= 2) {
        kth_ranks_fastpath(n, array, nk, k, values);
        return;
    }
    if (n < 30) {
        kth_ranks_brute(n, array, nk, k, values);
        return;
    }

    gdouble logn = log(n);
    if (nk > 0.12*exp(0.3*logn)*logn*logn) {
        kth_ranks_brute(n, array, nk, k, values);
        return;
    }

    guint *ksorted = g_new(guint, nk);
    gdouble *valsorted = g_new(gdouble, nk);

    /* Do uniq-sort on the k values.  The uniq is more to avoid some odd cases in the recursion than for efficiency. */
    gwy_assign(ksorted, k, nk);
    gwy_guint_sort(ksorted, nk);
    guint t = 0;
    for (guint j = 0; j < nk; j++) {
        if (ksorted[j] != ksorted[t]) {
            t++;
            ksorted[t] = ksorted[j];
        }
    }
    guint nkred = t+1;
    /* The recursion can be at most log2(nkred) deep. */
    kth_ranks_recurse(n, array, nkred, ksorted, valsorted);
    /* Assign the values to the original array. */
    for (guint j = 0; j < nk; j++)
        values[j] = valsorted[bisect_lower_guint(ksorted, nkred, k[j])];

    g_free(ksorted);
    g_free(valsorted);
}

/**
 * gwy_math_trimmed_mean:
 * @array: (array length=n): Array of doubles.  It is shuffled by this function.
 * @n: Number of items in @array.
 * @nlowest: The number of lowest values to discard.
 * @nhighest: The number of highest values to discard.
 *
 * Finds trimmed mean of an array of values.
 *
 * At least one value must remain after the trimming, i.e. @nlowest + @nhighest must be smaller than @n.  Usually one
 * passes the same number as both @nlowest and @nhighest, but it is not a requirement.
 *
 * The function can be also used to calculate normal mean values as it implements efficiently the cases when no
 * trimming is done at either end. However, the array is still considered mutable. Use gwy_math_mean() which has
 * a const array argument.
 *
 * Returns: The trimmed mean.
 **/
gdouble
gwy_math_trimmed_mean(gdouble *array, gsize n, guint nlowest, guint nhighest)
{
    g_return_val_if_fail(nlowest < n, 0.0);
    g_return_val_if_fail(nhighest < n - nlowest, 0.0);

    /* Note that when using the k-th rank functions, we ask for the first discarded item, and hence ignore it the
     * returned values. */
    gsize nred;
    if (!nlowest) {
        if (!nhighest)
            nred = n;
        else {
            nred = n-nhighest;
            gwy_math_kth_rank(array, n, nred);
        }
    }
    else if (!nhighest) {
        nred = n-nlowest;
        gwy_math_kth_rank(array, n, nlowest-1);
        array += nlowest;
    }
    else {
        guint k[2];
        gdouble v[2];

        k[0] = nlowest-1;
        k[1] = n-nhighest;
        nred = n - (nlowest + nhighest);
        gwy_math_kth_ranks(array, n, k, 2, v);
        array += nlowest;
    }

    /* Now the reduced array block is guaranteed to contain only non-discarded values. So just calculate the average. */
    return gwy_math_mean(array, nred);
}

/**
 * gwy_math_mean:
 * @array: (array length=n): Array of doubles.
 * @n: Number of items in @array.
 *
 * Calculates the mean of an array of values.
 *
 * Returns: The mean value.
 **/
gdouble
gwy_math_mean(const gdouble *array, gsize n)
{
    g_return_val_if_fail(n && array, 0.0);
    gdouble s = 0.0;
    for (gsize i = 0; i < n; i++)
        s += array[i];
    return s/n;
}

static inline guint
percentile_to_rank(gsize n, gdouble p, GwyPercentileInterpolationType interp)
{
    gdouble kreal, x, eps;
    guint k;

    g_return_val_if_fail(p >= 0.0, 0);
    g_return_val_if_fail(p <= 100.0, n-1);

    kreal = p/100.0*(n - 1);
    k = (guint)floor(kreal);
    x = kreal - k;
    eps = n*3e-16;
    if (interp == GWY_PERCENTILE_INTERPOLATION_LOWER || x <= eps)
        return k;
    if (interp == GWY_PERCENTILE_INTERPOLATION_HIGHER || x >= 1.0 - eps)
        return k+1;
    /* Nearest. */
    return x < 0.5 ? k : k+1;
}

/**
 * gwy_math_percentiles:
 * @array: (array length=n): Array of doubles.  It is shuffled by this function.
 * @n: Number of items in @array.
 * @interp: Interpolation method to use for percentiles that do not correspond exactly to an integer rank.
 * @p: Array of size @np with the percentiles to compute.  The values are in percents, i.e. from the range [0,100].
 * @np: Number of percentiles to find.
 * @values: Array where to store values with percentiles given in @p.
 *
 * Finds simultaneously several percentiles of an array of values.
 *
 * The values in @array are shuffled similarly to gwy_math_kth_ranks(). However, it is difficult to state how exactly
 * @p translates to the values that become correctly ranked (and it depends on @interp).  Hence you can only assume
 * the set of values is preserved.
 **/
void
gwy_math_percentiles(gdouble *array, gsize n,
                     GwyPercentileInterpolationType interp,
                     const gdouble *p, guint np, gdouble *values)
{
    gdouble *v2;
    guint *k;
    guint j;

    if (interp == GWY_PERCENTILE_INTERPOLATION_LOWER
        || interp == GWY_PERCENTILE_INTERPOLATION_HIGHER
        || interp == GWY_PERCENTILE_INTERPOLATION_NEAREST) {
        k = g_new(guint, np);
        for (j = 0; j < np; j++)
            k[j] = percentile_to_rank(n, p[j], interp);

        gwy_math_kth_ranks(array, n, k, np, values);
        g_free(k);
        return;
    }

    k = g_new(guint, 2*np);
    v2 = g_new(gdouble, 2*np);

    /* When p corresponds exactly to an integer rank we store the same k twice.
     * But gwy_math_kth_ranks() does a uniq-sort of k[] anyway. */
    for (j = 0; j < np; j++) {
        k[2*j] = percentile_to_rank(n, p[j], GWY_PERCENTILE_INTERPOLATION_LOWER);
        k[2*j + 1] = percentile_to_rank(n, p[j], GWY_PERCENTILE_INTERPOLATION_HIGHER);
    }
    gwy_math_kth_ranks(array, n, k, 2*np, v2);
    for (j = 0; j < np; j++) {
        gdouble vlower = v2[2*j], vupper = v2[2*j + 1], x;

        if (k[2*j + 1] == k[2*j])
            values[j] = vlower;
        else if (interp == GWY_PERCENTILE_INTERPOLATION_MIDPOINT)
            values[j] = 0.5*(vlower + vupper);
        else {
            g_assert(k[2*j + 1] == k[2*j] + 1);
            x = p[j]/100.0*(n - 1) - k[2*j];
            values[j] = x*vupper + (1.0 - x)*vlower;
        }
    }

    g_free(k);
    g_free(v2);
}


/* Copyright (C) 1991, 1992, 1996, 1997, 1999 Free Software Foundation, Inc.
   This file is part of the GNU C Library.
   Written by Douglas C. Schmidt (schmidt@ics.uci.edu).

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library 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.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, write to the Free
   Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
   MA 02110-1301, USA  */

/* If you consider tuning this algorithm, you should consult first:
   Engineering a sort function; Jon Bentley and M. Douglas McIlroy;
   Software - Practice and Experience; Vol. 23 (11), 1249-1265, 1993.  */

/* The next 4 #defines implement a very fast in-line stack abstraction. */
#define PUSH(low, high) ((void) ((top->lo = (low)), (top->hi = (high)), ++top))
#define POP(low, high)  ((void) (--top, (low = top->lo), (high = top->hi)))
#define STACK_NOT_EMPTY (stack < top)

/* Order size using quicksort.  This implementation incorporates four optimizations discussed in Sedgewick:

   1. Non-recursive, using an explicit stack of pointer that store the next array partition to sort.  To save time,
   this maximum amount of space required to store an array of SIZE_MAX is allocated on the stack.  Assuming a 32-bit
   (64 bit) integer for size_t, this needs only 32 * sizeof(stack_node) == 256 bytes (for 64 bit: 1024 bytes). Pretty
   cheap, actually.

   2. Chose the pivot element using a median-of-three decision tree. This reduces the probability of selecting a bad
   pivot value and eliminates certain extraneous comparisons.

   3. Only quicksorts TOTAL_ELEMS / MAX_THRESH partitions, leaving insertion sort to order the MAX_THRESH items within
   each partition. This is a big win, since insertion sort is faster for small, mostly sorted array segments.

   4. The larger of the two sub-partitions is always pushed onto the stack first, with the algorithm then
   concentrating on the smaller partition.  This *guarantees* no more than log(n) stack size is needed (actually O(1)
   in this case)!  */

/**
 * gwy_math_sort:
 * @n: Number of items in @array.
 * @array: (array length=n): Array of doubles to sort in place.
 *
 * Sorts an array of doubles using a quicksort algorithm.
 *
 * This is usually about twice as fast as the generic quicksort function thanks to specialization for doubles.
 **/
void
gwy_math_sort(gdouble *array, gsize n)
{
    /* Discontinue quicksort algorithm when partition gets below this size. This particular magic number was chosen to
     * work best on a Sun 4/260. */
    /* Specialization makes the insertion sort part relatively more efficient, after some benchmarking this seems be
     * about the best value on Athlon 64. */
    /* The stack needs log (total_elements) entries (we can even subtract log2(MAX_THRESH)).  Since total_elements has
     * type size_t, we get as upper bound for log (total_elements): bits per byte (CHAR_BIT) * sizeof(size_t).  */
    enum {
        MAX_THRESH = 12,
        LOG2_MAX_TRESH = 3,
        STACK_SIZE = CHAR_BIT*sizeof(gsize) - LOG2_MAX_TRESH,
    };

    /* Stack node declarations used to store unfulfilled partition obligations. */
    typedef struct {
        gdouble *lo;
        gdouble *hi;
    } stack_node;

    if (n < 2)
        /* Avoid lossage with unsigned arithmetic below.  */
        return;

    if (n > MAX_THRESH) {
        gdouble *lo = array;
        gdouble *hi = lo + (n - 1);
        stack_node stack[STACK_SIZE];
        stack_node *top = stack + 1;

        while (STACK_NOT_EMPTY) {
            gdouble *left_ptr;
            gdouble *right_ptr;

            /* Select median value from among LO, MID, and HI. Rearrange LO and HI so the three values are sorted.
             * This lowers the probability of picking a pathological pivot value and skips a comparison for both the
             * LEFT_PTR and RIGHT_PTR in the while loops. */

            gdouble *mid = lo + ((hi - lo) >> 1);

            if (*mid < *lo)
                DSWAP(*mid, *lo);

            if (*hi < *mid) {
                DSWAP(*mid, *hi);
                if (*mid < *lo)
                    DSWAP(*mid, *lo);
            }

            left_ptr  = lo + 1;
            right_ptr = hi - 1;

            /* Here's the famous ``collapse the walls'' section of quicksort. Gotta like those tight inner loops!
             * They are the main reason that this algorithm runs much faster than others. */
            do {
                while (*left_ptr < *mid)
                    left_ptr++;

                while (*mid < *right_ptr)
                    right_ptr--;

                if (left_ptr < right_ptr) {
                    DSWAP(*left_ptr, *right_ptr);
                    if (mid == left_ptr)
                        mid = right_ptr;
                    else if (mid == right_ptr)
                        mid = left_ptr;
                    left_ptr++;
                    right_ptr--;
                }
                else if (left_ptr == right_ptr) {
                    left_ptr++;
                    right_ptr--;
                    break;
                }
            }
            while (left_ptr <= right_ptr);

            /* Set up pointers for next iteration.  First determine whether left and right partitions are below the
             * threshold size.  If so, ignore one or both.  Otherwise, push the larger partition's bounds on the stack
             * and continue sorting the smaller one. */

            if ((gsize)(right_ptr - lo) <= MAX_THRESH) {
                if ((gsize)(hi - left_ptr) <= MAX_THRESH) {
                    /* Ignore both small partitions. */
                    POP(lo, hi);
                }
                else {
                    /* Ignore small left partition. */
                    lo = left_ptr;
                }
            }
            else if ((gsize)(hi - left_ptr) <= MAX_THRESH) {
                /* Ignore small right partition. */
                hi = right_ptr;
            }
            else if ((right_ptr - lo) > (hi - left_ptr)) {
                /* Push larger left partition indices. */
                PUSH(lo, right_ptr);
                lo = left_ptr;
            }
            else {
                /* Push larger right partition indices. */
                PUSH(left_ptr, hi);
                hi = right_ptr;
            }
        }
    }

    /* Once the BASE_PTR array is partially sorted by quicksort the rest is completely sorted using insertion sort,
     * since this is efficient for partitions below MAX_THRESH size. BASE_PTR points to the beginning of the array to
     * sort, and END_PTR points at the very last element in the array (*not* one beyond it!). */

    {
        gdouble *const end_ptr = array + (n - 1);
        gdouble *tmp_ptr = array;
        gdouble *thresh = MIN(end_ptr, array + MAX_THRESH);
        register gdouble *run_ptr;

        /* Find smallest element in first threshold and place it at the array's beginning.  This is the smallest array
         * element, and the operation speeds up insertion sort's inner loop. */
        for (run_ptr = tmp_ptr + 1; run_ptr <= thresh; run_ptr++) {
            if (*run_ptr < *tmp_ptr)
                tmp_ptr = run_ptr;
        }

        if (tmp_ptr != array)
            DSWAP(*tmp_ptr, *array);

        /* Insertion sort, running from left-hand-side up to right-hand-side. */
        run_ptr = array + 1;
        while (++run_ptr <= end_ptr) {
            tmp_ptr = run_ptr - 1;
            while (*run_ptr < *tmp_ptr)
                tmp_ptr--;

            tmp_ptr++;
            if (tmp_ptr != run_ptr) {
                gdouble *hi, *lo;
                gdouble d;

                d = *run_ptr;
                for (hi = lo = run_ptr; --lo >= tmp_ptr; hi = lo)
                    *hi = *lo;
                *hi = d;
            }
        }
    }
}

/**
 * gwy_guint_sort:
 * @n: Number of items in @array.
 * @array: (array length=n): Array of #guint values to sort in place.
 *
 * Sorts an array of unsigned integers using a quicksort algorithm.
 *
 * This is usually about twice as fast as the generic quicksort function thanks to specialization for integers.
 **/
void
gwy_guint_sort(guint *array, gsize n)
{
    /* Discontinue quicksort algorithm when partition gets below this size.
     * This particular magic number was chosen to work best on a Sun 4/260. */
    /* Specialization makes the insertion sort part relatively more
     * efficient, after some benchmarking this seems be about the best value
     * on Athlon 64. */
    /* The stack needs log (total_elements) entries (we can even subtract
     * log2(MAX_THRESH)).  Since total_elements has type size_t, we get as
     * upper bound for log (total_elements):
     * bits per byte (CHAR_BIT) * sizeof(size_t).  */
    enum {
        MAX_THRESH = 12,
        LOG2_MAX_TRESH = 3,
        STACK_SIZE = CHAR_BIT*sizeof(gsize) - LOG2_MAX_TRESH,
    };

    /* Stack node declarations used to store unfulfilled partition obligations.
     */
    typedef struct {
        guint *lo;
        guint *hi;
    } stack_node;

    if (n < 2)
        /* Avoid lossage with unsigned arithmetic below.  */
        return;

    if (n > MAX_THRESH) {
        guint *lo = array;
        guint *hi = lo + (n - 1);
        stack_node stack[STACK_SIZE];
        stack_node *top = stack + 1;

        while (STACK_NOT_EMPTY) {
            guint *left_ptr;
            guint *right_ptr;

            /* Select median value from among LO, MID, and HI. Rearrange
               LO and HI so the three values are sorted. This lowers the
               probability of picking a pathological pivot value and
               skips a comparison for both the LEFT_PTR and RIGHT_PTR in
               the while loops. */

            guint *mid = lo + ((hi - lo) >> 1);

            if (*mid < *lo)
                DSWAP(*mid, *lo);

            if (*hi < *mid) {
                DSWAP(*mid, *hi);
                if (*mid < *lo)
                    DSWAP(*mid, *lo);
            }

            left_ptr  = lo + 1;
            right_ptr = hi - 1;

            /* Here's the famous ``collapse the walls'' section of quicksort.
               Gotta like those tight inner loops!  They are the main reason
               that this algorithm runs much faster than others. */
            do {
                while (*left_ptr < *mid)
                    left_ptr++;

                while (*mid < *right_ptr)
                    right_ptr--;

                if (left_ptr < right_ptr) {
                    DSWAP(*left_ptr, *right_ptr);
                    if (mid == left_ptr)
                        mid = right_ptr;
                    else if (mid == right_ptr)
                        mid = left_ptr;
                    left_ptr++;
                    right_ptr--;
                }
                else if (left_ptr == right_ptr) {
                    left_ptr++;
                    right_ptr--;
                    break;
                }
            }
            while (left_ptr <= right_ptr);

          /* Set up pointers for next iteration.  First determine whether
             left and right partitions are below the threshold size.  If so,
             ignore one or both.  Otherwise, push the larger partition's
             bounds on the stack and continue sorting the smaller one. */

          if ((gsize)(right_ptr - lo) <= MAX_THRESH) {
              if ((gsize)(hi - left_ptr) <= MAX_THRESH)
                  /* Ignore both small partitions. */
                  POP(lo, hi);
              else
                  /* Ignore small left partition. */
                  lo = left_ptr;
          }
          else if ((gsize)(hi - left_ptr) <= MAX_THRESH)
              /* Ignore small right partition. */
              hi = right_ptr;
          else if ((right_ptr - lo) > (hi - left_ptr)) {
              /* Push larger left partition indices. */
              PUSH(lo, right_ptr);
              lo = left_ptr;
          }
          else {
              /* Push larger right partition indices. */
              PUSH(left_ptr, hi);
              hi = right_ptr;
          }
        }
    }

    /* Once the BASE_PTR array is partially sorted by quicksort the rest
       is completely sorted using insertion sort, since this is efficient
       for partitions below MAX_THRESH size. BASE_PTR points to the beginning
       of the array to sort, and END_PTR points at the very last element in
       the array (*not* one beyond it!). */

    {
        guint *const end_ptr = array + (n - 1);
        guint *tmp_ptr = array;
        guint *thresh = MIN(end_ptr, array + MAX_THRESH);
        register guint *run_ptr;

        /* Find smallest element in first threshold and place it at the
           array's beginning.  This is the smallest array element,
           and the operation speeds up insertion sort's inner loop. */

        for (run_ptr = tmp_ptr + 1; run_ptr <= thresh; run_ptr++) {
            if (*run_ptr < *tmp_ptr)
                tmp_ptr = run_ptr;
        }

        if (tmp_ptr != array)
            DSWAP(*tmp_ptr, *array);

        /* Insertion sort, running from left-hand-side up to right-hand-side.
         */

        run_ptr = array + 1;
        while (++run_ptr <= end_ptr) {
            tmp_ptr = run_ptr - 1;
            while (*run_ptr < *tmp_ptr)
                tmp_ptr--;

            tmp_ptr++;
            if (tmp_ptr != run_ptr) {
                guint *hi, *lo;
                guint d;

                d = *run_ptr;
                for (hi = lo = run_ptr; --lo >= tmp_ptr; hi = lo)
                    *hi = *lo;
                *hi = d;
            }
        }
    }
}

/**
 * gwy_math_sort_with_index:
 * @array: Array of doubles to sort in place.
 * @n: Number of items in @array.
 * @index_array: Array of integer identifiers of the items that are permuted simultaneously with @array.
 *
 * Sorts an array of doubles using a quicksort algorithm, remembering the permutation.
 *
 * The simplest and probably most common use of @index_array is to fill it with numbers 0 to @n-1 before calling
 * gwy_math_sort().  After sorting, @index_array[@i] then contains the original position of the @i-th item of the
 * sorted array.
 **/
/* FIXME: It is questionable whether it is still more efficient to use pointers instead of array indices when it
 * effectively doubles the number of variables.  This might force some variables from registers to memory... */
void
gwy_math_sort_with_index(gdouble *array, gsize n, guint *index_array)
{
    enum {
        MAX_THRESH = 12,
        LOG2_MAX_TRESH = 3,
        STACK_SIZE = CHAR_BIT*sizeof(gsize) - LOG2_MAX_TRESH,
    };

    /* Stack node declarations used to store unfulfilled partition obligations. */
    typedef struct {
        gdouble *lo;
        gdouble *hi;
        guint *loi;
        guint *hii;
    } stack_node;

    if (n < 2)
        /* Avoid lossage with unsigned arithmetic below.  */
        return;

    if (n > MAX_THRESH) {
        gdouble *lo = array;
        gdouble *hi = lo + (n - 1);
        guint *loi = index_array;
        guint *hii = loi + (n - 1);
        stack_node stack[STACK_SIZE];
        stack_node *top = stack + 1;

        while (STACK_NOT_EMPTY) {
            gdouble *left_ptr;
            gdouble *right_ptr;
            guint *left_ptri;
            guint *right_ptri;

            /* Select median value from among LO, MID, and HI. Rearrange LO and HI so the three values are sorted.
             * This lowers the probability of picking a pathological pivot value and skips a comparison for both the
             * LEFT_PTR and RIGHT_PTR in the while loops. */

            gdouble *mid = lo + ((hi - lo) >> 1);
            guint *midi = loi + ((hii - loi) >> 1);

            if (*mid < *lo) {
                DSWAP(*mid, *lo);
                ISWAP(*midi, *loi);
            }
            if (*hi < *mid) {
                DSWAP(*mid, *hi);
                ISWAP(*midi, *hii);

                if (*mid < *lo) {
                    DSWAP(*mid, *lo);
                    ISWAP(*midi, *loi);
                }
            }

          left_ptr  = lo + 1;
          right_ptr = hi - 1;
          left_ptri  = loi + 1;
          right_ptri = hii - 1;

          /* Here's the famous ``collapse the walls'' section of quicksort. Gotta like those tight inner loops!  They
           * are the main reason that this algorithm runs much faster than others. */
          do {
              while (*left_ptr < *mid) {
                  left_ptr++;
                  left_ptri++;
              }

              while (*mid < *right_ptr) {
                  right_ptr--;
                  right_ptri--;
              }

              if (left_ptr < right_ptr) {
                  DSWAP(*left_ptr, *right_ptr);
                  ISWAP(*left_ptri, *right_ptri);
                  if (mid == left_ptr) {
                      mid = right_ptr;
                      midi = right_ptri;
                  }
                  else if (mid == right_ptr) {
                      mid = left_ptr;
                      midi = left_ptri;
                  }
                  left_ptr++;
                  left_ptri++;
                  right_ptr--;
                  right_ptri--;
              }
              else if (left_ptr == right_ptr) {
                  left_ptr++;
                  left_ptri++;
                  right_ptr--;
                  right_ptri--;
                  break;
              }
          }
          while (left_ptr <= right_ptr);

          /* Set up pointers for next iteration.  First determine whether left and right partitions are below the
           * threshold size.  If so, ignore one or both.  Otherwise, push the larger partition's bounds on the stack
           * and continue sorting the smaller one. */

          if ((gsize)(right_ptr - lo) <= MAX_THRESH) {
              if ((gsize)(hi - left_ptr) <= MAX_THRESH) {
                  /* Ignore both small partitions. */
                  --top;
                  lo = top->lo;
                  hi = top->hi;
                  loi = top->loi;
                  hii = top->hii;
              }
              else {
                  /* Ignore small left partition. */
                  lo = left_ptr;
                  loi = left_ptri;
              }
          }
          else if ((gsize)(hi - left_ptr) <= MAX_THRESH) {
              /* Ignore small right partition. */
              hi = right_ptr;
              hii = right_ptri;
          }
          else if ((right_ptr - lo) > (hi - left_ptr)) {
              /* Push larger left partition indices. */
              top->lo = lo;
              top->loi = loi;
              top->hi = right_ptr;
              top->hii = right_ptri;
              ++top;
              lo = left_ptr;
              loi = left_ptri;
          }
          else {
              /* Push larger right partition indices. */
              top->lo = left_ptr;
              top->loi = left_ptri;
              top->hi = hi;
              top->hii = hii;
              ++top;
              hi = right_ptr;
              hii = right_ptri;
          }
        }
    }

    /* Once the BASE_PTR array is partially sorted by quicksort the rest is completely sorted using insertion sort,
     * since this is efficient for partitions below MAX_THRESH size. BASE_PTR points to the beginning of the array to
     * sort, and END_PTR points at the very last element in the array (*not* one beyond it!). */
    {
        gdouble *const end_ptr = array + (n - 1);
        gdouble *tmp_ptr = array;
        guint *tmp_ptri = index_array;
        gdouble *thresh = MIN(end_ptr, array + MAX_THRESH);
        gdouble *run_ptr;
        guint *run_ptri;

        /* Find smallest element in first threshold and place it at the array's beginning.  This is the smallest array
         * element, and the operation speeds up insertion sort's inner loop. */

        for (run_ptr = tmp_ptr + 1, run_ptri = tmp_ptri + 1;
             run_ptr <= thresh;
             run_ptr++, run_ptri++) {
            if (*run_ptr < *tmp_ptr) {
                tmp_ptr = run_ptr;
                tmp_ptri = run_ptri;
            }
        }

        if (tmp_ptr != array) {
            DSWAP(*tmp_ptr, *array);
            ISWAP(*tmp_ptri, *index_array);
        }

        /* Insertion sort, running from left-hand-side up to right-hand-side. */

        run_ptr = array + 1;
        run_ptri = index_array + 1;
        while (++run_ptr <= end_ptr) {
            tmp_ptr = run_ptr - 1;
            tmp_ptri = run_ptri;
            ++run_ptri;
            while (*run_ptr < *tmp_ptr) {
                tmp_ptr--;
                tmp_ptri--;
            }

            tmp_ptr++;
            tmp_ptri++;
            if (tmp_ptr != run_ptr) {
                gdouble *hi, *lo;
                guint *hii, *loi;
                gdouble d;
                guint i;

                d = *run_ptr;
                for (hi = lo = run_ptr; --lo >= tmp_ptr; hi = lo)
                    *hi = *lo;
                *hi = d;

                i = *run_ptri;
                for (hii = loi = run_ptri; --loi >= tmp_ptri; hii = loi)
                    *hii = *loi;
                *hii = i;
            }
        }
    }
}

/* vim: set cin columns=120 tw=118 et ts=4 sw=4 cino=>1s,e0,n0,f0,{0,}0,^0,\:1s,=0,g1s,h0,t0,+1s,c3,(0,u0 : */
