Files
tensor/signal/complex_refusal_pins_test.go
petrbalvin af4ee19703
Release / gates (push) Successful in 4m38s
Test / test (push) Successful in 5m16s
Release / release (push) Successful in 35s
feat: initial release
Assisted-by: GLM 5.3 Flash
2026-09-03 10:00:00 +02:00

120 lines
4.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: MIT
package signal
import (
"strings"
"testing"
"sourcedock.dev/petrbalvin/tensor/internal/core"
)
// Complex-refusal pins: complex inputs that reached FloatAt's nil-int
// branch, degenerate kernels and empty windows that answered NaN, and
// contract gaps the sibling entry points had already closed.
// TestResampleComplexRefusal: Resample and Decimate widened a complex
// series through widenFloats, which panicked instead of refusing.
func TestResampleComplexRefusal(t *testing.T) {
c := mustComplexes(t, []complex128{1 + 1i, 2, 3 - 1i, 4}, 4)
if _, err := Resample(c, 2, 1, 0); err == nil || !strings.Contains(err.Error(), "complex") {
t.Fatalf("Resample on complex: err = %v", err)
}
if _, err := Decimate(c, 2, 0); err == nil || !strings.Contains(err.Error(), "complex") {
t.Fatalf("Decimate on complex: err = %v", err)
}
}
// TestNUFFTComplexCoordinates: complex coordinates reached FloatAt's
// nil-int branch and panicked.
func TestNUFFTComplexCoordinates(t *testing.T) {
x := mustComplexes(t, []complex128{0.1 + 0.2i, -0.2}, 2)
c := mustComplexes(t, []complex128{1, 2i}, 2)
if _, err := NUFFTType1(x, c, 8); err == nil || !strings.Contains(err.Error(), "real") {
t.Fatalf("NUFFTType1 with complex coordinates: err = %v", err)
}
}
// TestAnalyticSignalComplexRefusal: the analytic signal is defined for
// a real series; a complex one was silently transformed.
func TestAnalyticSignalComplexRefusal(t *testing.T) {
c := mustComplexes(t, []complex128{1, 2, 3, 4}, 4)
if _, err := AnalyticSignal(c); err == nil || !strings.Contains(err.Error(), "complex") {
t.Fatalf("AnalyticSignal on complex: err = %v", err)
}
}
// TestConvComplexBiasRefusal: the convolutions gate the input and the
// kernel on dtype but widened the bias through FloatAt, whose complex
// branch reads a nil payload and panics. A complex bias is refused
// like a complex kernel.
func TestConvComplexBiasRefusal(t *testing.T) {
bias := mustComplexes(t, []complex128{1 + 2i}, 1)
in2 := mustFloats(t, []float64{1, 2, 3, 4, 5, 6, 7, 8}, 1, 1, 2, 4)
ker2 := mustFloats(t, []float64{1, 0, 0, 1}, 1, 1, 2, 2)
in3 := mustFloats(t, []float64{1, 2, 3, 4, 5, 6, 7, 8}, 1, 1, 2, 2, 2)
ker3 := mustFloats(t, []float64{1, 1, 1, 1, 1, 1, 1, 1}, 1, 1, 2, 2, 2)
in1 := mustFloats(t, []float64{1, 2, 3, 4}, 1, 1, 4)
ker1 := mustFloats(t, []float64{1, 1}, 1, 1, 2)
for name, fn := range map[string]func() (*core.Array, error){
"Conv1D": func() (*core.Array, error) { return Conv1D(in1, ker1, bias, 1, 0, 1) },
"Conv2D": func() (*core.Array, error) { return Conv2D(in2, ker2, bias, 1, 0) },
"Conv3D": func() (*core.Array, error) { return Conv3D(in3, ker3, bias, 1, [3]int{0, 0, 0}, [3]int{1, 1, 1}) },
"ConvTranspose2D": func() (*core.Array, error) { return ConvTranspose2D(in2, ker2, bias, 1, 0) },
} {
if _, err := fn(); err == nil || !strings.Contains(err.Error(), "complex bias") {
t.Fatalf("%s with a complex bias: err = %v", name, err)
}
}
}
// TestDecimateOneTap: taps = 1 divided by taps−1 = 0 in the Kaiser
// window and every output sample came back NaN; a one-tap kernel is
// the identity, so decimation keeps every factor-th sample.
func TestDecimateOneTap(t *testing.T) {
x := mustFromFloats(t, []float64{10, 11, 12, 13, 14, 15, 16, 17}, 8)
out, err := Decimate(x, 2, 1)
if err != nil {
t.Fatalf("Decimate taps=1: %v", err)
}
if out.Len() != 4 {
t.Fatalf("output length %d, want 4", out.Len())
}
for i := range 4 {
if got := out.FloatAt(i); got != float64(10+2*i) {
t.Fatalf("out[%d] = %v, want %d", i, got, 10+2*i)
}
}
}
// TestAdaptivePoolEmptySpatial: a zero-length spatial dimension left
// every window empty and max answered -Inf while avg divided by zero;
// both are errors now, in every rank.
func TestAdaptivePoolEmptySpatial(t *testing.T) {
empty1D, err := core.Zeros(core.Float, 1, 1, 0)
if err != nil {
t.Fatal(err)
}
empty2D, err := core.Zeros(core.Float, 1, 1, 0, 4)
if err != nil {
t.Fatal(err)
}
empty3D, err := core.Zeros(core.Float, 1, 1, 2, 0, 4)
if err != nil {
t.Fatal(err)
}
for name, fn := range map[string]func() (*core.Array, error){
"max1D": func() (*core.Array, error) { return AdaptiveMaxPool1D(empty1D, 2) },
"max2D": func() (*core.Array, error) { return AdaptiveMaxPool2D(empty2D, 2, 2) },
"max3D": func() (*core.Array, error) { return AdaptiveMaxPool3D(empty3D, 2, 2, 2) },
"avg1D": func() (*core.Array, error) { return AdaptiveAvgPool1D(empty1D, 2) },
"avg2D": func() (*core.Array, error) { return AdaptiveAvgPool2D(empty2D, 2, 2) },
"avg3D": func() (*core.Array, error) { return AdaptiveAvgPool3D(empty3D, 2, 2, 2) },
} {
if _, err := fn(); err == nil || !strings.Contains(err.Error(), "spatial") {
t.Fatalf("%s on an empty spatial dimension: err = %v", name, err)
}
}
}