Files
consensus/ai/models_test.go
zeekay 8e45bcd029 refactor: Go-idiomatic package layout
Reorganize consensus packages following Go conventions and best practices:

Protocol Consolidation:
- Move prism/, photon/, wave/ → protocol/ directory
- Consolidate all protocol implementations under single namespace
- Follows standard library pattern (encoding/json, encoding/xml)

Package Naming:
- Rename validators/ → validator/ (singular, lowercase per Go convention)
- Aligns with stdlib patterns (net/http, database/sql)

Import Path Updates:
- Update all 241 Go files with new import paths
- github.com/luxfi/consensus/prism → protocol/prism
- github.com/luxfi/consensus/photon → protocol/photon
- github.com/luxfi/consensus/wave → protocol/wave
- github.com/luxfi/consensus/validators → validator

Benefits:
- Clear separation: core (contracts) vs protocol (algorithms) vs engine (runtime)
- Improved discoverability (all protocols under protocol/)
- Consistent with Go naming conventions
- Zero breaking changes to core APIs
- Easier to add new protocol implementations

Test Results:
- All 67+ tests passing across 21 packages
- 100% test coverage maintained
- No performance regression

Structure:
consensus/
├── core/           # Pure contracts (interfaces, zero deps)
├── protocol/       # Consensus algorithms & mechanics
│   ├── prism/      # Polling/quorum primitives
│   ├── photon/     # Unary consensus
│   ├── wave/       # N-ary consensus (FPC)
│   └── ...         # Other protocol phases
├── engine/         # Runtime glue (runs protocols)
└── validator/      # Validator management (singular)
2025-11-06 04:19:26 -08:00

951 lines
21 KiB
Go

// Copyright (C) 2025, Lux Industries Inc All rights reserved.
// Comprehensive Tests for AI Models
package ai
import (
"context"
"testing"
"time"
)
// Mock FeatureExtractor for BlockData
type mockBlockFeatureExtractor struct{}
func (e *mockBlockFeatureExtractor) Extract(data BlockData) map[string]float64 {
return map[string]float64{
"height": float64(data.Height),
"tx_count": float64(len(data.Transactions)),
"size": float64(data.Size),
"gas_used": float64(data.GasUsed),
}
}
func (e *mockBlockFeatureExtractor) Names() []string {
return []string{"height", "tx_count", "size", "gas_used"}
}
// === SimpleModel Tests ===
func TestNewSimpleModel(t *testing.T) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
if model == nil {
t.Fatal("NewSimpleModel returned nil")
}
if model.nodeID != "node-1" {
t.Errorf("Expected nodeID 'node-1', got '%s'", model.nodeID)
}
if model.learningRate != 0.01 {
t.Errorf("Expected learning rate 0.01, got %f", model.learningRate)
}
if model.bias != 0.0 {
t.Error("Initial bias should be 0.0")
}
if len(model.weights) != 0 {
t.Error("Initial weights should be empty")
}
if len(model.history) != 0 {
t.Error("Initial history should be empty")
}
}
func TestSimpleModelDecide(t *testing.T) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
// Set some weights
model.weights["height"] = 0.5
model.weights["tx_count"] = 1.0
model.bias = 0.1
data := BlockData{
Height: 100,
Transactions: make([]string, 10),
Size: 2048,
GasUsed: 50000,
Timestamp: time.Now(),
}
ctx := context.Background()
decision, err := model.Decide(ctx, data, make(map[string]interface{}))
if err != nil {
t.Fatalf("Decide failed: %v", err)
}
if decision == nil {
t.Fatal("Decision is nil")
}
if decision.ID == "" {
t.Error("Decision ID not set")
}
if decision.ProposerID != "node-1" {
t.Errorf("Expected proposer 'node-1', got '%s'", decision.ProposerID)
}
if decision.Confidence < 0 || decision.Confidence > 1 {
t.Errorf("Confidence should be between 0 and 1, got %f", decision.Confidence)
}
if decision.Action != "approve" && decision.Action != "reject" {
t.Errorf("Unexpected action: %s", decision.Action)
}
if decision.Reasoning == "" {
t.Error("Reasoning should not be empty")
}
if decision.Timestamp.IsZero() {
t.Error("Timestamp not set")
}
}
func TestSimpleModelDecide_ApproveAction(t *testing.T) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
// Set weights that will result in positive score
model.weights["tx_count"] = 1.0
model.bias = 10.0 // Strong positive bias
data := BlockData{
Height: 100,
Transactions: make([]string, 10),
Size: 2048,
GasUsed: 50000,
Timestamp: time.Now(),
}
ctx := context.Background()
decision, err := model.Decide(ctx, data, nil)
if err != nil {
t.Fatalf("Decide failed: %v", err)
}
if decision.Action != "approve" {
t.Errorf("Expected action 'approve', got '%s'", decision.Action)
}
}
func TestSimpleModelDecide_RejectAction(t *testing.T) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
// Set weights that will result in negative score
model.weights["tx_count"] = -1.0
model.bias = -10.0 // Strong negative bias
data := BlockData{
Height: 100,
Transactions: make([]string, 10),
Size: 2048,
GasUsed: 50000,
Timestamp: time.Now(),
}
ctx := context.Background()
decision, err := model.Decide(ctx, data, nil)
if err != nil {
t.Fatalf("Decide failed: %v", err)
}
if decision.Action != "reject" {
t.Errorf("Expected action 'reject', got '%s'", decision.Action)
}
}
func TestSimpleModelProposeDecision(t *testing.T) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
data := BlockData{
Height: 100,
Transactions: make([]string, 10),
Size: 2048,
GasUsed: 50000,
Timestamp: time.Now(),
}
ctx := context.Background()
proposal, err := model.ProposeDecision(ctx, data)
if err != nil {
t.Fatalf("ProposeDecision failed: %v", err)
}
if proposal == nil {
t.Fatal("Proposal is nil")
}
if proposal.ID == "" {
t.Error("Proposal ID not set")
}
if proposal.NodeID != "node-1" {
t.Errorf("Expected node 'node-1', got '%s'", proposal.NodeID)
}
if proposal.Decision == nil {
t.Fatal("Proposal decision is nil")
}
if len(proposal.Evidence) == 0 {
t.Error("Proposal should have evidence")
}
if proposal.Weight != 1.0 {
t.Errorf("Expected weight 1.0, got %f", proposal.Weight)
}
if proposal.Timestamp.IsZero() {
t.Error("Proposal timestamp not set")
}
}
func TestSimpleModelLearn(t *testing.T) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
blockData := BlockData{
Height: 100,
Transactions: make([]string, 10),
Size: 2048,
GasUsed: 50000,
Timestamp: time.Now(),
}
example := TrainingExample[BlockData]{
Input: blockData,
Output: Decision[BlockData]{
ID: "test-decision",
Action: "approve",
Data: blockData,
Confidence: 0.9,
Reasoning: "Test decision",
Context: make(map[string]interface{}),
Timestamp: time.Now(),
ProposerID: "node-1",
},
Feedback: 1.0, // Positive feedback
NodeID: "node-1",
Weight: 1.0,
Context: make(map[string]interface{}),
}
initialBias := model.bias
initialWeights := make(map[string]float64)
for k, v := range model.weights {
initialWeights[k] = v
}
examples := []TrainingExample[BlockData]{example}
model.Learn(examples)
// Weights should have changed
if model.bias == initialBias && len(model.weights) == 0 {
t.Error("Model weights should have been updated")
}
// History should contain the example
if len(model.history) != 1 {
t.Errorf("Expected 1 history entry, got %d", len(model.history))
}
}
func TestSimpleModelLearnBatch(t *testing.T) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
blockData1 := BlockData{
Height: 100,
Transactions: make([]string, 10),
Size: 2048,
GasUsed: 50000,
Timestamp: time.Now(),
}
blockData2 := BlockData{
Height: 200,
Transactions: make([]string, 5),
Size: 1024,
GasUsed: 25000,
Timestamp: time.Now(),
}
examples := []TrainingExample[BlockData]{
{
Input: blockData1,
Output: Decision[BlockData]{
ID: "decision-1",
Action: "approve",
Data: blockData1,
Confidence: 0.9,
Reasoning: "Good block",
Context: make(map[string]interface{}),
Timestamp: time.Now(),
ProposerID: "node-1",
},
Feedback: 1.0,
NodeID: "node-1",
Weight: 1.0,
Context: make(map[string]interface{}),
},
{
Input: blockData2,
Output: Decision[BlockData]{
ID: "decision-2",
Action: "reject",
Data: blockData2,
Confidence: 0.8,
Reasoning: "Suspicious block",
Context: make(map[string]interface{}),
Timestamp: time.Now(),
ProposerID: "node-1",
},
Feedback: -1.0,
NodeID: "node-1",
Weight: 1.0,
Context: make(map[string]interface{}),
},
}
model.Learn(examples)
if len(model.history) != 2 {
t.Errorf("Expected 2 history entries, got %d", len(model.history))
}
}
func TestSimpleModelGetWeights(t *testing.T) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
model.weights["feature1"] = 0.5
model.weights["feature2"] = 1.0
model.bias = 0.3
weights := model.GetWeights()
if len(weights) != 2 {
t.Errorf("Expected 2 weights, got %d", len(weights))
}
if weights["feature1"] != 0.5 {
t.Error("Weight not correctly retrieved")
}
}
func TestSimpleModelSetWeights(t *testing.T) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
newWeights := map[string]float64{
"feature1": 2.0,
"feature2": 3.0,
}
model.SetWeights(newWeights)
if model.weights["feature1"] != 2.0 {
t.Error("Weights not properly set")
}
if model.weights["feature2"] != 3.0 {
t.Error("Weights not properly set")
}
}
func TestSimpleModelGetHistory(t *testing.T) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
// Add some history
blockData := BlockData{
Height: 100,
Transactions: make([]string, 10),
Size: 2048,
GasUsed: 50000,
Timestamp: time.Now(),
}
example := TrainingExample[BlockData]{
Input: blockData,
Output: Decision[BlockData]{
ID: "test-decision",
Action: "approve",
Data: blockData,
Confidence: 0.9,
Reasoning: "Test",
Context: make(map[string]interface{}),
Timestamp: time.Now(),
ProposerID: "node-1",
},
Feedback: 1.0,
NodeID: "node-1",
Weight: 1.0,
Context: make(map[string]interface{}),
}
examples := []TrainingExample[BlockData]{example}
model.Learn(examples)
if len(model.history) != 1 {
t.Errorf("Expected 1 history entry, got %d", len(model.history))
}
}
// === Utility Function Tests ===
func TestSigmoid(t *testing.T) {
tests := []struct {
input float64
expected float64
epsilon float64
}{
{0.0, 0.5, 0.001},
{1000.0, 1.0, 0.001}, // Large positive should approach 1
{-1000.0, 0.0, 0.001}, // Large negative should approach 0
{1.0, 0.731, 0.01},
{-1.0, 0.268, 0.01},
}
for _, test := range tests {
result := sigmoid(test.input)
if result < test.expected-test.epsilon || result > test.expected+test.epsilon {
t.Errorf("sigmoid(%f) = %f, expected ~%f", test.input, result, test.expected)
}
if result < 0 || result > 1 {
t.Errorf("sigmoid(%f) = %f, should be in [0, 1]", test.input, result)
}
}
}
func TestGenerateID(t *testing.T) {
id1 := generateID()
id2 := generateID()
if id1 == "" {
t.Error("generateID returned empty string")
}
if id1 == id2 {
t.Error("generateID should produce unique IDs")
}
if len(id1) < 10 {
t.Error("Generated ID seems too short")
}
}
// === Feature Extractor Tests ===
func TestMockFeatureExtractor(t *testing.T) {
extractor := &mockBlockFeatureExtractor{}
data := BlockData{
Height: 1000,
Transactions: make([]string, 20),
Size: 4096,
GasUsed: 100000,
Timestamp: time.Now(),
}
features := extractor.Extract(data)
if len(features) != 4 {
t.Errorf("Expected 4 features, got %d", len(features))
}
if features["height"] != 1000.0 {
t.Errorf("Expected height 1000, got %f", features["height"])
}
if features["tx_count"] != 20.0 {
t.Errorf("Expected tx_count 20, got %f", features["tx_count"])
}
names := extractor.Names()
if len(names) != 4 {
t.Errorf("Expected 4 feature names, got %d", len(names))
}
}
// === Block Feature Extractor Tests ===
func TestBlockFeatures(t *testing.T) {
extractor := NewBlockFeatures()
if extractor == nil {
t.Fatal("NewBlockFeatures returned nil")
}
names := extractor.Names()
if len(names) == 0 {
t.Error("BlockFeatures should have feature names")
}
// Test with BlockData
blockData := BlockData{
Height: 500,
ParentHash: "0xabc",
Timestamp: time.Now(),
TxCount: 100,
Size: 2048,
GasUsed: 50000,
}
features := extractor.Extract(blockData)
if len(features) == 0 {
t.Error("Extract should return features")
}
}
// === Transaction Feature Extractor Tests ===
func TestTransactionFeatures(t *testing.T) {
extractor := NewTransactionFeatures()
if extractor == nil {
t.Fatal("NewTransactionFeatures returned nil")
}
names := extractor.Names()
if len(names) == 0 {
t.Error("TransactionFeatures should have feature names")
}
// Test with TransactionData
txData := TransactionData{
From: "0x123",
To: "0x456",
Amount: 1000000,
GasPrice: 50,
GasLimit: 21000,
Nonce: 10,
Timestamp: time.Now(),
}
features := extractor.Extract(txData)
if len(features) == 0 {
t.Error("Extract should return features")
}
}
// === Upgrade Feature Extractor Tests ===
func TestUpgradeFeatures(t *testing.T) {
extractor := NewUpgradeFeatures()
if extractor == nil {
t.Fatal("NewUpgradeFeatures returned nil")
}
names := extractor.Names()
if len(names) == 0 {
t.Error("UpgradeFeatures should have feature names")
}
upgradeData := UpgradeData{
Version: "v2.0.0",
Changes: []string{"feature1", "feature2", "bugfix1"},
Risk: "medium",
TestResults: []string{"test1: pass", "test2: pass", "test3: fail"},
Timestamp: time.Now(),
}
features := extractor.Extract(upgradeData)
if len(features) == 0 {
t.Error("Extract should return features")
}
}
// === ValidateProposal Tests ===
func TestSimpleModelValidateProposal(t *testing.T) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
// Create a proposal
proposal := &Proposal[BlockData]{
ID: "test-proposal",
NodeID: "node-1",
Decision: &Decision[BlockData]{
ID: "decision-1",
Action: "approve",
Confidence: 0.85,
Data: BlockData{
Height: 100,
Timestamp: time.Now(),
},
},
Weight: 1.0,
Confidence: 0.85,
Timestamp: time.Now(),
}
confidence, err := model.ValidateProposal(proposal)
if err != nil {
t.Fatalf("ValidateProposal() error = %v", err)
}
if confidence < 0 || confidence > 1 {
t.Errorf("Confidence should be between 0 and 1, got %f", confidence)
}
}
func TestSimpleModelValidateProposal_NilDecision(t *testing.T) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
proposal := &Proposal[BlockData]{
ID: "test-proposal",
Decision: nil, // Nil decision
}
_, err := model.ValidateProposal(proposal)
if err == nil {
t.Fatal("Expected error for nil decision, got nil")
}
if err.Error() != "proposal has no decision" {
t.Errorf("Unexpected error: %v", err)
}
}
// === UpdateWeights Tests ===
func TestSimpleModelUpdateWeights(t *testing.T) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
// UpdateWeights expects gradients to match feature count + 1 (bias)
// mockBlockFeatureExtractor has 4 features, so we need 5 gradients
gradients := []float64{0.1, 0.2, 0.15, 0.25, 0.05} // 4 features + 1 bias
err := model.UpdateWeights(gradients)
if err != nil {
t.Fatalf("UpdateWeights() error = %v", err)
}
}
func TestSimpleModelUpdateWeights_WrongSize(t *testing.T) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
// Provide wrong number of gradients
err := model.UpdateWeights([]float64{0.1, 0.2})
if err == nil {
t.Fatal("Expected error for wrong gradient size, got nil")
}
if !contains(err.Error(), "gradient size mismatch") {
t.Errorf("Expected gradient size mismatch error, got: %v", err)
}
}
// Helper function
func contains(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
(len(s) > len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr ||
len(s) > len(substr)*2 && findSubstring(s, substr))))
}
func findSubstring(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}
// === GetState Tests ===
func TestSimpleModelGetState(t *testing.T) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
// Set some state
model.weights["param1"] = 1.5
model.weights["param2"] = 2.5
model.bias = 0.7
state := model.GetState()
if state == nil {
t.Fatal("GetState() returned nil")
}
// Check that weights are included
if weights, ok := state["weights"].(map[string]float64); ok {
if len(weights) != 2 {
t.Errorf("Expected 2 weights, got %d", len(weights))
}
if weights["param1"] != 1.5 {
t.Errorf("Expected param1 = 1.5, got %f", weights["param1"])
}
} else {
t.Error("State should contain weights map")
}
// Check that bias is included
if bias, ok := state["bias"].(float64); ok {
if bias != 0.7 {
t.Errorf("Expected bias = 0.7, got %f", bias)
}
} else {
t.Error("State should contain bias")
}
}
// === LoadState Tests ===
func TestSimpleModelLoadState(t *testing.T) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
// Create state to load (weights must be map[string]interface{})
state := map[string]interface{}{
"weights": map[string]interface{}{
"param1": 3.0,
"param2": 4.0,
},
"bias": 0.9,
}
err := model.LoadState(state)
if err != nil {
t.Fatalf("LoadState() error = %v", err)
}
// Check that state was loaded
if model.bias != 0.9 {
t.Errorf("Expected bias = 0.9, got %f", model.bias)
}
if model.weights["param1"] != 3.0 {
t.Errorf("Expected param1 = 3.0, got %f", model.weights["param1"])
}
if model.weights["param2"] != 4.0 {
t.Errorf("Expected param2 = 4.0, got %f", model.weights["param2"])
}
}
func TestSimpleModelLoadState_InvalidWeights(t *testing.T) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
// State with invalid weights type
state := map[string]interface{}{
"weights": "invalid",
"bias": 0.5,
}
err := model.LoadState(state)
// Should handle gracefully or return error
if err != nil {
// Error is acceptable
t.Logf("LoadState correctly returned error: %v", err)
}
}
// === Utility Function Tests ===
func TestHashComplexity_AllCases(t *testing.T) {
tests := []struct {
name string
hash string
want float64
}{
{"empty", "", 0.0},
{"numeric", "1234567890", 0.1},
{"hexadecimal", "abcdef", 0.2},
{"mixed", "a1b2c3", 0.15},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := hashComplexity(tt.hash)
if tt.name == "empty" && got != tt.want {
t.Errorf("hashComplexity(%q) = %v, want %v", tt.hash, got, tt.want)
}
// For non-empty, just check it's reasonable
if tt.name != "empty" && (got < 0 || got > 1) {
t.Errorf("hashComplexity(%q) = %v, should be between 0 and 1", tt.hash, got)
}
})
}
}
func TestAddressEntropy_Edge(t *testing.T) {
tests := []struct {
name string
address string
}{
{"empty", ""},
{"single_char", "a"},
{"repeated", "aaaa"},
{"diverse", "abcdefghij"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
entropy := addressEntropy(tt.address)
if entropy < 0 {
t.Errorf("addressEntropy(%q) = %v, should be >= 0", tt.address, entropy)
}
})
}
}
func TestVersionEntropy_Edge(t *testing.T) {
tests := []struct {
name string
version string
}{
{"empty", ""},
{"simple", "1.0"},
{"complex", "1.2.3"},
{"very_complex", "1.2.3.4.5"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
entropy := versionEntropy(tt.version)
if entropy < 0 {
t.Errorf("versionEntropy(%q) = %v, should be >= 0", tt.version, entropy)
}
})
}
}
func TestUpgradeExtract_AllRiskLevels(t *testing.T) {
extractor := NewUpgradeFeatures()
riskLevels := []string{"low", "medium", "high", "unknown"}
for _, risk := range riskLevels {
t.Run("risk_"+risk, func(t *testing.T) {
data := UpgradeData{
Version: "v1.0.0",
Changes: []string{"change1"},
Risk: risk,
TestResults: []string{"pass"},
Timestamp: time.Now(),
}
features := extractor.Extract(data)
if len(features) == 0 {
t.Error("Extract should return features")
}
// Check risk score is set
if riskScore, ok := features["risk_score"]; ok {
if riskScore < 0 || riskScore > 1 {
t.Errorf("Risk score %f should be between 0 and 1", riskScore)
}
}
})
}
}
// === Benchmark Tests ===
func BenchmarkSimpleModelDecide(b *testing.B) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
model.weights["tx_count"] = 1.0
model.bias = 0.5
data := BlockData{
Height: 100,
Transactions: make([]string, 10),
Size: 2048,
GasUsed: 50000,
Timestamp: time.Now(),
}
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = model.Decide(ctx, data, nil)
}
}
func BenchmarkSimpleModelLearn(b *testing.B) {
extractor := &mockBlockFeatureExtractor{}
model := NewSimpleModel[BlockData]("node-1", extractor)
blockData := BlockData{
Height: 100,
Transactions: make([]string, 10),
Size: 2048,
GasUsed: 50000,
Timestamp: time.Now(),
}
example := TrainingExample[BlockData]{
Input: blockData,
Output: Decision[BlockData]{
ID: "bench-decision",
Action: "approve",
Data: blockData,
Confidence: 0.9,
Reasoning: "Benchmark",
Context: make(map[string]interface{}),
Timestamp: time.Now(),
ProposerID: "node-1",
},
Feedback: 1.0,
NodeID: "node-1",
Weight: 1.0,
Context: make(map[string]interface{}),
}
examples := []TrainingExample[BlockData]{example}
b.ResetTimer()
for i := 0; i < b.N; i++ {
model.Learn(examples)
}
}
func BenchmarkFeatureExtraction(b *testing.B) {
extractor := &mockBlockFeatureExtractor{}
data := BlockData{
Height: 100,
Transactions: make([]string, 10),
Size: 2048,
GasUsed: 50000,
Timestamp: time.Now(),
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = extractor.Extract(data)
}
}
func BenchmarkSigmoid(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = sigmoid(float64(i % 100))
}
}