/*
 *  $Id: math.c 29777 2026-04-13 11:12:49Z yeti-dn $
 *  Copyright (C) 2003-2026 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 <glib/gi18n-lib.h>

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

#include "libgwyddion/omp.h"

/* Lower symmetric part indexing */
/* i MUST be greater or equal than j */
#define SLi(a, i, j) a[(i)*((i) + 1)/2 + (j)]

#define GWY_TWO_PI 6.28318530717958647692528676655900576839433879875016

/**
 * gwy_math_humanize_numbers:
 * @unit: The smallest possible step.
 * @maximum: The maximum possible value.
 * @precision: (out) (optional): A location to store printf() precession, if not %NULL.
 *
 * Finds a human-friendly representation for a range of numbers.
 *
 * Returns: The magnitude i.e., a power of 1000.
 **/
gdouble
gwy_math_humanize_numbers(gdouble unit,
                          gdouble maximum,
                          gint *precision)
{
    gdouble lm, lu, mag, q;

    g_return_val_if_fail(unit >= 0.0, 0.0);
    g_return_val_if_fail(maximum >= 0.0, 0.0);

    if (G_UNLIKELY(unit == 0.0 || maximum == 0.0)) {
        if (unit > 0.0)
            maximum = unit;
        else if (maximum > 0.0)
            unit = maximum;
        else {
            if (precision)
                *precision = 1;
            return 1.0;
        }
    }

    lm = log10(maximum) + 1e-12;
    lu = log10(unit) - 1e-12;
    mag = 3.0*floor(lm/3.0);
    q = 3.0*ceil(lu/3.0);
    if (q > mag)
        q = 3.0*ceil((lu - 1.0)/3.0);
    if (lu > -0.5 && lm < 3.1) {
        while (lu > mag+2)
            mag += 3.0;
    }
    else if (lm <= 0.5 && lm > -1.5) {
        mag = 0.0;
    }
    else {
        while (q > mag)
            mag += 3.0;
    }

    if (precision) {
        *precision = MAX(0, ceil(mag - lu));
        *precision = MIN(*precision, 16);
    }

    return gwy_exp10(mag);
}

/**
 * gwy_math_is_in_polygon:
 * @x: The x coordinate of the test point.
 * @y: The y coordinate of the test point.
 * @poly: An array of coordinate pairs (points) that define a polygon.
 * @n: The number of corners of the polygon.
 *
 * Establishes wether the test point @x, @y is inside the polygon @poly. The polygon can be defined either clockwise
 * or anti-clockwise and can be a concave, convex or self-intersecting polygon.
 *
 * <warning> Result can be either TRUE or FALSE if the test point is *exactly* on an edge. </warning>
 *
 * Returns: %TRUE if the test point is inside poly and %FALSE otherwise.
 **/
/* This neat little check algorithm  was found at http://alienryderflex.com/polygon and has been adapted */
gboolean
gwy_math_is_in_polygon(gdouble x,
                       gdouble y,
                       const gdouble *poly,
                       guint n)
{
    guint i, j = 0;
    gboolean inside = FALSE;
    gdouble xx, yy;

    for (i = 0; i < n; i++) {
        j++;
        if (j == n)
            j = 0;
        if ((poly[2*i + 1] < y && poly[2*j + 1] >= y) || (poly[2*j + 1] < y && poly[2*i + 1] >= y)) {
            xx = poly[2*j] - poly[2*i];
            yy = poly[2*j + 1] - poly[2*i + 1];
            if (poly[2*i] + ((y - poly[2*i + 1])/yy)*xx < x)
                inside = !inside;
        }
    }

    return inside;
}

/**
 * gwy_math_find_nearest_line:
 * @x: X-coordinate of the point.
 * @y: Y-coordinate of the point.
 * @d2min: (out) (optional): Where to store the squared minimal distance, or %NULL.
 * @coords: Line coordinates stored as x00, y00, x01, y01, x10, y10, etc.
 * @n: The number of lines (i.e. @coords has 4×@n items).
 * @metric: (array fixed-size=4) (nullable):
 *          Metric matrix (2x2, but stored sequentially by rows: m11, m12, m21, m22), it must be positive definite.
 *          Vector norm is then calculated as m11*x*x + (m12 + m21)*x*y + m22*y*y. It can be %NULL, standard Euclidean
 *          metric is then used.
 *
 * Finds the line nearest to the given point.
 *
 * Returns: The line number. It may return -1 if (@x, @y) doesn't lie in the orthogonal stripe of any of the lines.
 **/
gint
gwy_math_find_nearest_line(gdouble x, gdouble y,
                           gdouble *d2min,
                           const gdouble *coords, gint n,
                           const gdouble *metric)
{
    g_return_val_if_fail(n > 0, -1);
    g_return_val_if_fail(coords, -1);

    gint m = -1;
    gdouble d2m = G_MAXDOUBLE;

    if (metric) {
        gdouble m12 = metric[1] + metric[2];
        for (gint i = 0; i < n; i++, coords += 4) {
            gdouble xx = x - (coords[0] + coords[2])/2;
            gdouble yy = y - (coords[1] + coords[3])/2;

            gdouble vx = (coords[2] - coords[0])/2;
            gdouble vy = (coords[3] - coords[1])/2;
            if (vx == 0.0 && vy == 0.0)
                continue;
            gdouble d = metric[0]*vx*vx + m12*vx*vy + metric[3]*vy*vy;
            if (d <= 0.0) {
                g_warning("Metric does not evaluate as positive definite");
                continue;
            }
            d = -(metric[0]*vx*xx + m12*(vx*yy + vy*xx)/2 + metric[3]*vy*yy)/d;
            /* Out of orthogonal stripe */
            if (d < -1.0 || d > 1.0)
                continue;
            d = metric[0]*(xx + vx*d)*(xx + vx*d) + m12*(xx + vx*d)*(yy + vy*d) + metric[3]*(yy + vy*d)*(yy + vy*d);
            if (d < d2m) {
                d2m = d;
                m = i;
            }
        }
    }
    else {
        for (gint i = 0; i < n; i++, coords += 4) {
            gdouble xl0 = coords[0];
            gdouble yl0 = coords[1];
            gdouble xl1 = coords[2];
            gdouble yl1 = coords[3];

            gdouble vx = yl1 - yl0;
            gdouble vy = xl0 - xl1;
            if (vx == 0.0 && vy == 0.0)
                continue;
            if (vx*(y - yl0) < vy*(x - xl0))
                continue;
            if (vx*(yl1 - y) < vy*(xl1 - x))
                continue;
            gdouble d = vx*(x - xl0) + vy*(y - yl0);
            d *= d/(vx*vx + vy*vy);
            if (d < d2m) {
                d2m = d;
                m = i;
            }
        }
    }
    if (d2min)
      *d2min = d2m;

    return m;
}

/**
 * gwy_math_find_nearest_point:
 * @x: X-coordinate of the point.
 * @y: Y-coordinate of the point.
 * @d2min: (out) (optional): Location to store the squared minimal distance to, or %NULL.
 * @coords: Point coordinates stored as x0, y0, x1, y1, x2, y2, etc.
 * @n: The number of points (i.e. @coords has 2×@n items).
 * @metric: (array fixed-size=4) (nullable):
 *          Metric matrix (2x2, but stored sequentially by rows: m11, m12, m21, m22).  Vector norm is then calculated
 *          as m11*x*x + (m12 + m21)*x*y + m22*y*y. It can be %NULL, standard Euclidean metric is then used.
 *
 * Finds the point nearest to the given point.
 *
 * Returns: The point number.
 **/
gint
gwy_math_find_nearest_point(gdouble x, gdouble y,
                            gdouble *d2min,
                            const gdouble *coords, gint n,
                            const gdouble *metric)
{
    g_return_val_if_fail(n > 0, -1);
    g_return_val_if_fail(coords, -1);

    gint m = -1;
    gdouble d2m = G_MAXDOUBLE;

    if (metric) {
        gdouble m12 = metric[1] + metric[2];
        for (gint i = 0; i < n; i++, coords += 2) {
            gdouble xd = coords[0] - x;
            gdouble yd = coords[1] - y;
            gdouble d = metric[0]*xd*xd + m12*xd*yd + metric[3]*yd*yd;
            if (d < d2m) {
                d2m = d;
                m = i;
            }
        }
    }
    else {
        for (gint i = 0; i < n; i++, coords += 2) {
            gdouble xd = coords[0] - x;
            gdouble yd = coords[1] - y;
            gdouble d = xd*xd + yd*yd;
            if (d < d2m) {
                d2m = d;
                m = i;
            }
        }
    }
    if (d2min)
      *d2min = d2m;

    return m;
}

/**
 * gwy_math_find_nearest_corner:
 * @x: X-coordinate of the point.
 * @y: Y-coordinate of the point.
 * @d2min: (out) (optional): Location to store the squared minimal distance to, or %NULL.
 * @coords: Corner coordinates stored as x00, y00, x01, y01, x10, y10, etc.
 * @n: The number of rectangles (i.e. @coords has 4×@n items).
 * @metric: (array fixed-size=4) (nullable):
 *          Metric matrix (2x2, but stored sequentially by rows: m11, m12, m21, m22).  Vector norm is then calculated
 *          as m11*x*x + (m12 + m21)*x*y + m22*y*y. It can be %NULL, standard Euclidean metric is then used.
 *
 * Finds the rectangle corner nearest to the given point.
 *
 * The rectangles are given as non-redundant coordinates, i.e. only four numbers per rectangle. If you already have
 * all the 4×2 coordinates per rectangle use gwy_math_find_nearest_point() instead.
 *
 * The quadruples of points are interpreted as pairs of opposite rectangle corners. They are the 0th and 2nd corners
 * of the rectangle. The 1st point has coordinates (@x0, @y1) and the 3rd has coordinates (@x1, @y0). Meaning we move
 * away from a given corner by keeping @x and changing @y, and towards a given corner by keeping @y and changing @x.
 *
 * Returns: The corner number.
 **/
gint
gwy_math_find_nearest_corner(gdouble x, gdouble y,
                             gdouble *d2min,
                             const gdouble *coords, gint n,
                             const gdouble *metric)
{
    g_return_val_if_fail(n > 0, -1);
    g_return_val_if_fail(coords, -1);

    gint m = 0;
    gdouble d2m = G_MAXDOUBLE;

    if (metric) {
        gdouble m12 = metric[1] + metric[2];
        for (gint i = 0; i < n; i++, coords += 4) {
            gdouble xd0 = coords[0] - x;
            gdouble yd0 = coords[1] - y;
            gdouble xd1 = coords[2] - x;
            gdouble yd1 = coords[3] - y;
            gdouble d;

            if ((d = metric[0]*xd0*xd0 + m12*xd0*yd0 + metric[3]*yd0*yd0) < d2m) {
                d2m = d;
                m = 4*i;
            }
            if ((d = metric[0]*xd0*xd0 + m12*xd0*yd1 + metric[3]*yd1*yd1) < d2m) {
                d2m = d;
                m = 4*i + 1;
            }
            if ((d = metric[0]*xd1*xd1 + m12*xd1*yd1 + metric[3]*yd1*yd1) < d2m) {
                d2m = d;
                m = 4*i + 2;
            }
            if ((d = metric[0]*xd1*xd1 + m12*xd1*yd0 + metric[3]*yd0*yd0) < d2m) {
                d2m = d;
                m = 4*i + 3;
            }
        }
    }
    else {
        for (gint i = 0; i < n; i++, coords += 4) {
            gdouble xd0 = coords[0] - x;
            gdouble yd0 = coords[1] - y;
            gdouble xd1 = coords[2] - x;
            gdouble yd1 = coords[3] - y;
            gdouble d;

            if ((d = xd0*xd0 + yd0*yd0) < d2m) {
                d2m = d;
                m = 4*i;
            }
            if ((d = xd0*xd0 + yd1*yd1) < d2m) {
                d2m = d;
                m = 4*i + 1;
            }
            if ((d = xd1*xd1 + yd1*yd1) < d2m) {
                d2m = d;
                m = 4*i + 2;
            }
            if ((d = xd1*xd1 + yd0*yd0) < d2m) {
                d2m = d;
                m = 4*i + 3;
            }
        }
    }
    if (d2min)
      *d2min = d2m;

    return m;
}

/* Check whether t is between t1 and t2. Importantly, without assuming any ordering of t1 and t2. */
static inline gboolean
in_range(gdouble t1, gdouble t2, gdouble t)
{
    return (t - t1)*(t2 - t) >= 0.0;
}

/**
 * gwy_math_cut_line_by_rectangle:
 * @x: X-coordinate of the point.
 * @y: Y-coordinate of the point.
 * @vx: X-component of the line vector.
 * @vy: Y-component of the line vector.
 * @rect: (array fixed-size=4): Quadruple of points (x₁, y₁, x₂, y₂) defining two opposite corners of the rectangle.
 * @t: (out):
 *     Location where to store the intersection time. If the function return a negative value the intersection time is
 *     not set.
 *
 * Finds the closest intersection of a half-line with a rectangle side.
 *
 * The intersection time is such that (@x + @t×@vx, @y + @t×@vy) lies on a side of the rectangle. If the point is
 * inside the rectangle, there is always exactly one intersection provided (@vx, @vy) is non-zero. If the point is
 * outside, there may be multiple intersections. The closest one corresponds to the line entering the rectangle and
 * the corresponding @t is computed. There may not be any intersections, for instance when the half-line is pointed
 * away from the rectangle.
 *
 * The side numbering corresponds to @rect. Each rectangle side has exactly one coordinate constant and equal to x₁,
 * y₁, x₂ or y₂. So the corresponding index in @rect is returned.
 *
 * Returns: Non-negative number if an intersection was found, encoding the side. Negative number if the half-line does
 *          not intersect the rectangle.
 **/
gint
gwy_math_cut_line_by_rectangle(gdouble x,
                               gdouble y,
                               gdouble vx,
                               gdouble vy,
                               const gdouble *rect,
                               gdouble *t)
{
    g_return_val_if_fail(rect, -1);
    gdouble tbest = G_MAXDOUBLE;
    gint side = -1;

    if (vx != 0.0) {
        for (gint s = 0; s <= 2; s += 2) {
            gdouble tt = (rect[s] - x)/vx;
            if (tt >= 0.0 && tt < tbest && in_range(rect[1], rect[3], y + tt*vy)) {
                tbest = tt;
                side = s;
            }
        }
    }

    if (vy != 0.0) {
        for (gint s = 1; s <= 3; s += 2) {
            gdouble tt = (rect[s] - y)/vy;
            if (tt >= 0.0 && tt < tbest && in_range(rect[0], rect[2], x + tt*vx)) {
                tbest = tt;
                side = s;
            }
        }
    }

    if (side >= 0) {
        if (t)
            *t = tbest;
    }
    return side;
}

/**
 * gwy_math_lin_solve:
 * @n: The size of the system.
 * @matrix: The matrix of the system (@n times @n), ordered by row, then column.
 * @rhs: The right hand side of the sytem.
 * @result: (nullable): Where the result should be stored.  May be %NULL to allocate a fresh array for the result.
 *
 * Solve a regular system of linear equations.
 *
 * Returns: The solution (@result if it wasn't %NULL), may be %NULL if the matrix is singular.
 **/
gdouble*
gwy_math_lin_solve(gint n, const gdouble *matrix,
                   const gdouble *rhs,
                   gdouble *result)
{
    gdouble *m, *r;

    g_return_val_if_fail(n > 0, NULL);
    g_return_val_if_fail(matrix && rhs, NULL);

    m = (gdouble*)g_memdup(matrix, n*n*sizeof(gdouble));
    r = (gdouble*)g_memdup(rhs, n*sizeof(gdouble));
    result = gwy_math_lin_solve_rewrite(n, m, r, result);
    g_free(r);
    g_free(m);

    return result;
}

/**
 * gwy_math_lin_solve_rewrite:
 * @n: The size of the system.
 * @matrix: The matrix of the system (@n times @n), ordered by row, then column.
 * @rhs: The right hand side of the sytem.
 * @result: (nullable): Where the result should be stored.  May be %NULL to allocate a fresh array for the result.
 *
 * Solves a regular system of linear equations.
 *
 * This is a memory-conservative version of gwy_math_lin_solve() overwriting @matrix and @rhs with intermediate
 * results.
 *
 * Returns: The solution (@result if it wasn't %NULL), may be %NULL if the matrix is singular.
 **/
gdouble*
gwy_math_lin_solve_rewrite(gint n, gdouble *matrix,
                           gdouble *rhs,
                           gdouble *result)
{
    gint i, j, jj;

    g_return_val_if_fail(n > 0, NULL);
    g_return_val_if_fail(matrix && rhs, NULL);

    gint perm[n];

    /* elimination */
    for (i = 0; i < n; i++) {
        gdouble *row = matrix + i*n;
        gdouble piv = 0;
        gint pivj = 0;

        /* find pivot */
        for (j = 0; j < n; j++) {
            if (fabs(row[j]) > piv) {
                pivj = j;
                piv = fabs(row[j]);
            }
        }
        if (piv == 0.0) {
            g_warning("Singluar matrix");
            return NULL;
        }
        piv = row[pivj];
        perm[i] = pivj;

        /* subtract */
        for (j = i+1; j < n; j++) {
            gdouble *jrow = matrix + j*n;
            gdouble q = jrow[pivj]/piv;

            for (jj = 0; jj < n; jj++)
                jrow[jj] -= q*row[jj];

            jrow[pivj] = 0.0;
            rhs[j] -= q*rhs[i];
        }
    }

    /* back substitute */
    if (!result)
        result = g_new(gdouble, n);
    for (i = n-1; i >= 0; i--) {
        gdouble *row = matrix + i*n;
        gdouble x = rhs[i];

        for (j = n-1; j > i; j--)
            x -= result[perm[j]]*row[perm[j]];

        result[perm[i]] = x/row[perm[i]];
    }

    return result;
}

/* FIXME: We should subtract the mean value μ(x) from x data (both for equdistant and general data). Then the fitting
 * is much more stable. Finally the coefficient for power p is expressed as
 *
 * a_p = ∑_{k=0}^{d-p} (-1)^k a'_{k+p} choose(k+p, p) μ(x)^k
 *
 * where d is the degree and a' are the coefficients obtained with centralised x data. This is a generalisation of
 * what we do for the linear fits. */
static void
poly_solve_common(gdouble *sumx, gdouble *coeffs, gint degree)
{
    gdouble *m = g_new(gdouble, (degree+1)*(degree+2)/2);
    for (gint i = 0; i <= degree; i++) {
        gdouble *row = m + i*(i+1)/2;

        for (gint j = 0; j <= i; j++)
            row[j] = sumx[i+j];
    }
    if (!gwy_math_choleski_decompose(degree+1, m))
        gwy_clear(coeffs, degree+1);
    else
        gwy_math_choleski_solve(degree+1, m, coeffs);

    g_free(m);
}

/**
 * gwy_math_fit_poly:
 * @xdata: Independent variable data (of size @ndata).
 * @ydata: Dependent variable data (of size @ndata).
 * @ndata: The number of items in @xdata, @ydata.
 * @degree: The degree of polynomial to fit.
 * @coeffs: (nullable):
 *          An array of size @degree+1 to store the coefficients to, or %NULL (a fresh array is allocated then).
 *
 * Fits a polynomial through a general (x, y) data set.
 *
 * Returns: The coefficients of the polynomial (@coeffs when it was not %NULL, otherwise a newly allocated array).
 **/
gdouble*
gwy_math_fit_poly(const gdouble *xdata,
                  const gdouble *ydata,
                  gint ndata,
                  gint degree,
                  gdouble *coeffs)
{
    g_return_val_if_fail(ndata >= 0, NULL);
    g_return_val_if_fail(degree >= 0, NULL);
    g_return_val_if_fail(coeffs, NULL);
    g_return_val_if_fail(ydata || !ndata, NULL);
    g_return_val_if_fail(xdata || !ndata || degree == 0, NULL);

    if (!coeffs)
        coeffs = g_new0(gdouble, degree+1);
    else
        gwy_clear(coeffs, degree+1);

    if (ndata < degree+1)
        return coeffs;

    if (degree == 0) {
        coeffs[0] = gwy_math_mean(ydata, ndata);
        return coeffs;
    }
    if (degree == 1) {
        gdouble xmu = gwy_math_mean(xdata, ndata);
        gdouble ymu = gwy_math_mean(ydata, ndata);
        gdouble sxx = 0.0, sxy = 0.0;
        for (gint i = 0; i < ndata; i++) {
            gdouble x = xdata[i] - xmu, y = ydata[i] - ymu;
            sxx += x*x;
            sxy += x*y;
        }
        if (sxx > 0.0) {
            coeffs[1] = sxy/sxx;
            coeffs[0] = ymu - coeffs[1]*xmu;
        }
        return coeffs;
    }

    gdouble *sumx = g_new0(gdouble, 2*degree+1);
    for (gint i = 0; i < ndata; i++) {
        gdouble x = xdata[i];
        gdouble y = ydata[i];
        gdouble xp = 1.0;
        for (gint j = 0; j <= degree; j++) {
            sumx[j] += xp;
            coeffs[j] += xp*y;
            xp *= x;
        }
        for (gint j = degree+1; j <= 2*degree; j++) {
            sumx[j] += xp;
            xp *= x;
        }
    }

    poly_solve_common(sumx, coeffs, degree);
    g_free(sumx);

    return coeffs;
}

/**
 * gwy_math_fit_poly_equidist:
 * @ydata: Dependent variable data (of size @ndata).
 * @ndata: The number of items in @ydata.
 * @x0: Independent variable value corresponding to @ydata[0].
 * @dx: Step of independent variable.
 * @degree: The degree of polynomial to fit.
 * @coeffs: (nullable):
 *          An array of size @degree+1 to store the coefficients to, or %NULL (a fresh array is allocated then).
 *
 * Fits a polynomial through a data set with equidistant abscissa points.
 *
 * Returns: The coefficients of the polynomial (@coeffs when it was not %NULL, otherwise a newly allocated array).
 **/
gdouble*
gwy_math_fit_poly_equidist(const gdouble *ydata,
                           gint ndata,
                           gdouble x0,
                           gdouble dx,
                           gint degree,
                           gdouble *coeffs)
{
    g_return_val_if_fail(ndata >= 0, NULL);
    g_return_val_if_fail(degree >= 0, NULL);
    g_return_val_if_fail(coeffs, NULL);
    g_return_val_if_fail(ydata || !ndata, NULL);

    if (!coeffs)
        coeffs = g_new0(gdouble, degree+1);
    else
        gwy_clear(coeffs, degree+1);

    if (ndata < degree+1)
        return coeffs;

    if (degree == 0) {
        coeffs[0] = gwy_math_mean(ydata, ndata);
        return coeffs;
    }
    if (degree == 1) {
        gdouble xmu = x0 + 0.5*(ndata - 1)*dx;
        gdouble ymu = gwy_math_mean(ydata, ndata);
        gdouble sxx = 0.0, sxy = 0.0;
        for (gint i = 0; i < ndata; i++) {
            gdouble x = i - 0.5*(ndata - 1), y = ydata[i] - ymu;
            sxx += x*x;
            sxy += x*y;
        }
        sxx *= dx*dx;
        sxy *= dx;
        if (sxx > 0.0) {
            coeffs[1] = sxy/sxx;
            coeffs[0] = ymu - coeffs[1]*xmu;
        }
        return coeffs;
    }

    gdouble *sumx = g_new0(gdouble, 2*degree+1);
    for (gint i = 0; i < ndata; i++) {
        gdouble x = x0 + i*dx;
        gdouble y = ydata[i];
        gdouble xp = 1.0;
        for (gint j = 0; j <= degree; j++) {
            sumx[j] += xp;
            coeffs[j] += xp*y;
            xp *= x;
        }
        for (gint j = degree+1; j <= 2*degree; j++) {
            sumx[j] += xp;
            xp *= x;
        }
    }

    poly_solve_common(sumx, coeffs, degree);
    g_free(sumx);

    return coeffs;
}
/**
 * gwy_math_choleski_decompose:
 * @n: The dimension of @a.
 * @matrix: Lower triangular part of a symmetric matrix, stored by rows, i.e., matrix = [a_00 a_10 a_11 a_20 a_21 a_22
 *          a_30 ...].
 *
 * Decomposes a symmetric positive definite matrix in place.
 *
 * Returns: Whether the matrix was really positive definite.  If %FALSE, the decomposition failed and @a does not
 *          contain any meaningful values.
 **/
gboolean
gwy_math_choleski_decompose(gint dim, gdouble *a)
{
    gint i, j, k;
    gdouble s, r;

    for (k = 0; k < dim; k++) {
        /* diagonal element */
        s = SLi(a, k, k);
        for (i = 0; i < k; i++)
            s -= SLi(a, k, i) * SLi(a, k, i);
        if (s <= 0.0)
            return FALSE;
        SLi(a, k, k) = s = sqrt(s);

        /* nondiagonal elements */
        for (j = k+1; j < dim; j++) {
            r = SLi(a, j, k);
            for (i = 0; i < k; i++)
                r -= SLi(a, k, i) * SLi(a, j, i);
            SLi(a, j, k) = r/s;
        }
    }

    return TRUE;
}

/**
 * gwy_math_choleski_invert:
 * @n: Matrix size.
 * @matrix: Lower triangular part of a symmetric matrix, stored by rows, i.e., matrix = [a_00 a_10 a_11 a_20 a_21 a_22
 *          a_30 ...].
 *
 * Inverts a symmetric positive definite matrix in place.
 *
 * Returns: Whether the matrix was really positive definite.  If %FALSE, the inversion failed and @matrix does not
 *          contain any meaningful values.
 **/
gboolean
gwy_math_choleski_invert(gint n, gdouble *a)
{
    gint q = 0, m;
    gdouble s, t;
    gint k, i, j;

    gdouble x[n];
    for (k = n-1; k >= 0; k--) {
        s = a[0];
        if (s <= 0)
            return FALSE;
        m = 0;
        for (i = 0; i < n-1; i++) {
            q = m+1;
            m += i+2;
            t = a[q];
            x[i] = -t/s;      /* note use temporary x */
            if (i >= k)
                x[i] = -x[i];
            for (j = q; j < m; j++)
                a[j - (i+1)] = a[j+1] + t * x[j - q];
        }
        a[m] = 1.0/s;
        for (i = 0; i < n-1; i++)
            a[q + i] = x[i];
    }

    return TRUE;
}

/**
 * gwy_math_choleski_solve:
 * @n: The dimension of @a.
 * @decomp: Lower triangular part of Choleski decomposition as computed by gwy_math_choleski_decompose().
 * @rhs: Right hand side vector.  Is is modified in place, on return it contains the solution.
 *
 * Solves a system of linear equations with predecomposed symmetric positive definite matrix @a and right hand side
 * @b.
 **/
void
gwy_math_choleski_solve(gint dim, const gdouble *a, gdouble *b)
{
    gint i, j;

    /* back-substitution with the lower triangular matrix */
    for (j = 0; j < dim; j++) {
        for (i = 0; i < j; i++)
            b[j] -= SLi(a, j, i)*b[i];
        b[j] /= SLi(a, j, j);
    }

    /* back-substitution with the upper triangular matrix */
    for (j = dim-1; j >= 0; j--) {
        for (i = j+1; i < dim; i++)
            b[j] -= SLi(a, i, j)*b[i];
        b[j] /= SLi(a, j, j);
    }
}

/**
 * gwy_math_tridiag_solve_rewrite:
 * @n: The dimension of @d.
 * @d: The diagonal of a tridiagonal matrix, its contents will be overwritten.
 * @a: The above-diagonal stripe (it has @n-1 elements).
 * @b: The below-diagonal stripe (it has @n-1 elements).
 * @rhs: The right hand side of the system, upon return it will contain the solution.
 *
 * Solves a tridiagonal system of linear equations.
 *
 * Returns: %TRUE if the elimination suceeded, %FALSE if the system is (numerically) singular.  The contents of @d and
 *          @rhs may be overwritten in the case of failure too, but not to any meaningful values.
 **/
gboolean
gwy_math_tridiag_solve_rewrite(gint n,
                               gdouble *d,
                               const gdouble *a,
                               const gdouble *b,
                               gdouble *rhs)
{
    gint i;

    g_return_val_if_fail(n > 0, FALSE);

    /* Eliminate b[elow diagonal] */
    for (i = 0; i < n-1; i++) {
        /* If d[i] is zero, elimination fails (now or later) */
        if (!d[i])
            return FALSE;
        d[i+1] -= b[i]/d[i]*a[i];
        rhs[i+1] -= b[i]/d[i]*rhs[i];
    }
    if (!d[n-1])
        return FALSE;

    /* Eliminate a[bove diagonal], calculating the solution */
    for (i = n-1; i > 0; i--) {
        rhs[i] /= d[i];
        rhs[i-1] -= a[i-1]*rhs[i];
    }
    rhs[0] /= d[0];

    return TRUE;
}

/**
 * gwy_math_curvature_at_apex:
 * @coeffs: (array fixed-size=6):
 *          Array of the six polynomial coefficients of a quadratic surface in the following order: 1, x, y, x², xy, y².
 * @kappa1: (out): Location to store the smaller curvature to.
 * @kappa2: (out): Location to store the larger curvature to.
 * @phi1: (out): Location to store the direction of the smaller curvature to.
 * @phi2: (out): Location to store the direction of the larger curvature to.
 * @xc: (out): Location to store x-coordinate of the centre of the quadratic surface.
 * @yc: (out): Location to store y-coordinate of the centre of the quadratic surface.
 * @zc: (out): Location to store value at the centre of the quadratic surface.
 *
 * Calculates curvature parameters at the apex from two-dimensional quadratic polynomial coefficients.
 *
 * See also gwy_math_curvature_at_origin() which computes the local surface curvature at @x=0 and @y=0.
 *
 * If the quadratic surface was obtained by fitting the dimensions of the fitted area should not differ, in the
 * lateral coordinates used, by many orders from 1.  Otherwise the recognition of flat surfaces might not work.
 *
 * Curvatures have signs, positive mean a concave (cup-like) surface, negative mean a convex (cap-like) surface.  They
 * are ordered including the sign.
 *
 * Directions are angles from the interval [-π/2, π/2].
 *
 * If the quadratic surface is degenerate, i.e. flat in at least one direction, the centre is undefined.  The centre
 * is then chosen as the closest point the origin of coordinates.  For flat surfaces this means the origin is simply
 * returned as the centre position.  Consequently, you should use Cartesian coordinates with origin in a natural
 * centre, for instance centre of image or grain.
 *
 * Returns: The number of curved dimensions (0 to 2).
 **/
guint
gwy_math_curvature_at_apex(const gdouble *coeffs,
                           gdouble *pkappa1,
                           gdouble *pkappa2,
                           gdouble *pphi1,
                           gdouble *pphi2,
                           gdouble *pxc,
                           gdouble *pyc,
                           gdouble *pzc)
{
    gdouble a = coeffs[0], bx = coeffs[1], by = coeffs[2], cxx = coeffs[3], cxy = coeffs[4], cyy = coeffs[5];

    /* Check if any sane quadratic term is present. */
    if (fabs(cxx) + fabs(cxy) + fabs(cyy) <= 1e-14*(fabs(bx) + fabs(by))) {
        /* Linear gradient */
        if (pkappa1)
            *pkappa1 = 0.0;
        if (pkappa2)
            *pkappa2 = 0.0;
        if (pxc)
            *pxc = 0.0;
        if (pyc)
            *pyc = 0.0;
        if (pzc)
            *pzc = a;
        if (pphi1)
            *pphi1 = 0.0;
        if (pphi2)
            *pphi2 = G_PI_2;
        return 0;
    }

    /* At least one quadratic term.  Eliminate the mixed term, if any. */
    gdouble cm = cxx - cyy, cp = cxx + cyy;
    gdouble phi = 0.5*atan2(cxy, cm);
    gdouble H = hypot(cm, cxy);
    gdouble cx = 0.5*(cp + H), cy = 0.5*(cp - H);
    gdouble bx1 = bx*cos(phi) + by*sin(phi), by1 = -bx*sin(phi) + by*cos(phi);

    /* Eliminate linear terms */
    gdouble xc, yc;
    guint degree;
    if (fabs(cx) < 1e-14*fabs(cy)) {
        /* Only y quadratic term */
        xc = cx = 0.0;
        yc = -by1/(2.0*cy);
        degree = 1;
    }
    else if (fabs(cy) < 1e-14*fabs(cx)) {
        /* Only x quadratic term */
        xc = -bx1/(2.0*cx);
        yc = cy = 0.0;
        degree = 1;
    }
    else {
        /* Two quadratic terms */
        xc = -bx1/(2.0*cx);
        yc = -by1/(2.0*cy);
        degree = 2;
    }

    if (pxc)
        *pxc = xc*cos(phi) - yc*sin(phi);
    if (pyc)
        *pyc = xc*sin(phi) + yc*cos(phi);
    if (pzc)
        *pzc = a + xc*bx1 + yc*by1 + xc*xc*cx + yc*yc*cy;

    if (cx > cy) {
        GWY_SWAP(gdouble, cx, cy);
        phi += G_PI_2;
    }

    if (pkappa1)
        *pkappa1 = 2.0*cx;
    if (pkappa2)
        *pkappa2 = 2.0*cy;

    if (pphi1)
        *pphi1 = gwy_canonicalize_angle(phi, FALSE, FALSE);
    if (pphi2)
        *pphi2 = gwy_canonicalize_angle(phi + G_PI_2, FALSE, FALSE);

    return degree;
}

/**
 * gwy_math_curvature_at_origin:
 * @coeffs: (array fixed-size=6):
 *          Array of the six polynomial coefficients of a quadratic surface in the following order: 1, x, y, x², xy, y².
 * @kappa1: (out): Location to store the smaller curvature to.
 * @kappa2: (out): Location to store the larger curvature to.
 * @phi1: (out): Location to store the direction of the smaller curvature to.
 * @phi2: (out): Location to store the direction of the larger curvature to.
 *
 * Calculates curvature parameters at origin from two-dimensional quadratic polynomial coefficients.
 *
 * See gwy_math_curvature_at_apex() for discussion of scaling and sign convenrions.  This function function differs
 * from it by computing the local surface curvature at @x=0 and @y=0, whereas gwy_math_curvature_at_apex() computes
 * the curvature at the apex of the parabolic surface.
 *
 * The array @coeffs is consistent with gwy_math_curvature_at_apex(), even though here the constant term is not used.
 *
 * Returns: The number of curved dimensions (0 to 2).
 **/
guint
gwy_math_curvature_at_origin(const gdouble *coeffs,
                             gdouble *pkappa1,
                             gdouble *pkappa2,
                             gdouble *pphi1,
                             gdouble *pphi2)
{
    gdouble bx = coeffs[1], by = coeffs[2], cxx = coeffs[3], cxy = coeffs[4], cyy = coeffs[5];
    gdouble b2, tpcoeffs[6];

    /* Transform to a coordinate system in the local tangent plane to the surface (x points along the gradient, y is
     * horizontal, z is tilted to be normal to the surface). We only care about curvatures so we set all the
     * uninteresting coefficients to zeros and use gwy_math_curvature_at_apex(), knowing the apex and origin now
     * coincide. */
    tpcoeffs[0] = tpcoeffs[1] = tpcoeffs[2] = 0.0;
    b2 = bx*bx + by*by;
    if (b2 == 0.0)
        gwy_assign(tpcoeffs + 3, coeffs + 3, 3);
    else {
        gdouble beta2 = 1.0 + b2, beta = sqrt(beta2);
        tpcoeffs[3] = (bx*bx*cxx + bx*by*cxy + by*by*cyy)/(beta2*beta*b2);
        tpcoeffs[4] = (2.0*bx*by*(cyy - cxx) + (bx*bx - by*by)*cxy)/(beta2*b2);
        tpcoeffs[5] = (by*by*cxx - bx*by*cxy + bx*bx*cyy)/(beta*b2);
    }

    return gwy_math_curvature_at_apex(tpcoeffs, pkappa1, pkappa2, pphi1, pphi2, NULL, NULL, NULL);
}

static void
Rmatrix_to_x(gdouble sxx, gdouble sxy, gdouble syy,
             gdouble *R)
{
    gdouble cphi = sqrt(sxx/(sxx + syy));
    gdouble sphi = sqrt(syy/(sxx + syy));
    if (sxy < 0.0)
        sphi = -sphi;
    R[0] = R[3] = cphi;
    R[1] = sphi;
    R[2] = -sphi;
}

static void
matrix_vector_2x2(const gdouble *A, const gdouble *v, gdouble *vrot)
{
    vrot[0] = A[0]*v[0] + A[1]*v[1];
    vrot[1] = A[2]*v[0] + A[3]*v[1];
}

/**
 * gwy_math_plane_coefficients:
 * @sumx2: Sum of squared integer @x-coordinates.
 * @sumxy: Sum of products of integer @x- and @y-coordinates.
 * @sumy2: Sum of squared integer @y-coordinates.
 * @sumxz: Sum of products of integer @x-coordinates and @z values.
 * @sumyz: Sum of products of integer @y-coordinates and @z values.
 * @n: Number of points.
 * @bx: (out): Location where to store the plane @x-coeffictient.
 * @by: (out): Location where to store the plane @y-coeffictient.
 *
 * Calculates minimum-norm plane coefficients.
 *
 * The @x and @y coordinates are assumed to form a grid with step 1 (or larger) in each direction, at least
 * approximately. The sums entering the calculation must be calculated with respet to the centre of mass of the
 * point set in the @xy plane.
 *
 * The function then calculates the plane coefficients (in the same integer coordinates), in the sense of minimum-norm
 * solution in the degenerated cases. A typical degenerate case is that all points are collinear. In such case the
 * coefficient along the line can be determined, but the coefficient in the orthogonal direction cannot – and is set
 * to zero (and then they are projected into @x and @y). An even more degenerate case is a single point for which
 * the minimum-norm coefficients are zero.
 *
 * Returns: The rank, which is 2 in non-degenerate cases, 1 for collinear points, 0 for a single point and -1 for no
 *          points at all (which is the same here, but still useful to distinguish).
 **/
gint
gwy_math_plane_coefficients(gdouble sumx2, gdouble sumxy, gdouble sumy2,
                            gdouble sumxz, gdouble sumyz,
                            gsize n,
                            gdouble *bx, gdouble *by)
{
    /* Handle the trivial cases first. */
    if (n == 0)
        return -1;
    if (n == 1)
        return 0;

    /* In the specfied centred coordinates, the matrix has the block form
     * / n    0     0   \
     * | 0  sumx2 sumxy |
     * \ 0  sumxy sumy2 /
     */
    gdouble det = sumx2*sumy2 - sumxy*sumxy;

    /* There seems to be a theoretical non-degenerate limit |det/n³| ≥ 1/64, reached with 3-pixel configurations.
     * So we have quite a good margin for catching the degenerate cases. Handle the them first. */
    if (fabs(det)/gwy_powi(n, 3) > 0.01) {
        *bx = (sumxz*sumy2 - sumyz*sumxy)/det;
        *by = (sumyz*sumx2 - sumxz*sumxy)/det;
        return 2;
    }

    /* The single-degenerate case (collinear). */
    gdouble R[4];
    Rmatrix_to_x(sumx2, sumxy, sumy2, R);

    /* Transform the system to make the degenerate component the last one. But we actually need only one element
     * of the transformed matrix which is easy to calculate, so do it explicitly. Get the one element of rotated
     * rhs by an explicit transformation. */
    gdouble Arot00 = sumx2 + sumy2;

    gdouble rhsrot[2], rhs[2] = { sumxz, sumyz };
    matrix_vector_2x2(R, rhs, rhsrot);

    rhsrot[0] = rhsrot[0]/Arot00;
    /* Set the undetermined linear component to zero, getting the minimum-norm solution. */
    rhsrot[1] = 0.0;

    GWY_SWAP(gdouble, R[1], R[2]);
    matrix_vector_2x2(R, rhsrot, rhs);
    *bx = rhs[0];
    *by = rhs[1];

    return 1;
}

/**
 * gwy_math_refine_maximum_1d:
 * @y: (array fixed-size=3): Array of length 3, containing the neighbourhood values with the maximum in the centre.
 * @x: (out): Location to store the refined @x-coordinate.
 *
 * Performs subpixel refinement of parabolic a one-dimensional maximum.
 *
 * The central value corresponds to x-coordinate 0, distances between values are unity.  The refinement is based by
 * fitting a parabola through the maximum.  If it fails or the calculated maximum lies farther than the surrounding
 * values the function sets the refined maximum to the origin and returns %FALSE.
 *
 * Returns: %TRUE if the refinement succeeded, %FALSE if it failed.  The value of @x is usable regardless of the
 *          return value.
 **/
gboolean
gwy_math_refine_maximum_1d(const gdouble *y, gdouble *x)
{
    gdouble b, D;

    *x = 0.0;
    D = y[2] + y[0] - 2.0*y[1];
    b = 0.5*(y[0] - y[2]);
    if (D == 0.0 || fabs(D) < fabs(b))
        return FALSE;

    *x = b/D;
    return TRUE;
}

/**
 * gwy_math_refine_maximum_2d:
 * @z: (array fixed-size=9):
 *     Array of length 9, containing the square 3x3 neighbourhood values in matrix order and with the maximum in the
 *     centre.
 * @x: (out): Location to store the refined @x-coordinate.
 * @y: (out): Location to store the refined @y-coordinate.
 *
 * Performs subpixel refinement of parabolic a two-dimensional maximum.
 *
 * The central value corresponds to coordinates (0,0), distances between values are unity.  The refinement is based by
 * fitting a two-dimensional parabola through the maximum.  If it fails or the calculated maximum lies farther than
 * the surrounding values the function sets the refined maximum to the origin and returns %FALSE.
 *
 * Returns: %TRUE if the refinement succeeded, %FALSE if it failed.  The values of @x and @y are usable regardless of
 *          the return value.
 **/
gboolean
gwy_math_refine_maximum_2d(const gdouble *z,
                           gdouble *x, gdouble *y)
{
    gdouble sz, szx, szy, szxx, szxy, szyy;
    gdouble bx, by, cxx, cxy, cyy, D, sx, sy;
    gdouble m[6], rhs[3];

    *x = *y = 0;

    sz = z[0] + z[1] + z[2] + z[3] + z[4] + z[5] + z[6] + z[7] + z[8];
    szx = -z[0] + z[2] - z[3] + z[5] - z[6] + z[8];
    szy = -z[0] - z[1] - z[2] + z[6] + z[7] + z[8];
    szxx = z[0] + z[2] + z[3] + z[5] + z[6] + z[8];
    szxy = z[0] - z[2] - z[6] + z[8];
    szyy = z[0] + z[1] + z[2] + z[6] + z[7] + z[8];

    m[0] = 9.0;
    m[1] = m[2] = m[3] = m[5] = 6.0;
    m[4] = 4.0;
    gwy_math_choleski_decompose(3, m);

    rhs[0] = sz;
    rhs[1] = szxx;
    rhs[2] = szyy;
    gwy_math_choleski_solve(3, m, rhs);

    bx = szx/6.0;
    by = szy/6.0;
    cxx = rhs[1];
    cxy = szxy/4.0;
    cyy = rhs[2];

    D = 4.0*cxx*cyy - cxy*cxy;
    /* Don't try the sub-pixel refinement if bad cancellation occurs.  Zero D can means a line-like maximum that we
     * could still refine in the orthogonal direction but that seems a fringe case. */
    if (D == 0.0 || fabs(D) < 1e-8*MAX(fabs(4.0*cxx*cyy), fabs(cxy*cxy)))
        return FALSE;

    sx = (by*cxy - 2.0*bx*cyy)/D;
    sy = (bx*cxy - 2.0*by*cxx)/D;

    /* Don't trust the sub-pixel refinement if it moves the maximum too far from the centre. */
    if (sx*sx + sy*sy > 2.0)
        return FALSE;

    *x = sx;
    *y = sy;
    return TRUE;
}

static gboolean
interpolate_parabolic(const GwyXY *xy, gdouble *x)
{
    gdouble u1 = (xy[1].x - xy[0].x)*(xy[2].y - xy[1].y);
    gdouble u2 = (xy[2].x - xy[1].x)*(xy[0].y - xy[1].y);
    gdouble tx;

    if (fabs(u2 + u1) <= 1e-12*(fabs(u1) + fabs(u2)))
        return FALSE;

    tx = 0.5*(xy[1].x + (u2*xy[2].x + u1*xy[0].x)/(u2 + u1));
    if (tx <= xy[0].x || tx >= xy[2].x)
        return FALSE;

    *x = tx;
    return TRUE;
}

/**
 * gwy_accumulate_counts:
 * @array: Array of @n or @n+1 items, usually representing histogram bins.
 * @n: The number of histogram bins. Not the array size.
 * @do_rewind: %TRUE to ‘rewind’ the counts, making them start from 0. If you pass %TRUE @array must have @n+1
 *             elements.
 *
 * Transforms counts in histogram bins to cumulative counts.
 *
 * Each element of the array becomes the sum of previous elements, possibly plus itself. The behaviour depends on
 * @do_rewind.
 *
 * If @do_rewind is %FALSE each elements becomes the sum of all previous elements plus itself. The function only works
 * with @n elements of @array. The array [3, 1, 5, 2] with @n=4 is transformed to [3, 4, 9, 11].
 *
 * Often, this function is used for transforming block sizes to indices where a block starts. In such case pass %TRUE
 * and @array must have @n+1 elements because the items are effectively shifted right by one. The first element becomes
 * zero and the (@n+1)-th one becomes the sum of all elements up to @n-1. The array [3, 1, 5, 2, ?] with @n=4 becomes
 * [0, 3, 4, 9, 11]. The value marked ? is not read, but is overwritten.
 **/
void
gwy_accumulate_counts(guint *array,
                      gint n,
                      gboolean do_rewind)
{
    guint i, v, s;

    g_return_if_fail(n >= 0);
    g_return_if_fail(array);

    if (!do_rewind) {
        for (i = 1; i < n; i++)
            array[i] += array[i-1];
        return;
    }

    s = 0;
    for (i = 0; i < n; i++) {
        v = array[i];
        array[i] = s;
        s += v;
    }
    array[n] = s;
}

/**
 * gwy_math_linspace:
 * @array: (array length=n) (nullable) (transfer full): Array with @n elements to fill with values, or %NULL.
 * @n: Number of items in @array.
 * @first: Value the first item should be set to.
 * @step: Difference between consecutive values.
 *
 * Fills an array with a arithmetic progression of numbers.
 *
 * Element @i is set to @first + @i*@step.
 *
 * Note that the arguments differ from the similar numpy function.
 *
 * Returns: (array length=n) (transfer full):
 *          @array itself if it was not %NULL. If @array is %NULL a new array is allocated and returned.
 **/
gdouble*
gwy_math_linspace(gdouble *array,
                  gsize n,
                  gdouble first,
                  gdouble step)
{
    if (!array)
        array = g_new(gdouble, n);

    for (gsize i = 0; i < n; i++)
        array[i] = first + i*step;

    return array;
}

/**
 * gwy_math_fill:
 * @array: (array length=n) (nullable) (transfer full): Array with @n elements to fill with values, or %NULL.
 * @n: Number of items in @array.
 * @value: Value to fill the array with.
 *
 * Fills an array with a constant.
 *
 * Returns: (array length=n) (transfer full):
 *          @array itself if it was not %NULL. If @array is %NULL a new array is allocated and returned.
 **/
gdouble*
gwy_math_fill(gdouble *array,
              gsize n,
              gdouble value)
{
    if (!array)
        array = g_new(gdouble, n);

    for (gsize i = 0; i < n; i++)
        array[i] = value;

    return array;
}

/**
 * gwy_compare_double:
 * @a: Pointer to a double.
 * @b: Pointer to a double.
 *
 * Compares two double values, given as pointers.
 *
 * This function is suitable as #GCompareFunc and can be also used with plain qsort(). The typical usage is sorting
 * of arrays containing structs where the first item is a floating point value/coordinate, followed by additional
 * data. For sorting of plain arrays of doubles use gwy_math_sort().
 *
 * It should only be used to sort normal numbers. The behaviour for NaNs is undefined.
 **/
gint
gwy_compare_double(gconstpointer a, gconstpointer b)
{
    const gdouble *da = (const gdouble*)a;
    const gdouble *db = (const gdouble*)b;

    if (*da < *db)
        return -1;
    if (*da > *db)
        return 1;
    return 0;
}

static gboolean
find_min_in_array(const GwyXY *xy, guint n, guint *pimin)
{
    gboolean any_variation = FALSE;
    gdouble y, yy;
    guint imin, i;

    imin = n/2;
    for (i = 1; i <= n; i++) {
        if (xy[i].y < xy[imin].y)
            imin = i;
    }

    y = xy[imin].y;
    if (imin > 0) {
       yy = xy[imin-1].y;
       if (yy - y > 6e-16*(fabs(y) + fabs(yy)))
           any_variation = TRUE;
    }
    if (imin+1 < n) {
       yy = xy[imin+1].y;
       if (yy - y > 6e-16*(fabs(y) + fabs(yy)))
           any_variation = TRUE;
    }

    *pimin = imin;
    return any_variation;
}

/**
 * gwy_math_find_minimum_1d:
 * @function: (scope call): Function to minimize.
 * @a: First interval endpoint.
 * @b: Second interval endpoint.
 * @user_data: User data passed to @function.
 *
 * Finds a minimum of a real function in a finite interval.
 *
 * The function simply does what it says on the tin.  If there are multiple minima in [a,b] any of them can be
 * returned, even though some effort to scan the interval is made.  There is no requiement for the minimum to lie
 * inside [a,b]; if it occurrs at one of the endpoints, the endpoint is returned.
 **/
gdouble
gwy_math_find_minimum_1d(GwyRealFunc function,
                         gdouble a, gdouble b,
                         gpointer user_data)
{
    enum { initial_n = 12 };
    GwyXY xy[initial_n+1];
    gdouble x, y, xeps;
    guint i, imin, n, iter;
    gboolean at_left_edge = FALSE, at_right_edge = FALSE;

    GWY_ORDER(gdouble, a, b);
    if (b-a < 1.2e-16*(fabs(a) + fabs(b)))
        return 0.5*(a + b);

    /* Initial scan of the interval. */
    imin = 0;
    xy[0].x = a;
    xy[0].y = function(a, user_data);
    for (i = 1; i <= initial_n; i++) {
        x = (i == initial_n) ? b : b/initial_n*i + a/initial_n*(initial_n-i);
        xy[i].x = x;
        xy[i].y = y = function(x, user_data);
    }

    if (!find_min_in_array(xy, initial_n, &imin))
        return 0.5*(a + b);

    gwy_debug("initial minimum at %g, point #%u", xy[imin].x, imin);
    /* Use the first 4-5 values to keep points while iterating. */
    if (imin == 0)
        at_left_edge = TRUE;
    else if (imin == initial_n) {
        at_right_edge = TRUE;
        memmove(xy, xy + initial_n-2, 3*sizeof(GwyXY));
    }
    else
        memmove(xy, xy + imin-1, 3*sizeof(GwyXY));

    gwy_debug("initial subinterval [%g..%g]", xy[0].x, xy[2].x);

    iter = 0;
    while (xy[2].x - xy[0].x > 1.2e-15*(fabs(xy[0].x) + fabs(xy[2].x))) {
        gwy_debug("new iter, interval [%.16g..%.16g] %g, edges: %d %d",
                  xy[0].x, xy[2].x, xy[2].x - xy[0].x,
                  at_left_edge, at_right_edge);
        n = 3;
        /* Just split the interval closer to edge when the minimum seems at the edge. */
        if (at_left_edge)
            xy[n++].x = 0.8*xy[0].x + 0.2*xy[1].x;
        else if (at_right_edge)
            xy[n++].x = 0.2*xy[1].x + 0.8*xy[2].x;
        else {
            /* Optimistic bisection of the larger interval, always try this point.
             * XXX: This is not very efficient, we can end up only improving the interval from the bisection side. */
            if (xy[1].x - xy[0].x >= xy[2].x - xy[1].x) {
                xy[n++].x = 0.2*xy[0].x + 0.8*xy[1].x;
                gwy_debug("bisect-left %.16g (0.2)", xy[n-1].x);
            }
            else {
                xy[n++].x = 0.8*xy[1].x + 0.2*xy[2].x;
                gwy_debug("bisect-right %.16g (0.8)", xy[n-1].x);
            }

            /* Parabolic interpolation, use if it yields distinct point inside the interval. */
            if (interpolate_parabolic(xy, &x)) {
                xeps = 1.2e-15*(fabs(x));
                if (x - xy[0].x > xeps && xy[2].x - x > xeps && fabs(x - xy[3].x) > xeps) {
                    xy[n++].x = x;
                    gwy_debug("parabolic %.16g (%g)", x, (x - xy[0].x)/(xy[2].x - xy[0].x));
                }
            }
        }

        /* Find the new three points bracketing the minimum.  We should not change state from non-edge to edge, but we
         * can change state from edge to non-edge. */
        for (i = 3; i < n; i++) {
            xy[i].y = function(xy[i].x, user_data);
            gwy_debug("point %.16g, value %.16g", xy[i].x, xy[i].y);
        }
        qsort(xy, n, sizeof(GwyXY), gwy_compare_double);

        if (!find_min_in_array(xy, n, &imin))
            return xy[imin].x;

        gwy_debug("minimum at %g, point #%u (%g)", xy[imin].x, imin, (xy[imin].x - xy[0].x)/(xy[n-1].x - xy[0].x));
        if (imin == 0)
            at_left_edge = TRUE;
        else if (imin == n-1) {
            at_right_edge = TRUE;
            memmove(xy, xy + n-3, 3*sizeof(GwyXY));
        }
        else
            memmove(xy, xy + imin-1, 3*sizeof(GwyXY));

        if (iter++ == 50)
            break;
    }

    return xy[1].x;
}

/**
 * gwy_math_histogram:
 * @values: Values to make histogram from.
 * @n: Number of values in @values.
 * @min: Minimum value to consider (left edge of histogram).
 * @max: Maximum value to consider (right edge of histogram).
 * @nbins: Number of histogram bins (number of @counts items), a positive number.
 * @counts: Array where to store the counts.
 *
 * Counts the numbers of values falling into equal-sized bins.
 *
 * The value of @min must not be larger than @max.  The values may lie outside [@min,@max].  They are not counted in
 * the histogram, nor the returned total.
 *
 * Rounding rules for values exactly at the edge of two bins are arbitrary and must not be relied upon.
 *
 * Returns: The number of values inside the entire histogram, i.e. at most @n but possibly a reduced count.
 **/
guint
gwy_math_histogram(const gdouble *values,
                   guint n,
                   gdouble min,
                   gdouble max,
                   guint nbins,
                   guint *counts)
{
    guint i, total = 0;
    gint nb;   /* Just a signed value */
    gdouble d;

    g_return_val_if_fail(nbins > 0, 0);
    g_return_val_if_fail(counts, 0);
    g_return_val_if_fail(values || !n, 0);
    g_return_val_if_fail(min <= max, 0);

    gwy_clear(counts, nbins);
    d = max - min;
    if (G_UNLIKELY(!(d > 0.0))) {
#ifdef _OPENMP
#pragma omp parallel for if(gwy_threads_are_enabled()) default(none) \
            reduction(+:total) \
            private(i) \
            shared(values,n,min)
#endif
        for (i = 0; i < n; i++) {
            if (values[i] == min)
                total++;
        }
        counts[0] = total;
        return total;
    }

    gwy_clear(counts, nbins);
    d = nbins/d;
    nb = nbins;
#ifdef _OPENMP
#pragma omp parallel if(gwy_threads_are_enabled()) default(none) \
            reduction(+:total) \
            private(i) \
            shared(counts,values,n,min,max,d,nb,nbins)
#endif
    {
        guint ifrom = gwy_omp_chunk_start(n), ito = gwy_omp_chunk_end(n);
        guint *tcounts = gwy_omp_if_threads_new0(counts, nbins);

        for (i = ifrom; i < ito; i++) {
            gdouble v = values[i];
            gint bi;

            if (v < min || v > max)
                continue;

            bi = (gint)floor((v - min)*d);
            if (G_LIKELY(bi >= 0 && bi < nb)) {
                tcounts[bi]++;
                total++;
            }
            else if (v == max) {
                tcounts[nbins-1]++;
                total++;
            }
        }
        gwy_omp_if_threads_sum_uint(counts, tcounts, nbins);
    }

    return total;
}

/**
 * gwy_xlnx_int:
 * @x: Value to calculate @x*log(@x) of.
 *
 * Calculates natural logarithm multiplied by the argument for integers.
 *
 * The value for zero @x is taken as the limit, i.e. zero.
 *
 * This function is useful for entropy calculations where values of @n*log(@n) can be evaulated a lot for small @n.
 * Therefore, values for small arguments are tabulated.  For large arguments the function is evaluated using the
 * standard log() function which is of course slower.
 *
 * Returns: Value of @x*log(@x).
 **/
gdouble
gwy_xlnx_int(guint x)
{
    static const gdouble xlnx_table[] = {
        0.0,
        0.0,
        1.38629436111989061882,
        3.29583686600432907417,
        5.54517744447956247532,
        8.04718956217050187300,
        10.75055681536833000486,
        13.62137104338719313570,
        16.63553233343868742600,
        19.77502119602597444511,
        23.02585092994045684010,
        26.37684800078207598466,
        29.81887979745600372264,
        33.34434164699997756865,
        36.94680261461362060328,
        40.62075301653315098985,
        44.36141955583649980256,
        48.16462684895567336408,
        52.02669164213096445960,
        55.94434060416236874000,
        59.91464547107981986860,
        63.93497119219188292650,
        68.00293397388294877634,
        72.11636696637044288840,
        76.27329192835069487136,
        80.47189562170501873000,
    };

    /* Take the fast path quickly.  The slow path is slow anyway. */
    if (G_LIKELY(x < G_N_ELEMENTS(xlnx_table)))
        return xlnx_table[x];

    return x*log(x);
}

/**
 * gwy_canonicalize_angle:
 * @phi: Angle to canonicalize, in radians.
 * @positive: %TRUE if a positive angle is requested, %FALSE for outputs symmetrical around zero.
 * @oriented: %TRUE for direction of a vector, %FALSE for the direction of a line (i.e. with no distinction between
 *            forward and backward direction).
 *
 * Canonicalizes an angle to requested interval.
 *
 * For @positive=%FALSE, @oriented=%FALSE the output interval is [-π/2,π/2].
 *
 * For @positive=%FALSE, @oriented=%TRUE the output interval is [-π,π].
 *
 * For @positive=%TRUE, @oriented=%FALSE the output interval is [0,π).
 *
 * For @positive=%TRUE, @oriented=%TRUE the output interval is [0,2π).
 *
 * Returns: Canonicalized angle, equivalent (in given sense) to @phi.
 **/
gdouble
gwy_canonicalize_angle(gdouble phi, gboolean positive, gboolean oriented)
{
    if (oriented) {
        /* This can give anything from -2π up to 2π because fmod is based on rounding to zero. */
        phi = fmod(phi, GWY_TWO_PI);
        if (positive)
            return phi < 0.0 ? fmax(phi + GWY_TWO_PI, 0.0) : phi;

        if (phi < -G_PI)
            return phi + GWY_TWO_PI;
        if (phi > G_PI)
            return phi - GWY_TWO_PI;
        return phi;
    }

    /* This can give anything from -π up to π because fmod is based on rounding to zero. */
    phi = fmod(phi, G_PI);
    if (positive)
        return phi < 0.0 ? fmax(phi + G_PI, 0.0) : phi;

    if (phi < -G_PI_2)
        return phi + G_PI;
    if (phi > G_PI_2)
        return phi - G_PI;
    return phi;
}

/**
 * SECTION: math
 * @title: Maths
 * @short_description: Mathematical utility functions
 * @see_also: #GwyNLFitter, non-linear least square fitter
 *
 * Function gwy_math_humanize_numbers() deals with number representation.
 *
 * Nearest object finding functions gwy_math_find_nearest_line() and gwy_math_find_nearest_point() can be useful in
 * widget and vector layer implementation.
 *
 * And gwy_math_lin_solve(), gwy_math_lin_solve_rewrite(), and gwy_math_fit_poly() are general purpose numeric
 * methods.
 **/

/**
 * GWY_ROUND:
 * @x: A double value.
 *
 * Rounds a floating point number to nearest integer.
 **/

/**
 * gwy_round:
 * @x: A double value.
 *
 * Rounds a floating point number to nearest integer.
 **/

/**
 * gwy_clamp:
 * @x: A double value.
 * @lower: Lower bound.
 * @upper: Upper bound.
 *
 * Clamps a floating point number to an interval.
 *
 * The behaviour is undefined if @lower is larger than @upper.
 *
 * The macro uses fmax() and fmin() and evaluates its arguments only once.
 **/

/**
 * GWY_SQRT3:
 *
 * The square root of 3.
 **/

/**
 * GWY_SQRT_PI:
 *
 * The square root of pi.
 **/

/**
 * GwyTransformDirection:
 * @GWY_TRANSFORM_DIRECTION_BACKWARD: Backward (inverse) transform.
 * @GWY_TRANSFORM_DIRECTION_FORWARD: Forward (direct) transform.
 *
 * Transform (namely integral transform) direction.
 *
 * In FFT, it is equal to sign of the exponent, that is the backward transform uses -1, the forward transform +1.
 * This is the opposite sign convention to FFTW (for instance), so care must be taken when mixing operations.
 **/

/**
 * GwyPercentileInterpolationType:
 * @GWY_PERCENTILE_INTERPOLATION_LINEAR: Linear interpolation of the two nearest values.
 * @GWY_PERCENTILE_INTERPOLATION_LOWER: Round the rank down to an integer.
 * @GWY_PERCENTILE_INTERPOLATION_HIGHER: Round the rank up to an integer.
 * @GWY_PERCENTILE_INTERPOLATION_NEAREST: Round the rank to nearest integer.
 * @GWY_PERCENTILE_INTERPOLATION_MIDPOINT: Average of the two nearest values.
 *
 * Type of interpolation for percentile calculation.
 *
 * The interpolations are used when the percentile does not correspond exactly to a rank.
 **/

/**
 * GwyRealFunc:
 * @x: Value to calculate the function for.
 * @user_data: (closure): User data passed to the driver function.
 *
 * Type of single-variable real function with user data.
 **/

/* 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 : */
