113 lines
3.0 KiB
Go
113 lines
3.0 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integrate
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
)
|
|
|
|
// cubHeapPop takes the heap's head the way IntegrateND does: the last
|
|
// box replaces the root and sifts down.
|
|
func cubHeapPop(h []*cubBox) ([]*cubBox, *cubBox) {
|
|
top := h[0]
|
|
last := len(h) - 1
|
|
h[0] = h[last]
|
|
h[last] = nil
|
|
h = h[:last]
|
|
if last > 0 {
|
|
cubSiftDown(h)
|
|
}
|
|
return h, top
|
|
}
|
|
|
|
// TestCubatureBoxHeapOrder pins the order the box heap pops in, the
|
|
// substance of the heap replacing the linear scan: the largest finite
|
|
// estimate first, the earliest insertion among equals, and a
|
|
// non-finite estimate, which only an overflowed measure produces,
|
|
// below every finite one.
|
|
func TestCubatureBoxHeapOrder(t *testing.T) {
|
|
t.Run("larger estimate first", func(t *testing.T) {
|
|
h := []*cubBox{{est: 5, seq: 1}, {est: 1, seq: 2}, {est: math.NaN(), seq: 3}, {est: 3, seq: 4}}
|
|
for i := range h {
|
|
cubSiftUp(h[:i+1])
|
|
}
|
|
for _, want := range []float64{5, 3, 1} {
|
|
var top *cubBox
|
|
h, top = cubHeapPop(h)
|
|
if top.est != want {
|
|
t.Fatalf("popped estimate %v, want %v", top.est, want)
|
|
}
|
|
}
|
|
if h[0].est == h[0].est {
|
|
t.Fatalf("a finite estimate %v survived before the non-finite one", h[0].est)
|
|
}
|
|
})
|
|
t.Run("earliest insertion among equals", func(t *testing.T) {
|
|
h := []*cubBox{{est: 2, seq: 2}, {est: 2, seq: 0}, {est: 2, seq: 3}, {est: 2, seq: 1}}
|
|
for i := range h {
|
|
cubSiftUp(h[:i+1])
|
|
}
|
|
for _, want := range []int{0, 1, 2, 3} {
|
|
var top *cubBox
|
|
h, top = cubHeapPop(h)
|
|
if top.seq != want {
|
|
t.Fatalf("popped insertion %d, want %d", top.seq, want)
|
|
}
|
|
}
|
|
})
|
|
t.Run("interleaved push and pop", func(t *testing.T) {
|
|
h := []*cubBox{{est: 5, seq: 0}}
|
|
for _, b := range []*cubBox{{est: 7, seq: 1}, {est: 6, seq: 2}} {
|
|
h = append(h, b)
|
|
cubSiftUp(h)
|
|
}
|
|
var top *cubBox
|
|
h, top = cubHeapPop(h)
|
|
if top.est != 7 {
|
|
t.Fatalf("popped estimate %v, want 7", top.est)
|
|
}
|
|
h = append(h, &cubBox{est: 4, seq: 3})
|
|
cubSiftUp(h)
|
|
h, top = cubHeapPop(h)
|
|
if top.est != 6 {
|
|
t.Fatalf("popped estimate %v, want 6", top.est)
|
|
}
|
|
h, top = cubHeapPop(h)
|
|
if top.est != 5 {
|
|
t.Fatalf("popped estimate %v, want 5", top.est)
|
|
}
|
|
})
|
|
t.Run("overflowed estimates sort below every finite one", func(t *testing.T) {
|
|
// An overflowed measure can carry +Inf, and a corrupted one a
|
|
// NaN: both belong at the bottom of the heap, and the insertion
|
|
// order holds between them.
|
|
h := []*cubBox{
|
|
{est: math.Inf(1), seq: 0},
|
|
{est: 2, seq: 1},
|
|
{est: math.NaN(), seq: 2},
|
|
{est: 4, seq: 3},
|
|
{est: math.Inf(-1), seq: 4},
|
|
{est: 3, seq: 5},
|
|
}
|
|
for i := range h {
|
|
cubSiftUp(h[:i+1])
|
|
}
|
|
for _, want := range []float64{4, 3, 2} {
|
|
var top *cubBox
|
|
h, top = cubHeapPop(h)
|
|
if top.est != want {
|
|
t.Fatalf("popped estimate %v, want %v", top.est, want)
|
|
}
|
|
}
|
|
for _, want := range []int{0, 2, 4} {
|
|
var top *cubBox
|
|
h, top = cubHeapPop(h)
|
|
if top.seq != want {
|
|
t.Fatalf("popped insertion %d, want %d", top.seq, want)
|
|
}
|
|
}
|
|
})
|
|
}
|