rustygraph 0.4.2

A high-performance library for visibility graph computation from time series data
Documentation
#!/usr/bin/env python3
"""
Test script for RustyGraph Python bindings - Features & Optimizations

This tests:
1. Node features computation
2. SIMD + Parallel optimizations (automatically enabled)
3. Performance comparison
"""

import rustygraph
import numpy as np
import time


def test_features():
    """Test node feature computation."""
    print("="*60)
    print("TEST 1: Node Features")
    print("="*60)

    # Create test data
    data = np.sin(np.linspace(0, 10, 50))
    series = rustygraph.TimeSeries(data.tolist())

    # Create FeatureSet
    features = rustygraph.FeatureSet()
    features.add_builtin(rustygraph.BuiltinFeature("DeltaForward"))
    features.add_builtin(rustygraph.BuiltinFeature("LocalSlope"))
    features.add_builtin(rustygraph.BuiltinFeature("IsLocalMax"))
    features.add_builtin(rustygraph.BuiltinFeature("IsLocalMin"))

    print(f"Created FeatureSet with 4 features")

    # Create graph with features
    graph = series.natural_visibility_with_features(features)

    print(f"✅ Graph created:")
    print(f"   Nodes: {graph.node_count()}")
    print(f"   Edges: {graph.edge_count()}")
    print(f"   Has features: {graph.has_features()}")
    print(f"   Feature count: {graph.feature_count()}")

    # Get features for specific node
    node_features = graph.get_node_features(0)
    print(f"\n✅ Node 0 features:")
    for name, value in node_features.items():
        print(f"   {name}: {value:.4f}")

    # Get all features as array
    all_features = graph.get_all_features()
    print(f"\n✅ All features array:")
    print(f"   Shape: {all_features.shape}")
    print(f"   Mean: {all_features.mean():.4f}")
    print(f"   Std: {all_features.std():.4f}")

    return True


def test_optimizations():
    """Test that SIMD + Parallel optimizations are working."""
    print("\n" + "="*60)
    print("TEST 2: SIMD + Parallel Optimizations")
    print("="*60)

    print("\n📊 Performance test (1000 nodes):")

    # Generate test data
    data = np.sin(np.linspace(0, 100, 1000))

    # Measure performance
    start = time.time()
    graph = rustygraph.natural_visibility(data.tolist())
    elapsed = time.time() - start

    print(f"✅ Natural visibility graph:")
    print(f"   Time: {elapsed*1000:.2f}ms")
    print(f"   Nodes: {graph.node_count()}")
    print(f"   Edges: {graph.edge_count()}")
    print(f"   Density: {graph.density():.4f}")

    # Note: With SIMD + Parallel enabled (via python-bindings feature),
    # this should be significantly faster than without optimizations

    if elapsed < 0.5:  # Should be fast with optimizations
        print(f"   🚀 Performance looks good! (SIMD + Parallel enabled)")
    else:
        print(f"   ⚠️  Might be slower than expected")

    return True


def test_feature_comparison():
    """Compare graphs with and without features."""
    print("\n" + "="*60)
    print("TEST 3: Feature Comparison")
    print("="*60)

    data = [1.0, 3.0, 2.0, 4.0, 3.0, 5.0, 4.0]
    series = rustygraph.TimeSeries(data)

    # Without features
    print("\n📊 Without features:")
    start = time.time()
    graph1 = series.natural_visibility()
    t1 = time.time() - start
    print(f"   Time: {t1*1000:.2f}ms")
    print(f"   Has features: {graph1.has_features()}")

    # With features
    print("\n📊 With features:")
    features = rustygraph.FeatureSet()
    features.add_builtin(rustygraph.BuiltinFeature("DeltaForward"))
    features.add_builtin(rustygraph.BuiltinFeature("DeltaBackward"))
    features.add_builtin(rustygraph.BuiltinFeature("LocalSlope"))

    start = time.time()
    graph2 = series.natural_visibility_with_features(features)
    t2 = time.time() - start
    print(f"   Time: {t2*1000:.2f}ms")
    print(f"   Has features: {graph2.has_features()}")
    print(f"   Feature count: {graph2.feature_count()}")

    # Show features
    print(f"\n✅ Node features for all nodes:")
    for i in range(min(3, graph2.node_count())):
        features_dict = graph2.get_node_features(i)
        print(f"   Node {i}: {features_dict}")

    return True


def test_all_builtin_features():
    """Test all available builtin features."""
    print("\n" + "="*60)
    print("TEST 4: All Builtin Features")
    print("="*60)

    # All available features
    feature_names = [
        "DeltaForward",
        "DeltaBackward",
        "DeltaSymmetric",
        "LocalSlope",
        "Acceleration",
        "LocalMean",
        "LocalVariance",
        "IsLocalMax",
        "IsLocalMin",
        "ZScore",
    ]

    data = np.sin(np.linspace(0, 10, 20))
    series = rustygraph.TimeSeries(data.tolist())

    # Create FeatureSet with all features
    features = rustygraph.FeatureSet()
    for name in feature_names:
        features.add_builtin(rustygraph.BuiltinFeature(name))

    print(f"Testing with {len(feature_names)} features:")
    for name in feature_names:
        print(f"   - {name}")

    graph = series.natural_visibility_with_features(features)

    print(f"\n✅ Graph created successfully!")
    print(f"   Nodes: {graph.node_count()}")
    print(f"   Features per node: {graph.feature_count()}")

    # Show first node features
    node0_features = graph.get_node_features(0)
    print(f"\n✅ Node 0 features:")
    for name, value in node0_features.items():
        print(f"   {name}: {value:.6f}")

    return True


def main():
    """Run all tests."""
    print("\n" + "="*60)
    print("  RustyGraph Python Features & Optimizations Test")
    print("="*60)
    print("\nNote: SIMD + Parallel optimizations are ENABLED")
    print("      (via python-bindings feature in Cargo.toml)")

    try:
        test_features()
        test_optimizations()
        test_feature_comparison()
        test_all_builtin_features()

        print("\n" + "="*60)
        print("🎉 ALL TESTS PASSED!")
        print("="*60)
        print("\n✅ Node features: WORKING")
        print("✅ SIMD optimization: ENABLED")
        print("✅ Parallel optimization: ENABLED")
        print("✅ All builtin features: WORKING")
        print("\nPython bindings are fully optimized! 🚀")

    except Exception as e:
        print(f"\n❌ TEST FAILED: {e}")
        import traceback
        traceback.print_exc()
        return 1

    return 0


if __name__ == "__main__":
    exit(main())