/*
 *  $Id: math.c 29530 2026-02-23 17:47:11Z yeti-dn $
 *  Copyright (C) 2025-2026 David Nečas (Yeti).
 *  E-mail: yeti@gwyddion.net.
 *
 *  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 "tests/testlibgwy.h"

void
test_math_powi_zero(void)
{
    g_assert_cmpfloat(gwy_powi(1.0, 0), ==, 1.0);
    g_assert_cmpfloat(gwy_powi(0.25, 0), ==, 1.0);
    g_assert_cmpfloat(gwy_powi(-G_MAXDOUBLE, 0), ==, 1.0);
    // This is what pow() specifies so we should require it from powi.
    g_assert_cmpfloat(gwy_powi(0.0, 0), ==, 1.0);
}

void
test_math_powi_one(void)
{
    g_assert_cmpfloat(gwy_powi(1.0, 1), ==, 1.0);
    g_assert_cmpfloat(gwy_powi(1.0, -1), ==, 1.0);
    g_assert_cmpfloat(gwy_powi(1.0, 1753), ==, 1.0);
    g_assert_cmpfloat(gwy_powi(1.0, -486), ==, 1.0);
}

void
test_math_powi_basic(void)
{
    g_assert_cmpfloat_with_epsilon(gwy_powi(2.0, 2), 4.0, 4*DBL_EPSILON);
    g_assert_cmpfloat_with_epsilon(gwy_powi(2.0, -2), 0.25, DBL_EPSILON/4);
    g_assert_cmpfloat_with_epsilon(gwy_powi(-2.0, 2), 4.0, 4*DBL_EPSILON);
    g_assert_cmpfloat_with_epsilon(gwy_powi(-2.0, -2), 0.25, DBL_EPSILON/4);
    g_assert_cmpfloat_with_epsilon(gwy_powi(2.0, 3), 8.0, 8*DBL_EPSILON);
    g_assert_cmpfloat_with_epsilon(gwy_powi(2.0, -3), 0.125, DBL_EPSILON/8);
    g_assert_cmpfloat_with_epsilon(gwy_powi(-2.0, 3), -8.0, 8*DBL_EPSILON);
    g_assert_cmpfloat_with_epsilon(gwy_powi(-2.0, -3), -0.125, DBL_EPSILON/8);
    g_assert_cmpfloat_with_epsilon(gwy_powi(2.0, 4), 16.0, 16*DBL_EPSILON);
    g_assert_cmpfloat_with_epsilon(gwy_powi(2.0, -4), 0.0625, DBL_EPSILON/16);
    g_assert_cmpfloat_with_epsilon(gwy_powi(-2.0, 4), 16.0, 16*DBL_EPSILON);
    g_assert_cmpfloat_with_epsilon(gwy_powi(-2.0, -4), 0.0625, DBL_EPSILON/16);
    g_assert_cmpfloat_with_epsilon(gwy_powi(2.0, 5), 32.0, 32*DBL_EPSILON);
    g_assert_cmpfloat_with_epsilon(gwy_powi(2.0, -5), 0.03125, DBL_EPSILON/32);
    g_assert_cmpfloat_with_epsilon(gwy_powi(-2.0, 5), -32.0, 32*DBL_EPSILON);
    g_assert_cmpfloat_with_epsilon(gwy_powi(-2.0, -5), -0.03125, DBL_EPSILON/32);
}

void
test_math_deg2rad(void)
{
    g_assert_cmpfloat(gwy_deg2rad(0.0), ==, 0.0);
    g_assert_cmpfloat_with_epsilon(gwy_deg2rad(360.0), 2*G_PI, 6*DBL_EPSILON);
    g_assert_cmpfloat_with_epsilon(gwy_deg2rad(-45.0), -G_PI/4.0, DBL_EPSILON);
}

void
test_math_rad2deg(void)
{
    g_assert_cmpfloat(gwy_rad2deg(0.0), ==, 0.0);
    g_assert_cmpfloat_with_epsilon(gwy_rad2deg(2.0*G_PI), 360.0, 360*DBL_EPSILON);
    g_assert_cmpfloat_with_epsilon(gwy_rad2deg(-G_PI/3.0), -60.0, 60*DBL_EPSILON);
}

void
test_math_mean(void)
{
    static const gdouble data1[] = { -G_PI*1e106 };
    static const gdouble data2[] = { -1.0, 0.0, 4.0 };
    static const gdouble data3[] = { 2.0, 3.0, 4.0, HUGE_VAL };

    g_assert_cmpfloat(gwy_math_mean(data1, 1), ==, -G_PI*1e106);
    g_assert_cmpfloat(gwy_math_mean(data2, 3), ==, 1.0);
    g_assert_cmpfloat(gwy_math_mean(data3, 3), ==, 3.0);
}

void
test_math_sort_double(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint i = 0; i < n; i++) {
        guint size = g_test_rand_int_range(1, 1000);
        gdouble *data = g_new(gdouble, size);
        gdouble *data2 = g_new(gdouble, size);
        gdouble *data_orig = g_new(gdouble, size);

        for (guint k = 0; k < size; k++)
            data[k] = data_orig[k] = g_test_rand_double();

        gwy_math_sort(data, size);
        for (guint k = 1; k < size; k++)
            g_assert_cmpfloat(data[k-1], <=, data[k]);

        gwy_assign(data2, data, size);
        gwy_math_sort(data2, size);
        for (guint k = 0; k < size; k++)
            g_assert_cmpfloat(data2[k], ==, data[k]);

        // If the sorting does something weird like losing some values and overwriting them with others, it cannot
        // preserve the mean with random data.
        g_assert_cmpfloat_with_epsilon(gwy_math_mean(data, size), gwy_math_mean(data_orig, size), size*DBL_EPSILON);

        g_free(data2);
        g_free(data);
        g_free(data_orig);
    }
}

void
test_math_trimmed_mean(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint i = 0; i < n; i++) {
        guint size = g_test_rand_int_range(1, 1000);
        guint ntotal = g_test_rand_int_range(0, size);
        guint nlower = 0, nupper = 0;
        if (ntotal) {
            nlower = g_test_rand_int_range(0, ntotal);
            nupper = ntotal - nlower;
        }
        gdouble *data = g_new(gdouble, size);
        gdouble *data_orig = g_new(gdouble, size);

        for (guint k = 0; k < size; k++)
            data[k] = data_orig[k] = g_test_rand_double();

        gwy_math_sort(data_orig, size);
        gdouble ref_tmean = gwy_math_mean(data_orig + nlower, size - ntotal);
        gdouble tmean = gwy_math_trimmed_mean(data, size, nlower, nupper);
        g_assert_cmpfloat_with_epsilon(tmean, ref_tmean, size*DBL_EPSILON);
        gdouble tmean2 = gwy_math_trimmed_mean(data, size, nlower, nupper);
        g_assert_cmpfloat_with_epsilon(tmean2, tmean, sqrt(size)*DBL_EPSILON);

        // Check if we still have the same set of values (assuming sort works correctly).
        gwy_math_sort(data, size);
        for (guint k = 0; k < size; k++)
            g_assert_cmpfloat(data[k], ==, data_orig[k]);

        g_free(data);
        g_free(data_orig);
    }
}

void
test_math_curvature_apex_straight(void)
{
    gdouble k1, k2, phi1, phi2, xc, yc, zc;
    guint degree;

    // Function 1/2 × x² - 3/2 × y² + 0.3
    const gdouble coeffs1[6] = { 0.3, 0.0, 0.0, 1.0/2.0, 0.0, -3.0/2.0 };
    degree = gwy_math_curvature_at_apex(coeffs1, &k1, &k2, &phi1, &phi2, &xc, &yc, &zc);
    g_assert_cmpuint(degree, ==, 2);
    g_assert_cmpfloat_with_epsilon(k1, -3.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(k2, 1.0, 1e-12);
    if (fabs(phi1 + 0.5*G_PI) < 0.1)
        phi1 += G_PI;
    g_assert_cmpfloat_with_epsilon(phi1, 0.5*G_PI, 1e-12);
    g_assert_cmpfloat_with_epsilon(phi2, 0.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(xc, 0.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(yc, 0.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(zc, 0.3, 1e-12);

    // Function -(x - 1)² + 5(y + 2)² - 3
    const gdouble coeffs4[6] = { 16.0, 2.0, 20.0, -1.0, 0.0, 5.0 };
    degree = gwy_math_curvature_at_apex(coeffs4, &k1, &k2, &phi1, &phi2, &xc, &yc, &zc);
    g_assert_cmpuint(degree, ==, 2);
    g_assert_cmpfloat_with_epsilon(k1, -2.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(k2, 10.0, 1e-12);
    if (fabs(phi2 + 0.5*G_PI) < 0.1)
        phi2 += G_PI;
    g_assert_cmpfloat_with_epsilon(phi1, 0.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(phi2, 0.5*G_PI, 1e-12);
    g_assert_cmpfloat_with_epsilon(xc, 1.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(yc, -2.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(zc, -3.0, 1e-12);
}

void
test_math_curvature_apex_parabolic(void)
{
    gdouble k1, k2, phi1, phi2, xc, yc, zc;
    guint degree;

    // Function (x - εy + D)² + 1.5
    double eps1 = 0.1, D1 = 3.0;
    const gdouble coeffs1[6] = { D1*D1 + 1.5, 2.0*D1, -2*D1*eps1, 1.0, -2.0*eps1, eps1*eps1 };
    degree = gwy_math_curvature_at_apex(coeffs1, &k1, &k2, &phi1, &phi2, &xc, &yc, &zc);
    g_assert_cmpuint(degree, ==, 1);
    g_assert_cmpfloat_with_epsilon(k1, 0.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(k2, 2.0*(1.0 + eps1*eps1), 1e-12);
    g_assert_cmpfloat_with_epsilon(phi1, 0.5*G_PI - atan(eps1), 1e-12);
    g_assert_cmpfloat_with_epsilon(phi2, -atan(eps1), 1e-12);
    g_assert_cmpfloat_with_epsilon(xc, -D1/(1.0 + eps1*eps1), 1e-12);
    g_assert_cmpfloat_with_epsilon(yc, D1*eps1/(1.0 + eps1*eps1), 1e-12);
    g_assert_cmpfloat_with_epsilon(zc, 1.5, 1e-12);

    // Function (x + εy + D)² + 2.5
    double eps2 = 0.05, D2 = 1.25;
    const gdouble coeffs2[6] = { D2*D2 + 2.5, 2.0*D2, 2*D2*eps2, 1.0, 2.0*eps2, eps2*eps2 };
    degree = gwy_math_curvature_at_apex(coeffs2, &k1, &k2, &phi1, &phi2, &xc, &yc, &zc);
    g_assert_cmpuint(degree, ==, 1);
    g_assert_cmpfloat_with_epsilon(k1, 0.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(k2, 2.0*(1.0 + eps2*eps2), 1e-12);
    g_assert_cmpfloat_with_epsilon(phi1, -0.5*G_PI + atan(eps2), 1e-12);
    g_assert_cmpfloat_with_epsilon(phi2, atan(eps2), 1e-12);
    g_assert_cmpfloat_with_epsilon(xc, -D2/(1.0 + eps2*eps2), 1e-12);
    g_assert_cmpfloat_with_epsilon(yc, -D2*eps2/(1.0 + eps2*eps2), 1e-12);
    g_assert_cmpfloat_with_epsilon(zc, 2.5, 1e-12);

    // Function -(x - εy + D)² + 2.0
    double eps3 = 0.2, D3 = 2.0;
    const gdouble coeffs3[6] = { -D3*D3 - 1.0, -2.0*D3, 2*D3*eps3, -1.0, 2.0*eps3, -eps3*eps3 };
    degree = gwy_math_curvature_at_apex(coeffs3, &k1, &k2, &phi1, &phi2, &xc, &yc, &zc);
    g_assert_cmpuint(degree, ==, 1);
    g_assert_cmpfloat_with_epsilon(k1, -2.0*(1.0 + eps3*eps3), 1e-12);
    g_assert_cmpfloat_with_epsilon(k2, 0.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(phi1, -atan(eps3), 1e-12);
    g_assert_cmpfloat_with_epsilon(phi2, 0.5*G_PI - atan(eps3), 1e-12);
    g_assert_cmpfloat_with_epsilon(xc, -D3/(1.0 + eps3*eps3), 1e-12);
    g_assert_cmpfloat_with_epsilon(yc, D3*eps3/(1.0 + eps3*eps3), 1e-12);
    g_assert_cmpfloat_with_epsilon(zc, -1.0, 1e-12);

    // Function -(x + εy + D)² + 2.5
    double eps4 = 0.15, D4 = 2.5;
    const gdouble coeffs4[6] = { -D4*D4 + 5.5, -2.0*D4, 2*D4*eps4, -1.0, 2.0*eps4, -eps4*eps4 };
    degree = gwy_math_curvature_at_apex(coeffs4, &k1, &k2, &phi1, &phi2, &xc, &yc, &zc);
    g_assert_cmpuint(degree, ==, 1);
    g_assert_cmpfloat_with_epsilon(k1, -2.0*(1.0 + eps4*eps4), 1e-12);
    g_assert_cmpfloat_with_epsilon(k2, 0.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(phi1, -atan(eps4), 1e-12);
    g_assert_cmpfloat_with_epsilon(phi2, 0.5*G_PI - atan(eps4), 1e-12);
    g_assert_cmpfloat_with_epsilon(xc, -D4/(1.0 + eps4*eps4), 1e-12);
    g_assert_cmpfloat_with_epsilon(yc, D4*eps4/(1.0 + eps4*eps4), 1e-12);
    g_assert_cmpfloat_with_epsilon(zc, 5.5, 1e-12);

    // Function (εx - y + D)² - 2.5
    double eps5 = 0.125, D5 = -2.5;
    const gdouble coeffs5[6] = { D5*D5 - 2.5, 2.0*D5*eps5, -2.0*D5, eps5*eps5, -2.0*eps5, 1.0 };
    degree = gwy_math_curvature_at_apex(coeffs5, &k1, &k2, &phi1, &phi2, &xc, &yc, &zc);
    g_assert_cmpuint(degree, ==, 1);
    g_assert_cmpfloat_with_epsilon(k1, 0.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(k2, 2.0*(1.0 + eps5*eps5), 1e-12);
    g_assert_cmpfloat_with_epsilon(phi1, atan(eps5), 1e-12);
    g_assert_cmpfloat_with_epsilon(phi2, -0.5*G_PI + atan(eps5), 1e-12);
    g_assert_cmpfloat_with_epsilon(xc, -D5*eps5/(1.0 + eps5*eps5), 1e-12);
    g_assert_cmpfloat_with_epsilon(yc, D5/(1.0 + eps5*eps5), 1e-12);
    g_assert_cmpfloat_with_epsilon(zc, -2.5, 1e-12);

    // Function (εx + y + D)² + 4.0
    double eps6 = 0.11, D6 = 4.0;
    const gdouble coeffs6[6] = { D6*D6 + 4.0, 2.0*D6*eps6, 2.0*D6, eps6*eps6, 2.0*eps6, 1.0 };
    degree = gwy_math_curvature_at_apex(coeffs6, &k1, &k2, &phi1, &phi2, &xc, &yc, &zc);
    g_assert_cmpuint(degree, ==, 1);
    g_assert_cmpfloat_with_epsilon(k1, 0.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(k2, 2.0*(1.0 + eps6*eps6), 1e-12);
    g_assert_cmpfloat_with_epsilon(phi1, -atan(eps6), 1e-12);
    g_assert_cmpfloat_with_epsilon(phi2, 0.5*G_PI - atan(eps6), 1e-12);
    g_assert_cmpfloat_with_epsilon(xc, -D6*eps6/(1.0 + eps6*eps6), 1e-12);
    g_assert_cmpfloat_with_epsilon(yc, -D6/(1.0 + eps6*eps6), 1e-12);
    g_assert_cmpfloat_with_epsilon(zc, 4.0, 1e-12);

    // Function -(εx - y + D)² - 2.0
    double eps7 = 0.065, D7 = -2.0;
    const gdouble coeffs7[6] = { -D7*D7 - 2.0, -2.0*D7*eps7, 2.0*D7, -eps7*eps7, 2.0*eps7, -1.0 };
    degree = gwy_math_curvature_at_apex(coeffs7, &k1, &k2, &phi1, &phi2, &xc, &yc, &zc);
    g_assert_cmpuint(degree, ==, 1);
    g_assert_cmpfloat_with_epsilon(k1, -2.0*(1.0 + eps7*eps7), 1e-12);
    g_assert_cmpfloat_with_epsilon(k2, 0.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(phi1, -0.5*G_PI + atan(eps7), 1e-12);
    g_assert_cmpfloat_with_epsilon(phi2, atan(eps7), 1e-12);
    g_assert_cmpfloat_with_epsilon(xc, -D7*eps7/(1.0 + eps7*eps7), 1e-12);
    g_assert_cmpfloat_with_epsilon(yc, D7/(1.0 + eps7*eps7), 1e-12);
    g_assert_cmpfloat_with_epsilon(zc, -2.0, 1e-12);

    // Function -(εx + y + D)² + 1.6
    double eps8 = 0.11, D8 = 1.6;
    const gdouble coeffs8[6] = { -D8*D8 + 1.6, -2.0*D8*eps8, -2.0*D8, -eps8*eps8, -2.0*eps8, -1.0 };
    degree = gwy_math_curvature_at_apex(coeffs8, &k1, &k2, &phi1, &phi2, &xc, &yc, &zc);
    g_assert_cmpuint(degree, ==, 1);
    g_assert_cmpfloat_with_epsilon(k1, -2.0*(1.0 + eps8*eps8), 1e-12);
    g_assert_cmpfloat_with_epsilon(k2, 0.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(phi2, -atan(eps8), 1e-12);
    g_assert_cmpfloat_with_epsilon(phi1, 0.5*G_PI - atan(eps8), 1e-12);
    g_assert_cmpfloat_with_epsilon(xc, -D8*eps8/(1.0 + eps8*eps8), 1e-12);
    g_assert_cmpfloat_with_epsilon(yc, -D8/(1.0 + eps8*eps8), 1e-12);
    g_assert_cmpfloat_with_epsilon(zc, 1.6, 1e-12);
}

void
test_math_curvature_apex_askew(void)
{
    gdouble k1, k2, phi1, phi2, xc, yc, zc;
    guint degree;

    // Function 3x² + 4y² rotated by 30 degrees clockwise.
    const gdouble coeffs1[6] = { 0.0, 0.0, 0.0, 13.0/4.0, GWY_SQRT3/2.0, 15.0/4.0 };
    degree = gwy_math_curvature_at_apex(coeffs1, &k1, &k2, &phi1, &phi2, &xc, &yc, &zc);
    g_assert_cmpuint(degree, ==, 2);
    g_assert_cmpfloat_with_epsilon(k1, 6.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(k2, 8.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(phi1, -G_PI/6.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(phi2, G_PI/3.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(xc, 0.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(yc, 0.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(zc, 0.0, 1e-12);

    // The same function, but also shifted to (-2,1) and to height 4.1.
    const gdouble coeffs2[6] = {
        coeffs1[0] + 13.0 - GWY_SQRT3 + 15.0/4.0 + 4.1,
        coeffs1[1] + 13.0 - 0.5*GWY_SQRT3,
        coeffs1[2] + GWY_SQRT3 - 15.0/2.0,
        coeffs1[3], coeffs1[4], coeffs1[5],
    };
    degree = gwy_math_curvature_at_apex(coeffs2, &k1, &k2, &phi1, &phi2, &xc, &yc, &zc);
    g_assert_cmpuint(degree, ==, 2);
    g_assert_cmpfloat_with_epsilon(k1, 6.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(k2, 8.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(phi1, -G_PI/6.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(phi2, G_PI/3.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(xc, -2.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(yc, 1.0, 1e-12);
    g_assert_cmpfloat_with_epsilon(zc, 4.1, 1e-12);
}

void
test_math_is_in_polygon_square(void)
{
    const gdouble square_cw[8] = { 1, 1, -1, 1, -1, -1, 1, -1 };
    g_assert_true(gwy_math_is_in_polygon(0.0, 0.0, square_cw, 4));
    g_assert_true(gwy_math_is_in_polygon(0.999, 0.999, square_cw, 4));
    g_assert_true(gwy_math_is_in_polygon(-0.999, 0.999, square_cw, 4));
    g_assert_true(gwy_math_is_in_polygon(0.999, -0.999, square_cw, 4));
    g_assert_true(gwy_math_is_in_polygon(-0.999, -0.999, square_cw, 4));
    g_assert_false(gwy_math_is_in_polygon(1.001, -0.999, square_cw, 4));
    g_assert_false(gwy_math_is_in_polygon(1.001, 0.999, square_cw, 4));
    g_assert_false(gwy_math_is_in_polygon(0.0, 1.001, square_cw, 4));

    const gdouble square_acw[8] = { -1, -1, 1, -1, 1, 1, -1, 1 };
    g_assert_true(gwy_math_is_in_polygon(0.0, 0.0, square_acw, 4));
    g_assert_true(gwy_math_is_in_polygon(0.999, 0.999, square_acw, 4));
    g_assert_true(gwy_math_is_in_polygon(-0.999, 0.999, square_acw, 4));
    g_assert_true(gwy_math_is_in_polygon(0.999, -0.999, square_acw, 4));
    g_assert_true(gwy_math_is_in_polygon(-0.999, -0.999, square_acw, 4));
    g_assert_false(gwy_math_is_in_polygon(1.001, -0.999, square_acw, 4));
    g_assert_false(gwy_math_is_in_polygon(1.001, 0.999, square_acw, 4));
    g_assert_false(gwy_math_is_in_polygon(0.0, 1.001, square_acw, 4));
}

void
test_math_is_in_polygon_bowtie(void)
{
    const gdouble bowtie[8] = { 1, 1, -1, -1, -1, 1, 1, -1 };
    g_assert_true(gwy_math_is_in_polygon(0.999, 0.998, bowtie, 4));
    g_assert_true(gwy_math_is_in_polygon(-0.999, 0.998, bowtie, 4));
    g_assert_true(gwy_math_is_in_polygon(0.999, -0.998, bowtie, 4));
    g_assert_true(gwy_math_is_in_polygon(-0.999, -0.998, bowtie, 4));
    g_assert_true(gwy_math_is_in_polygon(0.001, 0.0, bowtie, 4));
    g_assert_true(gwy_math_is_in_polygon(-0.001, 0.0, bowtie, 4));
    g_assert_false(gwy_math_is_in_polygon(1.001, -0.999, bowtie, 4));
    g_assert_false(gwy_math_is_in_polygon(1.001, 0.999, bowtie, 4));
    g_assert_false(gwy_math_is_in_polygon(0.0, 0.001, bowtie, 4));
    g_assert_false(gwy_math_is_in_polygon(0.0, -0.001, bowtie, 4));
}

void
test_math_refine_maximum_1d(void)
{
    guint n = g_test_slow() ? 100 : 10;

    for (guint itr = 0; itr < n; itr++) {
        gdouble c = g_test_rand_double_range(-0.999, 0.999);
        gdouble a = g_test_rand_double_range(0.1, 10.0);
        gdouble y0 = g_test_rand_double_range(0.1, 10.0);
        gdouble y[3];
        for (gint i = -1; i <= 1; i++)
            y[i + 1] = -a*(c - i)*(c - i) + y0;
        gdouble x = G_MAXDOUBLE;
        g_assert_true(gwy_math_refine_maximum_1d(y, &x));
        g_assert_cmpfloat_with_epsilon(x, c, 20*DBL_EPSILON);
    }

    for (guint itr = 0; itr < n; itr++) {
        gdouble c = (g_test_rand_int() % 2 ? -1 : 1)*g_test_rand_double_range(1.001, 2.0);
        gdouble a = g_test_rand_double_range(0.1, 10.0);
        gdouble y0 = g_test_rand_double_range(0.1, 10.0);
        gdouble y[3];
        for (gint i = -1; i <= 1; i++)
            y[i + 1] = -a*(c - i)*(c - i) + y0;
        gdouble x;
        g_assert_false(gwy_math_refine_maximum_1d(y, &x));
        g_assert_cmpfloat(x, ==, 0.0);
    }

    gdouble y[3] = { G_PI, G_PI, G_PI };
    gdouble x;
    g_assert_false(gwy_math_refine_maximum_1d(y, &x));
    g_assert_cmpfloat(x, ==, 0.0);
}

void
test_math_refine_maximum_2d(void)
{
    guint n = g_test_slow() ? 100 : 10;

    for (guint itr = 0; itr < n; itr++) {
        gdouble cx = g_test_rand_double_range(-0.999, 0.999);
        gdouble cy = g_test_rand_double_range(-0.999, 0.999);
        gdouble ax = g_test_rand_double_range(0.1, 10.0);
        gdouble ay = g_test_rand_double_range(0.1, 10.0);
        gdouble z0 = g_test_rand_double_range(0.1, 10.0);
        gdouble z[9];
        for (gint i = -1; i <= 1; i++) {
            for (gint j = -1; j <= 1; j++)
                z[(i + 1)*3 + j + 1] = -ax*(cx - j)*(cx - j) - ay*(cy - i)*(cy - i) + z0;
        }
        gdouble x = G_MAXDOUBLE, y = G_MAXDOUBLE;
        g_assert_true(gwy_math_refine_maximum_2d(z, &x, &y));
        g_assert_cmpfloat_with_epsilon(x, cx, 100*DBL_EPSILON);
        g_assert_cmpfloat_with_epsilon(y, cy, 100*DBL_EPSILON);
    }

    for (guint itr = 0; itr < n; itr++) {
        gdouble cx, cy;

        do {
            cx = g_test_rand_double_range(-2.5, 2.5);
            cy = g_test_rand_double_range(-2.5, 2.5);
        } while (cx*cx + cy*cy <= 2.001);

        gdouble ax = g_test_rand_double_range(0.1, 10.0);
        gdouble ay = g_test_rand_double_range(0.1, 10.0);
        gdouble z0 = g_test_rand_double_range(0.1, 10.0);
        gdouble z[9];
        for (gint i = -1; i <= 1; i++) {
            for (gint j = -1; j <= 1; j++)
                z[(i + 1)*3 + j + 1] = -ax*(cx - j)*(cx - j) - ay*(cy - i)*(cy - i) + z0;
        }
        gdouble x, y;
        g_assert_false(gwy_math_refine_maximum_2d(z, &x, &y));
        g_assert_cmpfloat(x, ==, 0.0);
        g_assert_cmpfloat(y, ==, 0.0);
    }

    gdouble z[9] = {
        G_PI, G_PI, G_PI,
        G_PI, G_PI, G_PI,
        G_PI, G_PI, G_PI,
    };
    gdouble x, y;
    g_assert_false(gwy_math_refine_maximum_2d(z, &x, &y));
    g_assert_cmpfloat(x, ==, 0.0);
    g_assert_cmpfloat(y, ==, 0.0);
}

void
test_math_accumulate_counts(void)
{
    guint data1[] = { 1, 0, 2, 1, 0, 0, 5 };
    const guint expected1[] = { 1, 1, 3, 4, 4, 4, 9 };
    gwy_accumulate_counts(data1, G_N_ELEMENTS(data1), FALSE);

    for (guint i = 0; i < G_N_ELEMENTS(data1); i++)
        g_assert_cmpuint(data1[i], ==, expected1[i]);

    guint data2[] = { 3, 0, 0, 1, 1, 4, 7, 123456 };
    const guint expected2[] = { 0, 3, 3, 3, 4, 5, 9, 16 };
    gwy_accumulate_counts(data2, G_N_ELEMENTS(data2)-1, TRUE);

    for (guint i = 0; i < G_N_ELEMENTS(data2); i++)
        g_assert_cmpuint(data2[i], ==, expected2[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 : */
