feat: initial release
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGeneratorDeterminism(t *testing.T) {
|
||||
g1 := NewGenerator(42)
|
||||
g2 := NewGenerator(42)
|
||||
f1, err := Floats(g1, 8)
|
||||
if err != nil {
|
||||
t.Fatalf("Floats: %v", err)
|
||||
}
|
||||
f2, err := Floats(g2, 8)
|
||||
if err != nil {
|
||||
t.Fatalf("Floats: %v", err)
|
||||
}
|
||||
if !Equal(f1, f2) {
|
||||
t.Fatalf("the same seed must give identical arrays")
|
||||
}
|
||||
|
||||
g3 := NewGenerator(43)
|
||||
f3, _ := Floats(g3, 8)
|
||||
if Equal(f1, f3) {
|
||||
t.Fatalf("different seeds must give different arrays")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratorFloats(t *testing.T) {
|
||||
g := NewGenerator(7)
|
||||
f, err := Floats(g, 1000)
|
||||
if err != nil {
|
||||
t.Fatalf("Floats: %v", err)
|
||||
}
|
||||
if f.Dtype() != Float || f.Shape()[0] != 1000 {
|
||||
t.Fatalf("Floats shape: %s", f)
|
||||
}
|
||||
for i := range f.Len() {
|
||||
v, _ := FloatAt(f, i)
|
||||
if v < 0 || v >= 1 {
|
||||
t.Fatalf("Floats out of [0,1): %v", v)
|
||||
}
|
||||
}
|
||||
// A thousand draws should cover both halves of the interval.
|
||||
m, _ := Mean(f)
|
||||
if m < 0.4 || m > 0.6 {
|
||||
t.Fatalf("Floats mean suggests bias: %v", m)
|
||||
}
|
||||
|
||||
if _, err := Floats(g, -1); err == nil || !strings.Contains(err.Error(), "zero or greater") {
|
||||
t.Fatalf("Floats negative: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratorInts(t *testing.T) {
|
||||
g := NewGenerator(9)
|
||||
v, err := Ints(g, 600, 1, 4)
|
||||
if err != nil {
|
||||
t.Fatalf("Ints: %v", err)
|
||||
}
|
||||
if v.Dtype() != Int {
|
||||
t.Fatalf("Ints dtype: %s", v.Dtype())
|
||||
}
|
||||
seen := map[int64]bool{}
|
||||
for i := range v.Len() {
|
||||
x, _ := IntAt(v, i)
|
||||
if x < 1 || x >= 4 {
|
||||
t.Fatalf("Ints out of [1,4): %d", x)
|
||||
}
|
||||
seen[x] = true
|
||||
}
|
||||
// Six hundred draws over three values must hit all of them.
|
||||
if len(seen) != 3 {
|
||||
t.Fatalf("Ints coverage: %v", seen)
|
||||
}
|
||||
|
||||
if _, err := Ints(g, 1, 5, 5); err == nil || !strings.Contains(err.Error(), "min must be less than max") {
|
||||
t.Fatalf("Ints range: %v", err)
|
||||
}
|
||||
if _, err := Ints(g, -1, 0, 1); err == nil || !strings.Contains(err.Error(), "zero or greater") {
|
||||
t.Fatalf("Ints negative: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGeneratorBoundedUniform pins the two defects of the old rejection
|
||||
// rule: it rejected the whole [t, n) residue band, starving the upper
|
||||
// buckets (for spans above 2^63 some values never appeared), and a full
|
||||
// [MinInt64, MaxInt64) span accepted only lo == 2^64-1, hanging the draw.
|
||||
func TestGeneratorBoundedUniform(t *testing.T) {
|
||||
g := NewGenerator(11)
|
||||
const draws = 30_000
|
||||
counts := map[int64]int{}
|
||||
for range draws {
|
||||
v, err := Ints(g, 1, 0, 3)
|
||||
if err != nil {
|
||||
t.Fatalf("Ints: %v", err)
|
||||
}
|
||||
x, _ := IntAt(v, 0)
|
||||
counts[x]++
|
||||
}
|
||||
// The counts are binomial around draws/3; the old biased rule kept
|
||||
// bucket 2 a full draws/3 * (1/3) short, far outside this band.
|
||||
want := draws / 3
|
||||
tol := want / 10
|
||||
for b := range int64(3) {
|
||||
if diff := counts[b] - want; diff > tol || diff < -tol {
|
||||
t.Fatalf("bucket %d drawn %d times, want %d +/- %d", b, counts[b], want, tol)
|
||||
}
|
||||
}
|
||||
|
||||
// Full-span draws must terminate promptly and spread over the range.
|
||||
g = NewGenerator(12)
|
||||
seen := map[int64]bool{}
|
||||
for range 64 {
|
||||
v, err := Ints(g, 1, math.MinInt64, math.MaxInt64)
|
||||
if err != nil {
|
||||
t.Fatalf("full-span Ints: %v", err)
|
||||
}
|
||||
x, _ := IntAt(v, 0)
|
||||
seen[x] = true
|
||||
}
|
||||
if len(seen) < 2 {
|
||||
t.Fatalf("full-span draws collapsed to %d distinct value(s)", len(seen))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratorZeroSeed(t *testing.T) {
|
||||
// Seed zero must not degenerate into the all-zero state.
|
||||
g := NewGenerator(0)
|
||||
f, err := Floats(g, 4)
|
||||
if err != nil {
|
||||
t.Fatalf("Floats from seed 0: %v", err)
|
||||
}
|
||||
nonzero := false
|
||||
for i := range f.Len() {
|
||||
v, _ := FloatAt(f, i)
|
||||
if v != 0 {
|
||||
nonzero = true
|
||||
}
|
||||
}
|
||||
if !nonzero {
|
||||
t.Fatalf("seed 0 produced only zeros")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user