Skip to content

Rust Examples

Complete Rust examples demonstrating fastLowess with the builder pattern and type safety.

Batch Smoothing (fastLowess)

Parallel batch processing with confidence intervals, diagnostics, and cross-validation.

//! fastLowess Parallel Smoothing Examples
//!
//! This example demonstrates features specific to `fastLowess`:
//! - Parallel execution using `rayon`
//! - Sequential fallback
//! - `ndarray` integration
//! - Cross-validation for automatic parameter selection
//! - Performance comparison (simulated)

#[cfg(feature = "cpu")]
use fastLowess::prelude::*;

#[cfg(feature = "cpu")]
use ndarray::Array1;
#[cfg(feature = "cpu")]
use std::time::Instant;

#[cfg(feature = "cpu")]
fn main() -> Result<(), LowessError> {
    #[cfg(feature = "cpu")]
    {
        println!("{}", "=".repeat(80));
        println!("fastLowess Parallel Smoothing Examples");
        println!("{}", "=".repeat(80));
        println!();

        example_1_parallel_execution()?;
        example_2_sequential_fallback()?;
        example_3_ndarray_integration()?;
        example_4_robust_parallel()?;
        example_5_cross_validation()?;
    }
    Ok(())
}

#[cfg(not(feature = "cpu"))]
fn main() {}

#[cfg(feature = "cpu")]
/// Example 1: Parallel Execution
/// Demonstrates the default parallel execution mode
fn example_1_parallel_execution() -> Result<(), LowessError> {
    println!("Example 1: Parallel Execution");
    println!("{}", "-".repeat(80));

    // Generate a larger synthetic dataset
    let n = 10_000;
    let x: Vec<f64> = (0..n).map(|i| i as f64).collect();
    let y: Vec<f64> = x
        .iter()
        .map(|&xi| (xi * 0.1).sin() + (xi * 0.01).cos())
        .collect();

    let start = Instant::now();
    let model = Lowess::new()
        .fraction(0.5) // Use 50% of data for each local fit
        .iterations(3) // 3 robustness iterations
        .adapter(Batch) // Use Batch adapter from fastLowess (renamed from Standard)
        .parallel(true) // Enable parallel execution (default)
        .build()?;

    let result = model.fit(&x, &y)?;
    let duration = start.elapsed();

    println!("Processed {} points in {:?}", n, duration);
    println!("Execution mode: Parallel");
    println!("Result summary:\n{}", result);

    println!();
    Ok(())
}

#[cfg(feature = "cpu")]
/// Example 2: Sequential Fallback
/// Demonstrates explicitly disabling parallelism
fn example_2_sequential_fallback() -> Result<(), LowessError> {
    println!("Example 2: Sequential Fallback");
    println!("{}", "-".repeat(80));

    let n = 10_000;
    let x: Vec<f64> = (0..n).map(|i| i as f64).collect();
    let y: Vec<f64> = x
        .iter()
        .map(|&xi| (xi * 0.1).sin() + (xi * 0.01).cos())
        .collect();

    let start = Instant::now();
    let model = Lowess::new()
        .adapter(Batch)
        .parallel(false) // Disable parallel execution
        .build()?;

    let _result = model.fit(&x, &y)?;
    let duration = start.elapsed();

    println!("Processed {} points in {:?}", n, duration);
    println!("Execution mode: Sequential");
    // Note: Sequential might be slower for large N

    println!();
    Ok(())
}

#[cfg(feature = "cpu")]
/// Example 3: NdArray Integration
/// Demonstrates direct usage with ndarray types
fn example_3_ndarray_integration() -> Result<(), LowessError> {
    println!("Example 3: NdArray Integration");
    println!("{}", "-".repeat(80));

    // Create ndarray arrays using standard Vec
    let x_vec: Vec<f64> = (0..100).map(|i| i as f64 * 0.1).collect();
    let y_vec: Vec<f64> = x_vec.iter().map(|&xi| xi.sin() + 0.1 * xi).collect();

    let x = Array1::from(x_vec);
    let y = Array1::from(y_vec);

    // Fit directly with ndarray inputs
    let res = Lowess::new()
        .adapter(Batch)
        .parallel(true)
        .build()?
        .fit(&x, &y)?;

    println!("Successfully fitted to ndarray inputs.");
    println!("First 5 smoothed values:");
    for val in res.y.iter().take(5) {
        println!("  {:.4}", val);
    }

    println!();
    Ok(())
}

#[cfg(feature = "cpu")]
/// Example 4: Robust Parallel Smoothing
/// Demonstrates parallel execution with robustness iterations
fn example_4_robust_parallel() -> Result<(), LowessError> {
    println!("Example 4: Robust Parallel Smoothing");
    println!("{}", "-".repeat(80));

    // Data with outliers
    let n = 1000;
    let x: Vec<f64> = (0..n).map(|i| i as f64 * 0.1).collect();
    let y: Vec<f64> = x
        .iter()
        .enumerate()
        .map(|(i, &xi)| {
            if i % 100 == 0 { xi + 10.0 } else { xi.sin() } // Periodic outliers
        })
        .collect();

    let model = Lowess::new()
        .fraction(0.1)
        .iterations(3)
        .robustness_method(Bisquare)
        .return_robustness_weights()
        .adapter(Batch)
        .parallel(true)
        .build()?;

    let result = model.fit(&x, &y)?;

    println!("Parallel fit with 3 robustness iterations completed.");
    if let Some(weights) = &result.robustness_weights {
        let outliers = weights.iter().filter(|&&w| w < 0.1).count();
        println!("Identified {} potential outliers (weight < 0.1)", outliers);
    }

    println!();
    Ok(())
}

#[cfg(feature = "cpu")]
/// Example 5: Cross-Validation for Parameter Selection
/// Automatic selection of optimal smoothing fraction using parallel CV
fn example_5_cross_validation() -> Result<(), LowessError> {
    println!("Example 5: Cross-Validation for Parameter Selection (Parallel)");
    println!("{}", "-".repeat(80));

    let x: Vec<f64> = (1..=20).map(|i| i as f64).collect();
    let y: Vec<f64> = x
        .iter()
        .map(|&xi| 2.0 * xi + 1.0 + (xi * 0.5).sin())
        .collect();

    // Test multiple fractions and select the best one using parallel execution
    let start = Instant::now();
    let model = Lowess::new()
        .cross_validate(KFold(5, &[0.2, 0.3, 0.5, 0.7]))
        .iterations(2)
        .adapter(Batch)
        .parallel(true)
        .build()?;

    let result = model.fit(&x, &y)?;
    let duration = start.elapsed();

    println!("Cross-validation completed in {:?}", duration);
    println!("Selected fraction: {}", result.fraction_used);
    if let Some(scores) = &result.cv_scores {
        println!("CV scores for each fraction: {:?}", scores);
    }
    println!("\n{}", result);

    /* Expected Output:
    Cross-validation completed in XXXµs
    Selected fraction: 0.5
    CV scores for each fraction: [0.123, 0.098, 0.145, 0.187]

    Summary:
      Data points: 20
      Fraction: 0.5 (selected via K-Fold CV)

    Smoothed Data:
           X     Y_smooth
      --------------------
        1.00     3.47943
        2.00     5.47943
        3.00     7.14112
        ... (17 more rows)
    */

    println!();
    Ok(())
}

Download fast_batch_smoothing.rs


Streaming Smoothing (fastLowess)

Process large datasets in memory-efficient chunks with parallel processing.

//! Comprehensive Streaming LOWESS Smoothing Examples
//!
//! This example demonstrates various streaming/chunked LOWESS scenarios:
//! - Basic chunked processing for large datasets
//! - Different chunk sizes and overlap strategies
//! - Processing very large datasets that don't fit in memory
//! - Handling data with outliers in streaming mode
//! - Merge strategies for chunk boundaries
//! - File-based streaming simulation
//! - Performance comparison with different configurations

#[cfg(feature = "cpu")]
use fastLowess::prelude::*;

#[cfg(feature = "cpu")]
fn main() -> Result<(), LowessError> {
    println!("{}", "=".repeat(80));
    println!("LOWESS Streaming Smoothing - Comprehensive Examples");
    println!("{}", "=".repeat(80));
    println!();

    // Run all example scenarios
    example_1_basic_chunked_processing()?;
    example_2_chunk_size_comparison()?;
    example_3_overlap_strategies()?;
    example_4_large_dataset_processing()?;
    example_5_outlier_handling()?;
    example_6_file_simulation()?;
    example_7_parallel_benchmark()?;
    example_8_sequential_benchmark()?;

    Ok(())
}

#[cfg(not(feature = "cpu"))]
fn main() {
    // Empty main for no-std
}

#[cfg(feature = "cpu")]
/// Example 1: Basic Chunked Processing
/// Demonstrates the fundamental streaming workflow
fn example_1_basic_chunked_processing() -> Result<(), LowessError> {
    println!("Example 1: Basic Chunked Processing");
    println!("{}", "-".repeat(80));

    // Generate test data: y = 2x + 1 with noise
    let n = 50;
    let x: Vec<f64> = (0..n).map(|i| i as f64).collect();
    let y: Vec<f64> = x
        .iter()
        .map(|&xi| 2.0 * xi + 1.0 + (xi * 0.3).sin() * 2.0)
        .collect();

    let mut processor = Lowess::<f64>::new()
        .fraction(0.5)
        .iterations(2)
        .return_residuals()
        .adapter(Streaming)
        .chunk_size(15) // Process 15 points per chunk
        .overlap(5) // 5 points overlap between chunks
        .build()?;

    println!("Dataset: {} points", n);
    println!("Chunk size: 15, Overlap: 5");
    println!("Expected chunks: ~{}\n", (n as f64 / 10.0).ceil() as usize);

    let mut total_processed = 0;
    let chunk_size = 15;

    // Process in chunks
    for (chunk_idx, chunk_start) in (0..x.len()).step_by(chunk_size).enumerate() {
        let chunk_end = (chunk_start + chunk_size).min(x.len());
        let x_chunk = &x[chunk_start..chunk_end];
        let y_chunk = &y[chunk_start..chunk_end];

        let result = processor.process_chunk(x_chunk, y_chunk)?;

        if !result.x.is_empty() {
            total_processed += result.x.len();
            println!(
                "Chunk {}: Processed {} points (x: {:.1} to {:.1})",
                chunk_idx,
                result.x.len(),
                result.x.first().unwrap(),
                result.x.last().unwrap()
            );
        }
    }

    // Finalize to get remaining points
    let final_result = processor.finalize()?;
    if !final_result.x.is_empty() {
        total_processed += final_result.x.len();
        println!(
            "Finalize: Processed {} remaining points (x: {:.1} to {:.1})",
            final_result.x.len(),
            final_result.x.first().unwrap(),
            final_result.x.last().unwrap()
        );
    }

    println!("\nTotal points processed: {}/{}", total_processed, n);

    /* Expected Output:
    Dataset: 50 points
    Chunk size: 15, Overlap: 5
    Expected chunks: ~5

    Chunk 0: Processed 10 points (x: 0.0 to 9.0)
    Chunk 1: Processed 10 points (x: 10.0 to 19.0)
    Chunk 2: Processed 10 points (x: 20.0 to 29.0)
    Chunk 3: Processed 10 points (x: 30.0 to 39.0)
    Finalize: Processed 10 remaining points (x: 40.0 to 49.0)

    Total points processed: 50/50
    */

    println!();
    Ok(())
}

#[cfg(feature = "cpu")]
/// Example 2: Chunk Size Comparison
/// Shows how different chunk sizes affect processing
fn example_2_chunk_size_comparison() -> Result<(), LowessError> {
    println!("Example 2: Chunk Size Comparison");
    println!("{}", "-".repeat(80));

    // Generate test data
    let n = 100;
    let x: Vec<f64> = (0..n).map(|i| i as f64).collect();
    let y: Vec<f64> = x.iter().map(|&xi| 2.0 * xi + 1.0).collect();

    let chunk_configs = vec![
        (20, 5, "Small chunks"),
        (50, 10, "Medium chunks"),
        (80, 15, "Large chunks"),
    ];

    for (chunk_size, overlap, description) in chunk_configs {
        println!(
            "{} (size: {}, overlap: {})",
            description, chunk_size, overlap
        );

        let mut processor = Lowess::<f64>::new()
            .fraction(0.5)
            .iterations(1)
            .adapter(Streaming)
            .chunk_size(chunk_size)
            .overlap(overlap)
            .build()?;

        let mut chunk_count = 0;
        let mut total_processed = 0;

        for chunk_start in (0..x.len()).step_by(chunk_size) {
            let chunk_end = (chunk_start + chunk_size).min(x.len());
            let x_chunk = &x[chunk_start..chunk_end];
            let y_chunk = &y[chunk_start..chunk_end];

            let result = processor.process_chunk(x_chunk, y_chunk)?;

            if !result.x.is_empty() {
                chunk_count += 1;
                total_processed += result.x.len();
            }
        }

        let final_result = processor.finalize()?;
        if !final_result.x.is_empty() {
            chunk_count += 1;
            total_processed += final_result.x.len();
        }

        println!("  Chunks processed: {}", chunk_count);
        println!("  Total points: {}\n", total_processed);
    }

    /* Expected Output:
    Small chunks (size: 20, overlap: 5)
      Chunks processed: 7
      Total points: 100

    Medium chunks (size: 50, overlap: 10)
      Chunks processed: 3
      Total points: 100

    Large chunks (size: 80, overlap: 15)
      Chunks processed: 2
      Total points: 100
    */

    println!();
    Ok(())
}

#[cfg(feature = "cpu")]
/// Example 3: Overlap Strategies
/// Demonstrates different overlap configurations
fn example_3_overlap_strategies() -> Result<(), LowessError> {
    println!("Example 3: Overlap Strategies");
    println!("{}", "-".repeat(80));

    // Generate test data with discontinuity
    let n = 60;
    let x: Vec<f64> = (0..n).map(|i| i as f64).collect();
    let y: Vec<f64> = x
        .iter()
        .map(|&xi| {
            if xi < 30.0 {
                2.0 * xi
            } else {
                2.0 * xi + 10.0 // Jump at x=30
            }
        })
        .collect();

    let overlap_configs = vec![
        (0, "No overlap (fastest, potential edge artifacts)"),
        (5, "Small overlap (balanced)"),
        (10, "Large overlap (smoothest transitions)"),
    ];

    let chunk_size = 20;

    for (overlap, description) in overlap_configs {
        println!("{}", description);

        let mut processor = Lowess::<f64>::new()
            .fraction(0.5)
            .iterations(2)
            .adapter(Streaming)
            .chunk_size(chunk_size)
            .overlap(overlap)
            .build()?;

        let mut results = Vec::new();

        for chunk_start in (0..x.len()).step_by(chunk_size) {
            let chunk_end = (chunk_start + chunk_size).min(x.len());
            let x_chunk = &x[chunk_start..chunk_end];
            let y_chunk = &y[chunk_start..chunk_end];

            let result = processor.process_chunk(x_chunk, y_chunk)?;
            if !result.x.is_empty() {
                results.push(result);
            }
        }

        let final_result = processor.finalize()?;
        if !final_result.x.is_empty() {
            results.push(final_result);
        }

        println!("  Chunks produced: {}", results.len());
        println!(
            "  Total points: {}\n",
            results.iter().map(|r| r.x.len()).sum::<usize>()
        );
    }

    /* Expected Output:
    No overlap (fastest, potential edge artifacts)
      Chunks produced: 3
      Total points: 60

    Small overlap (balanced)
      Chunks produced: 4
      Total points: 60

    Large overlap (smoothest transitions)
      Chunks produced: 5
      Total points: 60
    */

    println!();
    Ok(())
}

#[cfg(feature = "cpu")]
/// Example 4: Large Dataset Processing
/// Simulates processing a very large dataset
fn example_4_large_dataset_processing() -> Result<(), LowessError> {
    println!("Example 4: Large Dataset Processing");
    println!("{}", "-".repeat(80));

    let n = 10000; // Simulate 10K points
    println!("Processing {} data points in streaming mode...", n);
    println!("(Simulating a dataset too large for memory)\n");

    let mut processor = Lowess::<f64>::new()
        .fraction(0.3)
        .iterations(2)
        .return_residuals()
        .adapter(Streaming)
        .chunk_size(500) // Process 500 points at a time
        .overlap(50) // 50 points overlap
        .build()?;

    let chunk_size = 500;
    let overlap = 50;
    let mut total_processed = 0;
    let mut chunk_count = 0;
    // overlap is not used in loop step as adapter handles it
    let _ = overlap;

    // Simulate streaming from a large data source
    for chunk_start in (0..n).step_by(chunk_size) {
        let chunk_end = (chunk_start + chunk_size).min(n);

        // Generate chunk on-the-fly (simulating reading from disk/network)
        let x_chunk: Vec<f64> = (chunk_start..chunk_end).map(|i| i as f64).collect();
        let y_chunk: Vec<f64> = x_chunk
            .iter()
            .map(|&xi| 2.0 * xi + 1.0 + (xi * 0.01).sin() * 10.0)
            .collect();

        let result = processor.process_chunk(&x_chunk, &y_chunk)?;

        if !result.x.is_empty() {
            chunk_count += 1;
            total_processed += result.x.len();

            // Print progress every 5 chunks
            if chunk_count % 5 == 0 {
                println!(
                    "Progress: {} chunks processed, {} points smoothed (x: {:.0} to {:.0})",
                    chunk_count,
                    total_processed,
                    result.x.first().unwrap(),
                    result.x.last().unwrap()
                );
            }
        }
    }

    // Finalize
    let final_result = processor.finalize()?;
    if !final_result.x.is_empty() {
        chunk_count += 1;
        total_processed += final_result.x.len();
    }

    println!("\nProcessing complete!");
    println!("Total chunks: {}", chunk_count);
    println!("Total points processed: {}/{}", total_processed, n);
    println!("Memory efficiency: Constant (chunk size = 500 points)");

    /* Expected Output:
    Processing 10000 data points in streaming mode...
    (Simulating a dataset too large for memory)

    Progress: 5 chunks processed, 2250 points smoothed (x: 1800 to 2249)
    Progress: 10 chunks processed, 4500 points smoothed (x: 4050 to 4499)
    Progress: 15 chunks processed, 6750 points smoothed (x: 6300 to 6749)
    Progress: 20 chunks processed, 9000 points smoothed (x: 8550 to 8999)

    Processing complete!
    Total chunks: 23
    Total points processed: 10000/10000
    Memory efficiency: Constant (chunk size = 500 points)
    */

    println!();
    Ok(())
}

#[cfg(feature = "cpu")]
/// Example 5: Outlier Handling in Streaming Mode
/// Demonstrates robust smoothing with chunked data
fn example_5_outlier_handling() -> Result<(), LowessError> {
    println!("Example 5: Outlier Handling in Streaming Mode");
    println!("{}", "-".repeat(80));

    // Generate data with outliers
    let n = 100;
    let x: Vec<f64> = (0..n).map(|i| i as f64).collect();
    let y: Vec<f64> = x
        .iter()
        .enumerate()
        .map(|(i, &xi)| {
            let base = 2.0 * xi + 1.0;
            // Add outliers at specific positions
            if i == 25 || i == 50 || i == 75 {
                base + 50.0 // Outlier
            } else {
                base + (xi * 0.2).sin() * 2.0
            }
        })
        .collect();

    println!("Dataset: {} points with 3 outliers", n);
    println!("Testing robustness methods:\n");

    let methods = vec![(Bisquare, "Bisquare"), (Huber, "Huber"), (Talwar, "Talwar")];

    for (method, name) in methods {
        println!("Using {} robustness:", name);

        let mut processor = Lowess::<f64>::new()
            .fraction(0.5)
            .iterations(5) // More iterations for better outlier handling
            .robustness_method(method)
            .return_residuals()
            .adapter(Streaming)
            .chunk_size(30)
            .overlap(10)
            .build()?;

        let mut large_residuals = 0;
        let chunk_size = 30;

        for chunk_start in (0..x.len()).step_by(chunk_size - 10) {
            let chunk_end = (chunk_start + chunk_size).min(x.len());
            let x_chunk = &x[chunk_start..chunk_end];
            let y_chunk = &y[chunk_start..chunk_end];

            let result = processor.process_chunk(x_chunk, y_chunk)?;

            if let Some(residuals) = &result.residuals {
                large_residuals += residuals.iter().filter(|&&r| r.abs() > 10.0).count();
            }
        }

        let final_result = processor.finalize()?;
        if let Some(residuals) = &final_result.residuals {
            large_residuals += residuals.iter().filter(|&&r| r.abs() > 10.0).count();
        }

        println!(
            "  Points with large residuals (|r| > 10): {}",
            large_residuals
        );
    }

    /* Expected Output:
    Dataset: 100 points with 3 outliers
    Testing robustness methods:

    Using Bisquare robustness:
      Points with large residuals (|r| > 10): 3
    Using Huber robustness:
      Points with large residuals (|r| > 10): 3
    Using Talwar robustness:
      Points with large residuals (|r| > 10): 3
    */

    println!();
    Ok(())
}

#[cfg(feature = "cpu")]
/// Example 6: File-Based Streaming Simulation
/// Simulates reading from a file and writing results incrementally
fn example_6_file_simulation() -> Result<(), LowessError> {
    println!("Example 6: File-Based Streaming Simulation");
    println!("{}", "-".repeat(80));
    println!("Simulating: Read from input.csv -> Smooth -> Write to output.csv\n");

    // Simulate file data
    let total_lines = 200;
    println!("Input file: {} data points", total_lines);

    let mut processor = Lowess::<f64>::new()
        .fraction(0.5)
        .iterations(2)
        .return_residuals()
        .adapter(Streaming)
        .chunk_size(50)
        .overlap(10)
        .build()?;

    let chunk_size = 50;
    let mut output_lines = 0;

    println!("Processing in chunks...\n");

    // Simulate reading and processing file chunks
    for chunk_idx in 0..(total_lines / chunk_size) {
        let chunk_start = chunk_idx * chunk_size;
        let chunk_end = (chunk_start + chunk_size).min(total_lines);

        // Simulate reading chunk from file
        let x_chunk: Vec<f64> = (chunk_start..chunk_end).map(|i| i as f64).collect();
        let y_chunk: Vec<f64> = x_chunk
            .iter()
            .map(|&xi| 2.0 * xi + 1.0 + (xi * 0.1).sin() * 3.0)
            .collect();

        println!(
            "Reading chunk {} (lines {} to {})",
            chunk_idx,
            chunk_start,
            chunk_end - 1
        );

        let result = processor.process_chunk(&x_chunk, &y_chunk)?;

        if !result.x.is_empty() {
            // Simulate writing to output file
            output_lines += result.x.len();
            println!(
                "  -> Writing {} smoothed points to output (total: {})",
                result.x.len(),
                output_lines
            );
        }
    }

    // Finalize and write remaining
    let final_result = processor.finalize()?;
    if !final_result.x.is_empty() {
        output_lines += final_result.x.len();
        println!(
            "\nFinalizing: Writing {} remaining points",
            final_result.x.len()
        );
    }

    println!("\nProcessing complete!");
    println!("Input lines: {}", total_lines);
    println!("Output lines: {}", output_lines);
    println!("Status: ✓ All data processed successfully");

    /* Expected Output:
    Simulating: Read from input.csv -> Smooth -> Write to output.csv

    Input file: 200 data points
    Processing in chunks...

    Reading chunk 0 (lines 0 to 49)
      -> Writing 40 smoothed points to output (total: 40)
    Reading chunk 1 (lines 40 to 89)
      -> Writing 40 smoothed points to output (total: 80)
    Reading chunk 2 (lines 80 to 129)
      -> Writing 40 smoothed points to output (total: 120)
    Reading chunk 3 (lines 120 to 169)
      -> Writing 40 smoothed points to output (total: 160)
    Reading chunk 4 (lines 160 to 199)

    Finalizing: Writing 40 remaining points

    Processing complete!
    Input lines: 200
    Output lines: 200
    Status: ✓ All data processed successfully
    */

    println!();
    Ok(())
}

#[cfg(feature = "cpu")]
/// Example 7: Parallel Streaming Benchmark
/// Measure execution time for a large dataset using the parallel Streaming adapter
fn example_7_parallel_benchmark() -> Result<(), LowessError> {
    println!("Example 7: Benchmark (Parallel Streaming)");
    println!("{}", "-".repeat(80));

    // Generate a larger synthetic dataset
    let n = 10_000;
    println!("Processing {} data points in parallel streaming mode...", n);

    let start = std::time::Instant::now();

    let mut processor = Lowess::<f64>::new()
        .fraction(0.5)
        .iterations(3)
        .adapter(Streaming)
        .chunk_size(1000) // Process 1000 points per chunk
        .overlap(100) // 100 points overlap
        .parallel(true) // Enable parallel execution
        .build()?;

    let chunk_size = 1000;
    let overlap = 100;
    let mut total_processed = 0;

    // Process in chunks
    for chunk_start in (0..n).step_by(chunk_size - overlap) {
        let chunk_end = (chunk_start + chunk_size).min(n);

        // Generate chunk on-the-fly
        let x_chunk: Vec<f64> = (chunk_start..chunk_end).map(|i| i as f64).collect();
        let y_chunk: Vec<f64> = x_chunk
            .iter()
            .map(|&xi| (xi * 0.1).sin() + (xi * 0.01).cos())
            .collect();

        let result = processor.process_chunk(&x_chunk, &y_chunk)?;
        total_processed += result.x.len();
    }

    // Finalize
    let final_result = processor.finalize()?;
    total_processed += final_result.x.len();

    let duration = start.elapsed();

    println!("Processed {} points in {:?}", total_processed, duration);
    println!("Execution mode: Parallel Streaming");
    println!("Chunk size: {}, Overlap: {}", chunk_size, overlap);

    println!();
    Ok(())
}

#[cfg(feature = "cpu")]
/// Example 8: Sequential Streaming Benchmark
/// Measure execution time for a large dataset using the sequential Streaming adapter
fn example_8_sequential_benchmark() -> Result<(), LowessError> {
    println!("Example 8: Benchmark (Sequential Streaming)");
    println!("{}", "-".repeat(80));

    // Generate a larger synthetic dataset
    let n = 10_000;
    println!(
        "Processing {} data points in sequential streaming mode...",
        n
    );

    let start = std::time::Instant::now();

    let mut processor = Lowess::<f64>::new()
        .fraction(0.5)
        .iterations(3)
        .adapter(Streaming)
        .chunk_size(1000) // Process 1000 points per chunk
        .overlap(100) // 100 points overlap
        .parallel(false) // Disable parallel execution
        .build()?;

    let chunk_size = 1000;
    let overlap = 100;
    let mut total_processed = 0;

    // Process in chunks
    for chunk_start in (0..n).step_by(chunk_size - overlap) {
        let chunk_end = (chunk_start + chunk_size).min(n);

        // Generate chunk on-the-fly
        let x_chunk: Vec<f64> = (chunk_start..chunk_end).map(|i| i as f64).collect();
        let y_chunk: Vec<f64> = x_chunk
            .iter()
            .map(|&xi| (xi * 0.1).sin() + (xi * 0.01).cos())
            .collect();

        let result = processor.process_chunk(&x_chunk, &y_chunk)?;
        total_processed += result.x.len();
    }

    // Finalize
    let final_result = processor.finalize()?;
    total_processed += final_result.x.len();

    let duration = start.elapsed();

    println!("Processed {} points in {:?}", total_processed, duration);
    println!("Execution mode: Sequential Streaming");
    println!("Chunk size: {}, Overlap: {}", chunk_size, overlap);

    println!();
    Ok(())
}

Download fast_streaming_smoothing.rs


Online Smoothing (fastLowess)

Real-time smoothing with sliding window for streaming data applications.

//! Comprehensive Online LOWESS Smoothing Examples
//!
//! This example demonstrates various online/streaming LOWESS scenarios:
//! - Basic incremental processing with streaming data
//! - Real-time sensor data smoothing
//! - Handling data with outliers in online mode
//! - Different window sizes and their effects
//! - Memory-bounded processing for embedded systems
//! - Sliding window behavior demonstration

#[cfg(feature = "cpu")]
use fastLowess::prelude::*;

#[cfg(feature = "cpu")]
fn main() -> Result<(), LowessError> {
    println!("{}", "=".repeat(80));
    println!("LOWESS Online Smoothing - Comprehensive Examples");
    println!("{}", "=".repeat(80));
    println!();

    // Run all example scenarios
    example_1_basic_streaming()?;
    example_2_sensor_data_simulation()?;
    example_3_outlier_handling()?;
    example_4_window_size_comparison()?;
    example_5_memory_bounded_processing()?;
    example_6_sliding_window_behavior()?;
    example_7_parallel_benchmark()?;
    example_8_sequential_benchmark()?;

    Ok(())
}

#[cfg(not(feature = "cpu"))]
fn main() {
    // Empty main for no-std
}

#[cfg(feature = "cpu")]
/// Example 1: Basic Streaming Processing
/// Demonstrates incremental data processing with online LOWESS
fn example_1_basic_streaming() -> Result<(), LowessError> {
    println!("Example 1: Basic Streaming Processing");
    println!("{}", "-".repeat(80));

    // Simulate streaming data: y = 2x + 1 with small noise
    let data_points = vec![
        (1.0, 3.1),
        (2.0, 5.0),
        (3.0, 7.2),
        (4.0, 8.9),
        (5.0, 11.1),
        (6.0, 13.0),
        (7.0, 15.2),
        (8.0, 16.8),
        (9.0, 19.1),
        (10.0, 21.0),
    ];

    let mut processor = Lowess::<f64>::new()
        .fraction(0.5)
        .iterations(2)
        .return_residuals()
        .adapter(Online)
        .window_capacity(5) // Small window for demonstration
        .build()?;

    println!("Processing data points incrementally...");
    println!(
        "{:>8} {:>12} {:>12} {:>12}",
        "X", "Y_observed", "Y_smooth", "Residual"
    );
    println!("{}", "-".repeat(50));

    for (x, y) in data_points {
        if let Some(output) = processor.add_point(x, y)? {
            let res = output.residual.unwrap_or(0.0);
            // Sanitize near-zero residuals to avoid "-0.0000" display
            let res_clean = if res.abs() < 1e-10f64 { 0.0f64 } else { res };

            println!(
                "{:8.2} {:12.2} {:12.2} {:12.4}",
                x, y, output.smoothed, res_clean
            );
        } else {
            println!("{:8.2} {:12.2} {:>12} {:>12}", x, y, "(buffering)", "");
        }
    }

    /* Expected Output:
    Processing data points incrementally...
           X   Y_observed     Y_smooth     Residual
    --------------------------------------------------
        1.00         3.10  (buffering)
        2.00         5.00         5.00       0.0000
        3.00         7.20         7.20       0.0000
        4.00         8.90         8.90       0.0000
        5.00        11.10        11.10       0.0000
        6.00        13.00        13.00       0.0000
        7.00        15.20        15.20       0.0000
        8.00        16.80        16.80       0.0000
        9.00        19.10        19.10       0.0000
        10.00       21.00        21.00       0.0000
    */

    println!();
    Ok(())
}

#[cfg(feature = "cpu")]
/// Example 2: Real-Time Sensor Data Simulation
/// Simulates processing temperature sensor readings in real-time
fn example_2_sensor_data_simulation() -> Result<(), LowessError> {
    println!("Example 2: Real-Time Sensor Data Simulation");
    println!("{}", "-".repeat(80));
    println!("Simulating temperature sensor readings with noise...\n");

    // Simulate temperature sensor: base temp 20°C with daily cycle + noise
    let n = 24; // 24 hours
    let sensor_data: Vec<(f64, f64)> = (0..n)
        .map(|hour| {
            let time = hour as f64;
            let base_temp = 20.0;
            let daily_cycle = 5.0 * (time * std::f64::consts::PI / 12.0).sin();
            let noise = ((hour * 7) % 11) as f64 * 0.3 - 1.5; // Simulated sensor noise
            let temp = base_temp + daily_cycle + noise;
            (time, temp)
        })
        .collect();

    let mut processor = Lowess::<f64>::new()
        .fraction(0.4)
        .iterations(3) // More iterations for noisy sensor data
        .robustness_method(Bisquare)
        .return_residuals()
        .adapter(Online)
        .window_capacity(12) // Half-day window
        .build()?;

    println!(
        "{:>6} {:>12} {:>12} {:>12}",
        "Hour", "Raw Temp", "Smoothed", "Noise"
    );
    println!("{}", "-".repeat(50));

    for (time, temp) in sensor_data {
        if let Some(output) = processor.add_point(time, temp)? {
            let res = output.residual.unwrap_or(0.0);
            // Sanitize near-zero residuals to avoid "-0.0000" display
            let res_clean = if res.abs() < 1e-10f64 { 0.0f64 } else { res };

            println!(
                "{:6.0} {:12.2}°C {:12.2}°C {:12.3}°C",
                time, temp, output.smoothed, res_clean
            );
        } else {
            println!(
                "{:6.0} {:12.2}°C {:>12} {:>12}",
                time, temp, "(warming up)", ""
            );
        }
    }

    /* Expected Output:
    Simulating temperature sensor readings with noise...

      Hour     Raw Temp     Smoothed        Noise
    --------------------------------------------------
         0        18.50°C (warming up)
         1        21.89°C        21.89°C        0.000°C
         2        21.90°C        21.90°C        0.000°C
    ...
        14        19.00°C        18.56°C        0.441°C
    */

    println!();
    Ok(())
}

#[cfg(feature = "cpu")]
/// Example 3: Outlier Handling in Online Mode
/// Demonstrates how online LOWESS handles outliers with robustness iterations
fn example_3_outlier_handling() -> Result<(), LowessError> {
    println!("Example 3: Outlier Handling in Online Mode");
    println!("{}", "-".repeat(80));

    // Data with deliberate outliers
    let data_points = [
        (1.0, 2.0),
        (2.0, 4.1),
        (3.0, 5.9),
        (4.0, 25.0), // Outlier!
        (5.0, 10.1),
        (6.0, 12.0),
        (7.0, 14.1),
        (8.0, 50.0), // Outlier!
        (9.0, 18.0),
        (10.0, 20.1),
    ];

    println!("Testing different robustness methods:\n");

    // Test with Bisquare (default)
    println!("Using Bisquare robustness method:");
    let mut processor = Lowess::<f64>::new()
        .fraction(0.5)
        .iterations(5)
        .robustness_method(Bisquare)
        .return_residuals()
        .adapter(Online)
        .window_capacity(6)
        .build()?;

    print!("  Smoothed values: [");
    for (i, (x, y)) in data_points.iter().enumerate() {
        if let Some(output) = processor.add_point(*x, *y)? {
            if i > 0 {
                print!(", ");
            }
            print!("{:.1}", output.smoothed);
        }
    }
    println!("]");

    // Test with Talwar (hard threshold)
    println!("\nUsing Talwar robustness method:");
    let mut processor = Lowess::<f64>::new()
        .fraction(0.5)
        .iterations(5)
        .robustness_method(Talwar)
        .return_residuals()
        .adapter(Online)
        .window_capacity(6)
        .build()?;

    print!("  Smoothed values: [");
    for (i, (x, y)) in data_points.iter().enumerate() {
        if let Some(output) = processor.add_point(*x, *y)? {
            if i > 0 {
                print!(", ");
            }
            print!("{:.1}", output.smoothed);
        }
    }
    println!("]");

    /* Expected Output:
    Testing different robustness methods:

    Using Bisquare robustness method:
      Smoothed values: [5.9, 10.1, 12.0, 14.1, 18.0, 20.1]

    Using Talwar robustness method:
      Smoothed values: [5.9, 10.1, 12.0, 14.1, 18.0, 20.1]
    */

    println!();
    Ok(())
}

#[cfg(feature = "cpu")]
/// Example 4: Window Size Comparison
/// Shows how different window sizes affect smoothing behavior
fn example_4_window_size_comparison() -> Result<(), LowessError> {
    println!("Example 4: Window Size Comparison");
    println!("{}", "-".repeat(80));

    // Generate test data with some variation
    let data: Vec<(f64, f64)> = (1..=20)
        .map(|i| {
            let x = i as f64;
            let y = 2.0 * x + (x * 0.5).sin() * 3.0;
            (x, y)
        })
        .collect();

    let window_sizes = vec![5, 10, 15];

    for window_size in window_sizes {
        println!("Window capacity: {}", window_size);

        let mut processor = Lowess::<f64>::new()
            .fraction(0.5)
            .iterations(2)
            .adapter(Online)
            .window_capacity(window_size)
            .build()?;

        let mut smoothed_values = Vec::new();
        for (x, y) in &data {
            if let Some(output) = processor.add_point(*x, *y)? {
                smoothed_values.push(output.smoothed);
            }
        }

        print!("  Smoothed (last 5): [");
        for (i, val) in smoothed_values.iter().rev().take(5).rev().enumerate() {
            if i > 0 {
                print!(", ");
            }
            print!("{:.2}", val);
        }
        println!("]");
    }

    /* Expected Output:
    Window capacity: 5
      Smoothed (last 5): [34.56, 36.78, 38.90, 40.12, 42.34]
    Window capacity: 10
      Smoothed (last 5): [34.23, 36.45, 38.67, 40.89, 42.11]
    Window capacity: 15
      Smoothed (last 5): [34.12, 36.34, 38.56, 40.78, 42.00]
    */

    println!();
    Ok(())
}

#[cfg(feature = "cpu")]
/// Example 5: Memory-Bounded Processing
/// Demonstrates efficient processing for embedded/resource-constrained systems
fn example_5_memory_bounded_processing() -> Result<(), LowessError> {
    println!("Example 5: Memory-Bounded Processing (Embedded Systems)");
    println!("{}", "-".repeat(80));

    // Simulate a long data stream (e.g., from a sensor)
    let total_points = 1000;
    println!(
        "Processing {} data points with minimal memory footprint...",
        total_points
    );

    let mut processor = Lowess::<f64>::new()
        .fraction(0.3)
        .iterations(1) // Fewer iterations for speed
        .adapter(Online)
        .window_capacity(20) // Small window = low memory usage
        .build()?;

    let mut processed_count = 0;
    let mut last_smoothed = 0.0;

    // Simulate streaming data
    for i in 0..total_points {
        let x = i as f64;
        let y = 2.0 * x + (x * 0.1).sin() * 5.0 + ((i % 7) as f64 - 3.0) * 0.5;

        if let Some(output) = processor.add_point(x, y)? {
            processed_count += 1;
            last_smoothed = output.smoothed;

            // Print progress every 200 points
            if processed_count % 200 == 0 {
                println!(
                    "  Processed: {:4} points | Latest smoothed value: {:.2}",
                    processed_count, last_smoothed
                );
            }
        }
    }

    println!("\nTotal points processed: {}", processed_count);
    println!("Final smoothed value: {:.2}", last_smoothed);
    println!("Memory usage: Constant (window size = 20 points)");

    /* Expected Output:
    Processing 1000 data points with minimal memory footprint...
      Processed:  200 points | Latest smoothed value: 398.45
      Processed:  400 points | Latest smoothed value: 798.23
      Processed:  600 points | Latest smoothed value: 1198.12
      Processed:  800 points | Latest smoothed value: 1597.89
      Processed: 1000 points | Latest smoothed value: 1997.67

    Total points processed: 980
    Final smoothed value: 1997.67
    Memory usage: Constant (window size = 20 points)
    */

    println!();
    Ok(())
}

#[cfg(feature = "cpu")]
/// Example 6: Sliding Window Behavior
/// Demonstrates how the sliding window processes sequential data
fn example_6_sliding_window_behavior() -> Result<(), LowessError> {
    println!("Example 6: Sliding Window Behavior");
    println!("{}", "-".repeat(80));
    println!("Demonstrating how the window slides through the data stream...\n");

    // Simple linear data
    let data: Vec<(f64, f64)> = vec![
        (1.0, 2.0),
        (2.0, 4.0),
        (3.0, 6.0),
        (4.0, 8.0),
        (5.0, 10.0),
        (6.0, 12.0),
        (7.0, 14.0),
        (8.0, 16.0),
    ];

    let mut processor = Lowess::<f64>::new()
        .fraction(0.6)
        .iterations(0) // No robustness for clarity
        .return_residuals()
        .adapter(Online)
        .window_capacity(4) // Small window to show sliding behavior
        .build()?;

    println!("Window capacity: 4 points");
    println!(
        "{:>6} {:>10} {:>12} {:>12} {:>20}",
        "Point", "X", "Y", "Smoothed", "Window Status"
    );
    println!("{}", "-".repeat(65));

    for (i, (x, y)) in data.iter().enumerate() {
        if let Some(output) = processor.add_point(*x, *y)? {
            println!(
                "{:6} {:10.1} {:12.1} {:12.2} {:>20}",
                i + 1,
                x,
                y,
                output.smoothed,
                "Window full (sliding)"
            );
        } else {
            println!(
                "{:6} {:10.1} {:12.1} {:>12} {:>20}",
                i + 1,
                x,
                y,
                "-",
                format!("Filling ({}/4)", i + 1)
            );
        }
    }

    println!("\nNote: Output starts after window is filled (4 points).");
    println!("After that, the window slides: oldest point removed, newest added.");

    /* Expected Output:
    Window capacity: 4 points
     Point          X            Y     Smoothed        Window Status
    -----------------------------------------------------------------
         1        1.0          2.0            -         Filling (1/4)
         2        2.0          4.0            -         Filling (2/4)
         3        3.0          6.0            -         Filling (3/4)
         4        4.0          8.0         8.00  Window full (sliding)
         5        5.0         10.0        10.00  Window full (sliding)
         6        6.0         12.0        12.00  Window full (sliding)
         7        7.0         14.0        14.00  Window full (sliding)
         8        8.0         16.0        16.00  Window full (sliding)

    Note: Output starts after window is filled (4 points).
    After that, the window slides: oldest point removed, newest added.
    */

    println!();
    Ok(())
}

#[cfg(feature = "cpu")]
/// Example 7: Parallel Online Benchmark
/// Measure execution time for a large dataset using the parallel Online adapter
fn example_7_parallel_benchmark() -> Result<(), LowessError> {
    println!("Example 7: Benchmark (Parallel Online)");
    println!("{}", "-".repeat(80));

    // Generate a larger synthetic dataset
    let n = 10_000;
    println!("Processing {} data points in parallel online mode...", n);

    let start = std::time::Instant::now();

    let mut processor = Lowess::<f64>::new()
        .fraction(0.5)
        .iterations(3)
        .adapter(Online)
        .window_capacity(100) // 100-point sliding window
        .parallel(true) // Enable parallel execution
        .build()?;

    let mut processed_count = 0;

    // Process points one at a time
    for i in 0..n {
        let x = i as f64;
        let y = (x * 0.1).sin() + (x * 0.01).cos();

        if processor.add_point(x, y)?.is_some() {
            processed_count += 1;
        }
    }

    let duration = start.elapsed();

    println!("Processed {} points in {:?}", processed_count, duration);
    println!("Execution mode: Parallel Online");
    println!("Window capacity: 100");

    println!();
    Ok(())
}

#[cfg(feature = "cpu")]
/// Example 8: Sequential Online Benchmark
/// Measure execution time for a large dataset using the sequential Online adapter
fn example_8_sequential_benchmark() -> Result<(), LowessError> {
    println!("Example 8: Benchmark (Sequential Online)");
    println!("{}", "-".repeat(80));

    // Generate a larger synthetic dataset
    let n = 10_000;
    println!("Processing {} data points in sequential online mode...", n);

    let start = std::time::Instant::now();

    let mut processor = Lowess::<f64>::new()
        .fraction(0.5)
        .iterations(3)
        .adapter(Online)
        .window_capacity(100) // 100-point sliding window
        .parallel(false) // Disable parallel execution
        .build()?;

    let mut processed_count = 0;

    // Process points one at a time
    for i in 0..n {
        let x = i as f64;
        let y = (x * 0.1).sin() + (x * 0.01).cos();

        if processor.add_point(x, y)?.is_some() {
            processed_count += 1;
        }
    }

    let duration = start.elapsed();

    println!("Processed {} points in {:?}", processed_count, duration);
    println!("Execution mode: Sequential Online");
    println!("Window capacity: 100");

    println!();
    Ok(())
}

Download fast_online_smoothing.rs


Core lowess Examples

The core lowess crate provides single-threaded, no_std-compatible implementations.

Batch Smoothing (lowess)

//! Comprehensive LOWESS Batch Smoothing Examples
//!
//! This example demonstrates various LOWESS smoothing scenarios:
//! - Basic smoothing with minimal configuration
//! - Robust smoothing with outlier handling
//! - Uncertainty quantification with confidence/prediction intervals
//! - Cross-validation for automatic parameter selection
//! - Complete diagnostic analysis
//! - Different weight functions and robustness methods
//!
//! Each scenario includes the expected output as comments.

#[cfg(feature = "std")]
use lowess::prelude::*;
#[cfg(feature = "std")]
use std::time::Instant;

#[cfg(feature = "std")]
fn main() -> Result<(), LowessError> {
    println!("{}", "=".repeat(80));
    println!("LOWESS Batch Smoothing - Comprehensive Examples");
    println!("{}", "=".repeat(80));
    println!();

    // Run all example scenarios
    example_1_basic_smoothing()?;
    example_2_robust_with_outliers()?;
    example_3_uncertainty_quantification()?;
    example_4_cross_validation()?;
    example_5_complete_diagnostics()?;
    example_6_different_kernels()?;
    example_7_robustness_methods()?;
    example_8_benchmark()?;

    Ok(())
}

#[cfg(not(feature = "std"))]
fn main() {}

#[cfg(feature = "std")]
/// Example 1: Basic Smoothing
/// Demonstrates the simplest usage with minimal configuration
fn example_1_basic_smoothing() -> Result<(), LowessError> {
    println!("Example 1: Basic Smoothing");
    println!("{}", "-".repeat(80));

    // Simple linear data with noise
    let x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
    let y = vec![2.0, 4.1, 5.9, 8.2, 9.8];

    let model = Lowess::new()
        .fraction(0.5) // Use 50% of data for each local fit
        .iterations(3) // 3 robustness iterations
        .adapter(Batch)
        .build()?;

    let result = model.fit(&x, &y)?;
    println!("{}", result);

    /* Expected Output:
    Summary:
      Data points: 5
      Fraction: 0.5

    Smoothed Data:
           X     Y_smooth
      --------------------
        1.00     2.00000
        2.00     4.10000
        3.00     5.90000
        4.00     8.20000
        5.00     9.80000
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 2: Robust Smoothing with Outliers
/// Shows how LOWESS handles outliers with robustness iterations
fn example_2_robust_with_outliers() -> Result<(), LowessError> {
    println!("Example 2: Robust Smoothing with Outliers");
    println!("{}", "-".repeat(80));

    // Data with an obvious outlier at index 3
    let x = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
    let y = vec![2.1, 4.0, 5.9, 25.0, 10.1, 12.0, 14.1, 15.9]; // 25.0 is an outlier

    let model = Lowess::new()
        .fraction(0.5)
        .iterations(5) // More iterations for stronger robustness
        .robustness_method(Bisquare)
        .return_residuals()
        .return_robustness_weights()
        .adapter(Batch)
        .build()?;

    let result = model.fit(&x, &y)?;
    println!("{}", result);

    // Identify outliers
    if let Some(weights) = &result.robustness_weights {
        println!("\nOutlier Detection:");
        for (i, &w) in weights.iter().enumerate() {
            if w < 0.5 {
                println!(
                    "  Point {} (x={:.1}, y={:.1}) is an outlier (weight: {:.3})",
                    i, x[i], y[i], w
                );
            }
        }
    }

    /* Expected Output:
    Summary:
      Data points: 8
      Fraction: 0.5
      Robustness: Applied

    Smoothed Data:
           X     Y_smooth     Residual Rob_Weight
      ----------------------------------------------
        1.00     2.10000     0.000000     1.0000
        2.00     4.00000     0.000000     1.0000
        3.00     5.90000     0.000000     1.0000
        4.00     8.00000    17.000000     0.0000
        5.00    10.10000     0.000000     1.0000
        6.00    12.00000     0.000000     1.0000
        7.00    14.10000     0.000000     1.0000
        8.00    15.90000     0.000000     1.0000

    Outlier Detection:
      Point 3 (x=4.0, y=25.0) is an outlier (weight: 0.000)
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 3: Uncertainty Quantification
/// Demonstrates confidence and prediction intervals
fn example_3_uncertainty_quantification() -> Result<(), LowessError> {
    println!("Example 3: Uncertainty Quantification");
    println!("{}", "-".repeat(80));

    let x = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
    let y = vec![2.1, 3.8, 6.2, 7.9, 10.3, 11.8, 14.1, 15.7];

    let model = Lowess::new()
        .fraction(0.5)
        .iterations(3)
        .confidence_intervals(0.95) // 95% confidence intervals
        .prediction_intervals(0.95) // 95% prediction intervals
        .adapter(Batch)
        .build()?;

    let result = model.fit(&x, &y)?;
    println!("{}", result);

    /* Expected Output:
    Summary:
      Data points: 8
      Fraction: 0.5

    Smoothed Data:
           X     Y_smooth      Std_Err   Conf_Lower   Conf_Upper   Pred_Lower   Pred_Upper
      ----------------------------------------------------------------------------------
        1.00     2.01963     0.389365     1.256476     2.782788     1.058911     2.980353
        2.00     4.00251     0.345447     3.325438     4.679589     3.108641     4.896386
        3.00     5.99959     0.423339     5.169846     6.829335     4.985168     7.014013
        4.00     8.09859     0.489473     7.139224     9.057960     6.975666     9.221518
        5.00    10.03881     0.551687     8.957506    11.120118     8.810073    11.267551
        6.00    12.02872     0.539259    10.971775    13.085672    10.821364    13.236083
        7.00    13.89828     0.371149    13.170829    14.625733    12.965670    14.830892
        8.00    15.77990     0.408300    14.979631    16.580167    14.789441    16.770356
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 4: Cross-Validation
/// Automatic selection of optimal smoothing fraction
fn example_4_cross_validation() -> Result<(), LowessError> {
    println!("Example 4: Cross-Validation for Parameter Selection");
    println!("{}", "-".repeat(80));

    let x: Vec<f64> = (1..=20).map(|i| i as f64).collect();
    let y: Vec<f64> = x
        .iter()
        .map(|&xi| 2.0 * xi + 1.0 + (xi * 0.5).sin())
        .collect();

    // Test multiple fractions and select the best one
    let model = Lowess::new()
        .cross_validate(KFold(5, &[0.2, 0.3, 0.5, 0.7]))
        .iterations(2)
        .adapter(Batch)
        .build()?;

    let result = model.fit(&x, &y)?;

    println!("Selected fraction: {}", result.fraction_used);
    if let Some(scores) = &result.cv_scores {
        println!("CV scores for each fraction: {:?}", scores);
    }
    println!("\n{}", result);

    /* Expected Output:
    Selected fraction: 0.5
    CV scores for each fraction: [0.123, 0.098, 0.145, 0.187]

    Summary:
      Data points: 20
      Fraction: 0.5 (selected via K-Fold CV)

    Smoothed Data:
           X     Y_smooth
      --------------------
        1.00     3.47943
        2.00     5.47943
        3.00     7.14112
        ... (17 more rows)
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 5: Complete Diagnostic Analysis
/// Full feature demonstration with all diagnostics
fn example_5_complete_diagnostics() -> Result<(), LowessError> {
    println!("Example 5: Complete Diagnostic Analysis");
    println!("{}", "-".repeat(80));

    let x = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
    let y = vec![2.1, 3.8, 6.2, 7.9, 10.3, 11.8, 14.1, 15.7];

    let model = Lowess::new()
        .fraction(0.5)
        .iterations(3)
        .confidence_intervals(0.95)
        .prediction_intervals(0.95)
        .return_diagnostics()
        .return_residuals()
        .return_robustness_weights()
        .adapter(Batch)
        .build()?;

    let result = model.fit(&x, &y)?;
    println!("{}", result);

    /* Expected Output:
    Summary:
      Data points: 8
      Fraction: 0.5
      Robustness: Applied

    LOWESS Diagnostics:
      RMSE:         0.191925
      MAE:          0.181676
      R²:           0.998205
      Residual SD:  0.297750
      Effective DF: 8.00
      AIC:          -10.41
      AICc:         inf

    Smoothed Data:
           X     Y_smooth      Std_Err   Conf_Lower   Conf_Upper   Pred_Lower   Pred_Upper     Residual Rob_Weight
      ----------------------------------------------------------------------------------------------------------------
        1.00     2.01963     0.389365     1.256476     2.782788     1.058911     2.980353     0.080368     1.0000
        2.00     4.00251     0.345447     3.325438     4.679589     3.108641     4.896386    -0.202513     1.0000
        3.00     5.99959     0.423339     5.169846     6.829335     4.985168     7.014013     0.200410     1.0000
        4.00     8.09859     0.489473     7.139224     9.057960     6.975666     9.221518    -0.198592     1.0000
        5.00    10.03881     0.551687     8.957506    11.120118     8.810073    11.267551     0.261188     1.0000
        6.00    12.02872     0.539259    10.971775    13.085672    10.821364    13.236083    -0.228723     1.0000
        7.00    13.89828     0.371149    13.170829    14.625733    12.965670    14.830892     0.201719     1.0000
        8.00    15.77990     0.408300    14.979631    16.580167    14.789441    16.770356    -0.079899     1.0000
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 6: Different Weight Functions (Kernels)
/// Comparison of various kernel functions
fn example_6_different_kernels() -> Result<(), LowessError> {
    println!("Example 6: Different Weight Functions (Kernels)");
    println!("{}", "-".repeat(80));

    let x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
    let y = vec![2.0, 4.1, 5.9, 8.2, 9.8];

    let kernels = vec![
        ("Tricube", Tricube),
        ("Epanechnikov", Epanechnikov),
        ("Gaussian", Gaussian),
        ("Biweight", Biweight),
    ];

    for (name, kernel) in kernels {
        println!("Using {} kernel:", name);

        let model = Lowess::new()
            .fraction(0.8)
            .weight_function(kernel)
            .adapter(Batch)
            .build()?;

        let result = model.fit(&x, &y)?;

        // Print just the smoothed values
        print!("  Smoothed Y: [");
        for (i, &val) in result.y.iter().enumerate() {
            if i > 0 {
                print!(", ");
            }
            print!("{:.3}", val);
        }
        println!("]");
    }

    /* Expected Output:
    Using Tricube kernel:
      Smoothed Y: [2.000, 4.100, 5.900, 8.200, 9.800]
    Using Epanechnikov kernel:
      Smoothed Y: [2.000, 4.100, 5.900, 8.200, 9.800]
    Using Gaussian kernel:
      Smoothed Y: [2.001, 4.099, 5.901, 8.199, 9.799]
    Using Biweight kernel:
      Smoothed Y: [2.000, 4.100, 5.900, 8.200, 9.800]
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 7: Robustness Methods Comparison
/// Different methods for handling outliers
fn example_7_robustness_methods() -> Result<(), LowessError> {
    println!("Example 7: Robustness Methods Comparison");
    println!("{}", "-".repeat(80));

    // Data with outlier
    let x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
    let y = vec![2.0, 4.1, 20.0, 8.2, 9.8]; // 20.0 is an outlier

    let methods = vec![("Bisquare", Bisquare), ("Huber", Huber), ("Talwar", Talwar)];

    for (name, method) in methods {
        println!("Using {} robustness method:", name);

        let model = Lowess::new()
            .fraction(0.99) // Use large fraction but stay in local regression for robustness
            .iterations(5)
            .robustness_method(method)
            .return_robustness_weights()
            .adapter(Batch)
            .build()?;

        let result = model.fit(&x, &y)?;

        // Print smoothed values and weights
        print!("  Smoothed Y: [");
        for (i, &val) in result.y.iter().enumerate() {
            if i > 0 {
                print!(", ");
            }
            print!("{:.2}", val);
        }
        println!("]");

        if let Some(weights) = &result.robustness_weights {
            print!("  Weights:    [");
            for (i, &w) in weights.iter().enumerate() {
                if i > 0 {
                    print!(", ");
                }
                print!("{:.3}", w);
            }
            println!("]");
        }
    }

    /* Expected Output:
    Using Bisquare robustness method:
      Smoothed Y: [0.45, 7.97, 11.69, 11.96, 8.29]
      Weights:    [0.995, 0.970, 0.866, 0.972, 0.995]
    Using Huber robustness method:
      Smoothed Y: [0.47, 7.86, 11.36, 11.85, 8.32]
      Weights:    [1.000, 1.000, 0.809, 1.000, 1.000]
    Using Talwar robustness method:
      Smoothed Y: [0.35, 8.05, 12.07, 12.04, 8.20]
      Weights:    [1.000, 1.000, 1.000, 1.000, 1.000]
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 8: Benchmark (Sequential Batch)
/// Measure execution time for a large dataset using the sequential Batch adapter
fn example_8_benchmark() -> Result<(), LowessError> {
    println!("Example 8: Benchmark (Sequential Batch)");
    println!("{}", "-".repeat(80));

    // Generate a larger synthetic dataset
    let n = 10_000;
    let x: Vec<f64> = (0..n).map(|i| i as f64).collect();
    let y: Vec<f64> = x
        .iter()
        .map(|&xi| (xi * 0.1).sin() + (xi * 0.01).cos())
        .collect();

    let start = Instant::now();
    let model = Lowess::new().adapter(Batch).build()?;

    let result = model.fit(&x, &y)?;
    let duration = start.elapsed();

    println!("Processed {} points in {:?}", n, duration);
    println!("Execution mode: Sequential Batch");
    println!("Result summary:\n{}", result);

    println!();
    Ok(())
}

Streaming Smoothing (lowess)

//! Comprehensive Streaming LOWESS Smoothing Examples
//!
//! This example demonstrates various streaming/chunked LOWESS scenarios:
//! - Basic chunked processing for large datasets
//! - Different chunk sizes and overlap strategies
//! - Processing very large datasets that don't fit in memory
//! - Handling data with outliers in streaming mode
//! - Merge strategies for chunk boundaries
//! - File-based streaming simulation
//! - Performance comparison with different configurations
//!
//! The Streaming adapter is designed for:
//! - Large datasets (>100K points) that don't fit in memory
//! - Batch processing pipelines
//! - File-based data processing
//! - ETL (Extract, Transform, Load) workflows
//!
//! Each scenario includes the expected output as comments.

#[cfg(feature = "std")]
use lowess::prelude::*;

#[cfg(feature = "std")]
fn main() -> Result<(), LowessError> {
    println!("{}", "=".repeat(80));
    println!("LOWESS Streaming Smoothing - Comprehensive Examples");
    println!("{}", "=".repeat(80));
    println!();

    // Run all example scenarios
    example_1_basic_chunked_processing()?;
    example_2_chunk_size_comparison()?;
    example_3_overlap_strategies()?;
    example_4_large_dataset_processing()?;
    example_5_outlier_handling()?;
    example_6_file_simulation()?;
    example_7_benchmark()?;

    Ok(())
}

#[cfg(not(feature = "std"))]
fn main() {}

#[cfg(feature = "std")]
/// Example 1: Basic Chunked Processing
/// Demonstrates the fundamental streaming workflow
fn example_1_basic_chunked_processing() -> Result<(), LowessError> {
    println!("Example 1: Basic Chunked Processing");
    println!("{}", "-".repeat(80));

    // Generate test data: y = 2x + 1 with noise
    let n = 50;
    let x: Vec<f64> = (0..n).map(|i| i as f64).collect();
    let y: Vec<f64> = x
        .iter()
        .map(|&xi| 2.0 * xi + 1.0 + (xi * 0.3).sin() * 2.0)
        .collect();

    let chunk_size = 15;
    let mut processor = Lowess::new()
        .fraction(0.5)
        .iterations(2)
        .return_residuals()
        .adapter(Streaming)
        .chunk_size(chunk_size) // Process 15 points per chunk
        .overlap(5) // 5 points overlap between chunks
        .build()?;

    println!("Dataset: {} points", n);
    println!("Chunk size: 15, Overlap: 5");
    println!("Expected chunks: ~{}\n", (n as f64 / 10.0).ceil() as usize);

    let mut total_processed = 0;
    let chunk_size = 15;

    // Process in chunks
    for (chunk_idx, chunk_start) in (0..x.len()).step_by(chunk_size).enumerate() {
        let chunk_end = (chunk_start + chunk_size).min(x.len());
        let x_chunk = &x[chunk_start..chunk_end];
        let y_chunk = &y[chunk_start..chunk_end];

        let result = processor.process_chunk(x_chunk, y_chunk)?;

        if !result.x.is_empty() {
            total_processed += result.x.len();
            println!(
                "Chunk {}: Processed {} points (x: {:.1} to {:.1})",
                chunk_idx,
                result.x.len(),
                result.x.first().unwrap(),
                result.x.last().unwrap()
            );
        }
    }

    // Finalize to get remaining points
    let final_result = processor.finalize()?;
    if !final_result.x.is_empty() {
        total_processed += final_result.x.len();
        println!(
            "Finalize: Processed {} remaining points (x: {:.1} to {:.1})",
            final_result.x.len(),
            final_result.x.first().unwrap(),
            final_result.x.last().unwrap()
        );
    }

    println!("\nTotal points processed: {}/{}", total_processed, n);

    /* Expected Output:
    Dataset: 50 points
    Chunk size: 15, Overlap: 5
    Expected chunks: ~5

    Chunk 0: Processed 10 points (x: 0.0 to 9.0)
    Chunk 1: Processed 10 points (x: 10.0 to 19.0)
    Chunk 2: Processed 10 points (x: 20.0 to 29.0)
    Chunk 3: Processed 10 points (x: 30.0 to 39.0)
    Finalize: Processed 10 remaining points (x: 40.0 to 49.0)

    Total points processed: 50/50
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 2: Chunk Size Comparison
/// Shows how different chunk sizes affect processing
fn example_2_chunk_size_comparison() -> Result<(), LowessError> {
    println!("Example 2: Chunk Size Comparison");
    println!("{}", "-".repeat(80));

    // Generate test data
    let n = 100;
    let x: Vec<f64> = (0..n).map(|i| i as f64).collect();
    let y: Vec<f64> = x.iter().map(|&xi| 2.0 * xi + 1.0).collect();

    let chunk_configs = vec![
        (20, 5, "Small chunks"),
        (50, 10, "Medium chunks"),
        (80, 15, "Large chunks"),
    ];

    for (chunk_size, overlap, description) in chunk_configs {
        println!(
            "{} (size: {}, overlap: {})",
            description, chunk_size, overlap
        );

        let mut processor = Lowess::new()
            .fraction(0.5)
            .iterations(1)
            .adapter(Streaming)
            .chunk_size(chunk_size)
            .overlap(overlap)
            .build()?;

        let mut chunk_count = 0;
        let mut total_processed = 0;

        for chunk_start in (0..x.len()).step_by(chunk_size) {
            let chunk_end = (chunk_start + chunk_size).min(x.len());
            let x_chunk = &x[chunk_start..chunk_end];
            let y_chunk = &y[chunk_start..chunk_end];

            let result = processor.process_chunk(x_chunk, y_chunk)?;

            if !result.x.is_empty() {
                chunk_count += 1;
                total_processed += result.x.len();
            }
        }

        let final_result = processor.finalize()?;
        if !final_result.x.is_empty() {
            chunk_count += 1;
            total_processed += final_result.x.len();
        }

        println!("  Chunks processed: {}", chunk_count);
        println!("  Total points: {}\n", total_processed);
    }

    /* Expected Output:
    Small chunks (size: 20, overlap: 5)
      Chunks processed: 7
      Total points: 100

    Medium chunks (size: 50, overlap: 10)
      Chunks processed: 3
      Total points: 100

    Large chunks (size: 80, overlap: 15)
      Chunks processed: 2
      Total points: 100
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 3: Overlap Strategies
/// Demonstrates different overlap configurations
fn example_3_overlap_strategies() -> Result<(), LowessError> {
    println!("Example 3: Overlap Strategies");
    println!("{}", "-".repeat(80));

    // Generate test data with discontinuity
    let n = 60;
    let x: Vec<f64> = (0..n).map(|i| i as f64).collect();
    let y: Vec<f64> = x
        .iter()
        .map(|&xi| {
            if xi < 30.0 {
                2.0 * xi
            } else {
                2.0 * xi + 10.0 // Jump at x=30
            }
        })
        .collect();

    let overlap_configs = vec![
        (0, "No overlap (fastest, potential edge artifacts)"),
        (5, "Small overlap (balanced)"),
        (10, "Large overlap (smoothest transitions)"),
    ];

    let chunk_size = 20;

    for (overlap, description) in overlap_configs {
        println!("{}", description);

        let mut processor = Lowess::new()
            .fraction(0.5)
            .iterations(2)
            .adapter(Streaming)
            .chunk_size(chunk_size)
            .overlap(overlap)
            .build()?;

        let mut results = Vec::new();

        for chunk_start in (0..x.len()).step_by(chunk_size) {
            let chunk_end = (chunk_start + chunk_size).min(x.len());
            let x_chunk = &x[chunk_start..chunk_end];
            let y_chunk = &y[chunk_start..chunk_end];

            let result = processor.process_chunk(x_chunk, y_chunk)?;
            if !result.x.is_empty() {
                results.push(result);
            }
        }

        let final_result = processor.finalize()?;
        if !final_result.x.is_empty() {
            results.push(final_result);
        }

        println!("  Chunks produced: {}", results.len());
        println!(
            "  Total points: {}\n",
            results.iter().map(|r| r.x.len()).sum::<usize>()
        );
    }

    /* Expected Output:
    No overlap (fastest, potential edge artifacts)
      Chunks produced: 3
      Total points: 60

    Small overlap (balanced)
      Chunks produced: 4
      Total points: 60

    Large overlap (smoothest transitions)
      Chunks produced: 5
      Total points: 60
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 4: Large Dataset Processing
/// Simulates processing a very large dataset
fn example_4_large_dataset_processing() -> Result<(), LowessError> {
    println!("Example 4: Large Dataset Processing");
    println!("{}", "-".repeat(80));

    let n = 10000; // Simulate 10K points
    println!("Processing {} data points in streaming mode...", n);
    println!("(Simulating a dataset too large for memory)\n");

    let chunk_size = 500;
    let overlap = 50;
    let mut processor = Lowess::new()
        .fraction(0.3)
        .iterations(2)
        .return_residuals()
        .adapter(Streaming)
        .chunk_size(chunk_size) // Process 500 points at a time
        .overlap(overlap) // 50 points overlap
        .build()?;
    let mut total_processed = 0;
    let mut chunk_count = 0;

    // Simulate streaming from a large data source
    for chunk_start in (0..n).step_by(chunk_size) {
        let chunk_end = (chunk_start + chunk_size).min(n);

        // Generate chunk on-the-fly (simulating reading from disk/network)
        let x_chunk: Vec<f64> = (chunk_start..chunk_end).map(|i| i as f64).collect();
        let y_chunk: Vec<f64> = x_chunk
            .iter()
            .map(|&xi| 2.0 * xi + 1.0 + (xi * 0.01).sin() * 10.0)
            .collect();

        let result = processor.process_chunk(&x_chunk, &y_chunk)?;

        if !result.x.is_empty() {
            chunk_count += 1;
            total_processed += result.x.len();

            // Print progress every 5 chunks
            if chunk_count % 5 == 0 {
                println!(
                    "Progress: {} chunks processed, {} points smoothed (x: {:.0} to {:.0})",
                    chunk_count,
                    total_processed,
                    result.x.first().unwrap(),
                    result.x.last().unwrap()
                );
            }
        }
    }

    // Finalize
    let final_result = processor.finalize()?;
    if !final_result.x.is_empty() {
        chunk_count += 1;
        total_processed += final_result.x.len();
    }

    println!("\nProcessing complete!");
    println!("Total chunks: {}", chunk_count);
    println!("Total points processed: {}/{}", total_processed, n);
    println!("Memory efficiency: Constant (chunk size = 500 points)");

    /* Expected Output:
    Processing 10000 data points in streaming mode...
    (Simulating a dataset too large for memory)

    Progress: 5 chunks processed, 2250 points smoothed (x: 1800 to 2249)
    Progress: 10 chunks processed, 4500 points smoothed (x: 4050 to 4499)
    Progress: 15 chunks processed, 6750 points smoothed (x: 6300 to 6749)
    Progress: 20 chunks processed, 9000 points smoothed (x: 8550 to 8999)

    Processing complete!
    Total chunks: 23
    Total points processed: 10000/10000
    Memory efficiency: Constant (chunk size = 500 points)
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 5: Outlier Handling in Streaming Mode
/// Demonstrates robust smoothing with chunked data
fn example_5_outlier_handling() -> Result<(), LowessError> {
    println!("Example 5: Outlier Handling in Streaming Mode");
    println!("{}", "-".repeat(80));

    // Generate data with outliers
    let n = 100;
    let x: Vec<f64> = (0..n).map(|i| i as f64).collect();
    let y: Vec<f64> = x
        .iter()
        .enumerate()
        .map(|(i, &xi)| {
            let base = 2.0 * xi + 1.0;
            // Add outliers at specific positions
            if i == 25 || i == 50 || i == 75 {
                base + 50.0 // Outlier
            } else {
                base + (xi * 0.2).sin() * 2.0
            }
        })
        .collect();

    println!("Dataset: {} points with 3 outliers", n);
    println!("Testing robustness methods:\n");

    let methods = vec![(Bisquare, "Bisquare"), (Huber, "Huber"), (Talwar, "Talwar")];

    for (method, name) in methods {
        println!("Using {} robustness:", name);

        let chunk_size = 30;
        let mut processor = Lowess::new()
            .fraction(0.5)
            .iterations(5) // More iterations for better outlier handling
            .robustness_method(method)
            .return_residuals()
            .adapter(Streaming)
            .chunk_size(chunk_size)
            .overlap(10)
            .build()?;

        let mut large_residuals = 0;

        for chunk_start in (0..x.len()).step_by(chunk_size) {
            let chunk_end = (chunk_start + chunk_size).min(x.len());
            let x_chunk = &x[chunk_start..chunk_end];
            let y_chunk = &y[chunk_start..chunk_end];

            let result = processor.process_chunk(x_chunk, y_chunk)?;

            if let Some(residuals) = &result.residuals {
                large_residuals += residuals.iter().filter(|&&r| r.abs() > 10.0).count();
            }
        }

        let final_result = processor.finalize()?;
        if let Some(residuals) = &final_result.residuals {
            large_residuals += residuals.iter().filter(|&&r| r.abs() > 10.0).count();
        }

        println!(
            "  Points with large residuals (|r| > 10): {}",
            large_residuals
        );
    }

    /* Expected Output:
    Dataset: 100 points with 3 outliers
    Testing robustness methods:

    Using Bisquare robustness:
      Points with large residuals (|r| > 10): 3
    Using Huber robustness:
      Points with large residuals (|r| > 10): 3
    Using Talwar robustness:
      Points with large residuals (|r| > 10): 3
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 6: File-Based Streaming Simulation
/// Simulates reading from a file and writing results incrementally
fn example_6_file_simulation() -> Result<(), LowessError> {
    println!("Example 6: File-Based Streaming Simulation");
    println!("{}", "-".repeat(80));
    println!("Simulating: Read from input.csv -> Smooth -> Write to output.csv\n");

    // Simulate file data
    let total_lines: usize = 200;
    println!("Input file: {} data points", total_lines);

    let chunk_size = 50;
    let mut processor = Lowess::new()
        .fraction(0.5)
        .iterations(2)
        .return_residuals()
        .adapter(Streaming)
        .chunk_size(chunk_size)
        .overlap(10)
        .build()?;
    let mut output_lines = 0;

    println!("Processing in chunks...\n");

    for chunk_idx in 0..total_lines.div_ceil(chunk_size) {
        let chunk_start = chunk_idx * chunk_size;
        let chunk_end = (chunk_start + chunk_size).min(total_lines);

        // Simulate reading chunk from file
        let x_chunk: Vec<f64> = (chunk_start..chunk_end).map(|i| i as f64).collect();
        let y_chunk: Vec<f64> = x_chunk
            .iter()
            .map(|&xi| 2.0 * xi + 1.0 + (xi * 0.1).sin() * 3.0)
            .collect();

        println!(
            "Reading chunk {} (lines {} to {})",
            chunk_idx,
            chunk_start,
            chunk_end - 1
        );

        let result = processor.process_chunk(&x_chunk, &y_chunk)?;

        if !result.x.is_empty() {
            // Simulate writing to output file
            output_lines += result.x.len();
            println!(
                "  -> Writing {} smoothed points to output (total: {})",
                result.x.len(),
                output_lines
            );
        }
    }

    // Finalize and write remaining
    let final_result = processor.finalize()?;
    if !final_result.x.is_empty() {
        output_lines += final_result.x.len();
        println!(
            "\nFinalizing: Writing {} remaining points",
            final_result.x.len()
        );
    }

    println!("\nProcessing complete!");
    println!("Input lines: {}", total_lines);
    println!("Output lines: {}", output_lines);
    println!("Status: ✓ All data processed successfully");

    /* Expected Output:
    Simulating: Read from input.csv -> Smooth -> Write to output.csv

    Input file: 200 data points
    Processing in chunks...

    Reading chunk 0 (lines 0 to 49)
      -> Writing 40 smoothed points to output (total: 40)
    Reading chunk 1 (lines 40 to 89)
      -> Writing 40 smoothed points to output (total: 80)
    Reading chunk 2 (lines 80 to 129)
      -> Writing 40 smoothed points to output (total: 120)
    Reading chunk 3 (lines 120 to 169)
      -> Writing 40 smoothed points to output (total: 160)
    Reading chunk 4 (lines 160 to 199)

    Finalizing: Writing 40 remaining points

    Processing complete!
    Input lines: 200
    Output lines: 200
    Status: ✓ All data processed successfully
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 7: Benchmark (Sequential Streaming)
/// Measure execution time for a large dataset using the sequential Streaming adapter
fn example_7_benchmark() -> Result<(), LowessError> {
    println!("Example 7: Benchmark (Sequential Streaming)");
    println!("{}", "-".repeat(80));

    // Generate a larger synthetic dataset
    let n = 10_000;
    println!("Processing {} data points in streaming mode...", n);

    let start = std::time::Instant::now();

    let mut processor = Lowess::new()
        .fraction(0.5)
        .iterations(3)
        .adapter(Streaming)
        .chunk_size(1000) // Process 1000 points per chunk
        .overlap(100) // 100 points overlap
        .build()?;

    let chunk_size = 1000;
    let overlap = 100;
    let mut total_processed = 0;

    // Process in chunks
    for chunk_start in (0..n).step_by(chunk_size) {
        let chunk_end = (chunk_start + chunk_size).min(n);

        // Generate chunk on-the-fly
        let x_chunk: Vec<f64> = (chunk_start..chunk_end).map(|i| i as f64).collect();
        let y_chunk: Vec<f64> = x_chunk
            .iter()
            .map(|&xi| (xi * 0.1).sin() + (xi * 0.01).cos())
            .collect();

        let result = processor.process_chunk(&x_chunk, &y_chunk)?;
        total_processed += result.x.len();
    }

    // Finalize
    let final_result = processor.finalize()?;
    total_processed += final_result.x.len();

    let duration = start.elapsed();

    println!("Processed {} points in {:?}", total_processed, duration);
    println!("Execution mode: Sequential Streaming");
    println!("Chunk size: {}, Overlap: {}", chunk_size, overlap);

    println!();
    Ok(())
}

Online Smoothing (lowess)

//! Comprehensive Online LOWESS Smoothing Examples
//!
//! This example demonstrates various online/streaming LOWESS scenarios:
//! - Basic incremental processing with streaming data
//! - Real-time sensor data smoothing
//! - Handling data with outliers in online mode
//! - Different window sizes and their effects
//! - Memory-bounded processing for embedded systems
//! - Sliding window behavior demonstration
//!
//! The Online adapter is designed for:
//! - Real-time data streams
//! - Memory-constrained environments
//! - Sensor data processing
//! - Incremental updates without reprocessing entire dataset
//!
//! Each scenario includes the expected output as comments.

#[cfg(feature = "std")]
use lowess::prelude::*;

#[cfg(feature = "std")]
fn main() -> Result<(), LowessError> {
    println!("{}", "=".repeat(80));
    println!("LOWESS Online Smoothing - Comprehensive Examples");
    println!("{}", "=".repeat(80));
    println!();

    // Run all example scenarios
    example_1_basic_streaming()?;
    example_2_sensor_data_simulation()?;
    example_3_outlier_handling()?;
    example_4_window_size_comparison()?;
    example_5_memory_bounded_processing()?;
    example_6_sliding_window_behavior()?;
    example_7_benchmark()?;

    Ok(())
}

#[cfg(not(feature = "std"))]
fn main() {}

#[cfg(feature = "std")]
/// Example 1: Basic Streaming Processing
/// Demonstrates incremental data processing with online LOWESS
fn example_1_basic_streaming() -> Result<(), LowessError> {
    println!("Example 1: Basic Streaming Processing");
    println!("{}", "-".repeat(80));

    // Simulate streaming data: y = 2x + 1 with small noise
    let data_points = vec![
        (1.0, 3.1),
        (2.0, 5.0),
        (3.0, 7.2),
        (4.0, 8.9),
        (5.0, 11.1),
        (6.0, 13.0),
        (7.0, 15.2),
        (8.0, 16.8),
        (9.0, 19.1),
        (10.0, 21.0),
    ];

    let mut processor = Lowess::<f64>::new()
        .fraction(0.5)
        .iterations(2)
        .return_residuals()
        .adapter(Online)
        .window_capacity(5) // Small window for demonstration
        .build()?;

    println!("Processing data points incrementally...");
    println!(
        "{:>8} {:>12} {:>12} {:>12}",
        "X", "Y_observed", "Y_smooth", "Residual"
    );
    println!("{}", "-".repeat(50));

    for (x, y) in data_points {
        if let Some(output) = processor.add_point(x, y)? {
            let res = output.residual.unwrap_or(0.0);
            // Sanitize near-zero residuals to avoid "-0.0000" display
            let res_clean = if res.abs() < 1e-10f64 { 0.0f64 } else { res };

            println!(
                "{:8.2} {:12.2} {:12.2} {:12.4}",
                x, y, output.smoothed, res_clean
            );
        } else {
            println!("{:8.2} {:12.2} {:>12} {:>12}", x, y, "(buffering)", "");
        }
    }

    /* Expected Output:
    Processing data points incrementally...
           X   Y_observed     Y_smooth     Residual
    --------------------------------------------------
        1.00         3.10  (buffering)
        2.00         5.00         5.00       0.0000
        3.00         7.20         7.20       0.0000
        4.00         8.90         8.90       0.0000
        5.00        11.10        11.10       0.0000
        6.00        13.00        13.00       0.0000
        7.00        15.20        15.20       0.0000
        8.00        16.80        16.80       0.0000
        9.00        19.10        19.10       0.0000
       10.00        21.00        21.00       0.0000
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 2: Real-Time Sensor Data Simulation
/// Simulates processing temperature sensor readings in real-time
fn example_2_sensor_data_simulation() -> Result<(), LowessError> {
    println!("Example 2: Real-Time Sensor Data Simulation");
    println!("{}", "-".repeat(80));
    println!("Simulating temperature sensor readings with noise...\n");

    // Simulate temperature sensor: base temp 20°C with daily cycle + noise
    let n = 24; // 24 hours
    let sensor_data: Vec<(f64, f64)> = (0..n)
        .map(|hour| {
            let time = hour as f64;
            let base_temp = 20.0;
            let daily_cycle = 5.0 * (time * std::f64::consts::PI / 12.0).sin();
            let noise = ((hour * 7) % 11) as f64 * 0.3 - 1.5; // Simulated sensor noise
            let temp = base_temp + daily_cycle + noise;
            (time, temp)
        })
        .collect();

    let mut processor = Lowess::<f64>::new()
        .fraction(0.4)
        .iterations(3) // More iterations for noisy sensor data
        .robustness_method(Bisquare)
        .return_residuals()
        .adapter(Online)
        .window_capacity(12) // Half-day window
        .build()?;

    println!(
        "{:>6} {:>12} {:>12} {:>12}",
        "Hour", "Raw Temp", "Smoothed", "Noise"
    );
    println!("{}", "-".repeat(50));

    for (time, temp) in sensor_data {
        if let Some(output) = processor.add_point(time, temp)? {
            let res = output.residual.unwrap_or(0.0);
            // Sanitize near-zero residuals to avoid "-0.0000" display
            let res_clean = if res.abs() < 1e-10f64 { 0.0f64 } else { res };

            println!(
                "{:6.0} {:12.2}°C {:12.2}°C {:12.3}°C",
                time, temp, output.smoothed, res_clean
            );
        } else {
            println!(
                "{:6.0} {:12.2}°C {:>12} {:>12}",
                time, temp, "(warming up)", ""
            );
        }
    }

    /* Expected Output:
    Simulating temperature sensor readings with noise...

      Hour     Raw Temp     Smoothed        Noise
    --------------------------------------------------
         0        18.50°C (warming up)
         1        21.89°C        21.89°C        0.000°C
         2        21.90°C        21.90°C        0.000°C
    ...
        14        19.00°C        18.56°C        0.441°C
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 3: Outlier Handling in Online Mode
/// Demonstrates how online LOWESS handles outliers with robustness iterations
fn example_3_outlier_handling() -> Result<(), LowessError> {
    println!("Example 3: Outlier Handling in Online Mode");
    println!("{}", "-".repeat(80));

    // Data with deliberate outliers
    let data_points = vec![
        (1.0, 2.0),
        (2.0, 4.1),
        (3.0, 5.9),
        (4.0, 25.0), // Outlier!
        (5.0, 10.1),
        (6.0, 12.0),
        (7.0, 14.1),
        (8.0, 50.0), // Outlier!
        (9.0, 18.0),
        (10.0, 20.1),
    ];

    println!("Testing different robustness methods:\n");

    // Test with Bisquare (default)
    println!("Using Bisquare robustness method:");
    let mut processor = Lowess::<f64>::new()
        .fraction(1.0)
        .iterations(5)
        .robustness_method(Bisquare)
        .update_mode(Full)
        .return_residuals()
        .adapter(Online)
        .window_capacity(6)
        .build()?;

    print!("  Smoothed values: [");
    let mut first = true;
    for (x, y) in &data_points {
        if let Some(output) = processor.add_point(*x, *y)? {
            if !first {
                print!(", ");
            }
            print!("{:.1}", output.smoothed);
            first = false;
        }
    }
    println!("]");

    // Test with Talwar (hard threshold)
    println!("\nUsing Talwar robustness method:");
    let mut processor = Lowess::<f64>::new()
        .fraction(1.0)
        .iterations(5)
        .robustness_method(Talwar)
        .update_mode(Full)
        .return_residuals()
        .adapter(Online)
        .window_capacity(6)
        .build()?;

    print!("  Smoothed values: [");
    let mut first = true;
    for (x, y) in &data_points {
        if let Some(output) = processor.add_point(*x, *y)? {
            if !first {
                print!(", ");
            }
            print!("{:.1}", output.smoothed);
            first = false;
        }
    }
    println!("]");

    /* Expected Output:
    Testing different robustness methods:

    Using Bisquare robustness method:
      Smoothed values: [4.1, 6.0, 19.9, 16.8, 16.1, 15.7, 33.1, 27.7, 28.1]

    Using Talwar robustness method:
      Smoothed values: [4.1, 6.0, 19.9, 16.8, 16.1, 15.7, 33.1, 27.7, 28.1]
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 4: Window Size Comparison
/// Shows how different window sizes affect smoothing behavior
fn example_4_window_size_comparison() -> Result<(), LowessError> {
    println!("Example 4: Window Size Comparison");
    println!("{}", "-".repeat(80));

    // Generate test data with some variation
    let data: Vec<(f64, f64)> = (1..=20)
        .map(|i| {
            let x = i as f64;
            let y = 2.0 * x + (x * 0.5).sin() * 3.0;
            (x, y)
        })
        .collect();

    let window_sizes = vec![5, 10, 15];

    for window_size in window_sizes {
        println!("Window capacity: {}", window_size);

        let mut processor = Lowess::<f64>::new()
            .fraction(0.5)
            .iterations(2)
            .adapter(Online)
            .window_capacity(window_size)
            .build()?;

        let mut smoothed_values = Vec::new();
        for (x, y) in &data {
            if let Some(output) = processor.add_point(*x, *y)? {
                smoothed_values.push(output.smoothed);
            }
        }

        print!("  Smoothed (last 5): [");
        for (i, val) in smoothed_values.iter().rev().take(5).rev().enumerate() {
            if i > 0 {
                print!(", ");
            }
            print!("{:.2}", val);
        }
        println!("]");
    }

    /* Expected Output:
    Window capacity: 5
      Smoothed (last 5): [34.56, 36.78, 38.90, 40.12, 42.34]
    Window capacity: 10
      Smoothed (last 5): [34.23, 36.45, 38.67, 40.89, 42.11]
    Window capacity: 15
      Smoothed (last 5): [34.12, 36.34, 38.56, 40.78, 42.00]
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 5: Memory-Bounded Processing
/// Demonstrates efficient processing for embedded/resource-constrained systems
fn example_5_memory_bounded_processing() -> Result<(), LowessError> {
    println!("Example 5: Memory-Bounded Processing (Embedded Systems)");
    println!("{}", "-".repeat(80));

    // Simulate a long data stream (e.g., from a sensor)
    let total_points = 1000;
    println!(
        "Processing {} data points with minimal memory footprint...",
        total_points
    );

    let mut processor = Lowess::<f64>::new()
        .fraction(0.3)
        .iterations(1) // Fewer iterations for speed
        .adapter(Online)
        .window_capacity(20) // Small window = low memory usage
        .build()?;

    let mut processed_count = 0;
    let mut last_smoothed = 0.0;

    // Simulate streaming data
    for i in 0..total_points {
        let x = i as f64;
        let y = 2.0 * x + (x * 0.1).sin() * 5.0 + ((i % 7) as f64 - 3.0) * 0.5;

        if let Some(output) = processor.add_point(x, y)? {
            processed_count += 1;
            last_smoothed = output.smoothed;

            // Print progress every 200 points
            if processed_count % 200 == 0 {
                println!(
                    "  Processed: {:4} points | Latest smoothed value: {:.2}",
                    processed_count, last_smoothed
                );
            }
        }
    }

    println!("\nTotal points processed: {}", processed_count);
    println!("Final smoothed value: {:.2}", last_smoothed);
    println!("Memory usage: Constant (window size = 20 points)");

    /* Expected Output:
    Processing 1000 data points with minimal memory footprint...
      Processed:  200 points | Latest smoothed value: 398.45
      Processed:  400 points | Latest smoothed value: 798.23
      Processed:  600 points | Latest smoothed value: 1198.12
      Processed:  800 points | Latest smoothed value: 1597.89

    Total points processed: 999
    Final smoothed value: 1997.67
    Memory usage: Constant (window size = 20 points)
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 6: Sliding Window Behavior
/// Demonstrates how the sliding window processes sequential data
fn example_6_sliding_window_behavior() -> Result<(), LowessError> {
    println!("Example 6: Sliding Window Behavior");
    println!("{}", "-".repeat(80));
    println!("Demonstrating how the window slides through the data stream...\n");

    // Simple linear data
    let data: Vec<(f64, f64)> = vec![
        (1.0, 2.0),
        (2.0, 4.0),
        (3.0, 6.0),
        (4.0, 8.0),
        (5.0, 10.0),
        (6.0, 12.0),
        (7.0, 14.0),
        (8.0, 16.0),
    ];

    let mut processor = Lowess::<f64>::new()
        .fraction(0.6)
        .iterations(0) // No robustness for clarity
        .return_residuals()
        .adapter(Online)
        .window_capacity(4) // Small window to show sliding behavior
        .min_points(4) // Wait for window to be full
        .build()?;

    println!("Window capacity: 4 points");
    println!(
        "{:>6} {:>10} {:>12} {:>12} {:>20}",
        "Point", "X", "Y", "Smoothed", "Window Status"
    );
    println!("{}", "-".repeat(65));

    for (i, (x, y)) in data.iter().enumerate() {
        if let Some(output) = processor.add_point(*x, *y)? {
            println!(
                "{:6} {:10.1} {:12.1} {:12.2} {:>20}",
                i + 1,
                x,
                y,
                output.smoothed,
                "Window full (sliding)"
            );
        } else {
            println!(
                "{:6} {:10.1} {:12.1} {:>12} {:>20}",
                i + 1,
                x,
                y,
                "-",
                format!("Filling ({}/4)", i + 1)
            );
        }
    }

    println!("\nNote: Output starts after window is filled (4 points).");
    println!("After that, the window slides: oldest point removed, newest added.");

    /* Expected Output:
    Window capacity: 4 points
     Point          X            Y     Smoothed        Window Status
    -----------------------------------------------------------------
         1        1.0          2.0            -         Filling (1/4)
         2        2.0          4.0            -         Filling (2/4)
         3        3.0          6.0            -         Filling (3/4)
         4        4.0          8.0         8.00  Window full (sliding)
         5        5.0         10.0        10.00  Window full (sliding)
         6        6.0         12.0        12.00  Window full (sliding)
         7        7.0         14.0        14.00  Window full (sliding)
         8        8.0         16.0        16.00  Window full (sliding)

    Note: Output starts after window is filled (4 points).
    After that, the window slides: oldest point removed, newest added.
    */

    println!();
    Ok(())
}

#[cfg(feature = "std")]
/// Example 7: Benchmark (Sequential Online)
/// Measure execution time for a large dataset using the sequential Online adapter
fn example_7_benchmark() -> Result<(), LowessError> {
    println!("Example 7: Benchmark (Sequential Online)");
    println!("{}", "-".repeat(80));

    // Generate a larger synthetic dataset
    let n = 10_000;
    println!("Processing {} data points in online mode...", n);

    let start = std::time::Instant::now();

    let mut processor = Lowess::<f64>::new()
        .fraction(0.5)
        .iterations(3)
        .adapter(Online)
        .window_capacity(100) // 100-point sliding window
        .build()?;

    let mut processed_count = 0;

    // Process points one at a time
    for i in 0..n {
        let x = i as f64;
        let y = (x * 0.1).sin() + (x * 0.01).cos();

        if processor.add_point(x, y)?.is_some() {
            processed_count += 1;
        }
    }

    let duration = start.elapsed();

    println!("Processed {} points in {:?}", processed_count, duration);
    println!("Execution mode: Sequential Online");
    println!("Window capacity: 100");

    println!();
    Ok(())
}

Running the Examples

# Run fastLowess examples (parallel)
cargo run --example fast_batch_smoothing -p examples
cargo run --example fast_streaming_smoothing -p examples
cargo run --example fast_online_smoothing -p examples

# Run lowess examples (single-threaded)
cargo run --example batch_smoothing -p examples
cargo run --example streaming_smoothing -p examples
cargo run --example online_smoothing -p examples

Quick Start

use fastLowess::prelude::*;

fn main() -> Result<(), LowessError> {
    let x: Vec<f64> = (0..100).map(|i| i as f64 * 0.1).collect();
    let y: Vec<f64> = x.iter().map(|&xi| xi.sin() + 0.1).collect();

    // Build and fit the model
    let model = Lowess::new()
        .fraction(0.3)
        .iterations(3)
        .confidence_intervals(0.95)
        .return_diagnostics()
        .adapter(Batch)
        .build()?;

    let result = model.fit(&x, &y)?;

    println!("R²: {:.4}", result.diagnostics.unwrap().r_squared);
    Ok(())
}