temporal-compare 0.5.0

High-performance framework for benchmarking temporal prediction algorithms inspired by Time-R1
mod data;
mod metrics;
mod baseline;
mod mlp;
mod mlp_optimized;
mod mlp_ultra;
mod mlp_classifier;
mod ensemble;
mod attention;
mod reservoir;
mod fourier;
mod sparse;
mod mlp_avx512;
mod quantization;
mod mlp_quantized;
mod ruv_fann_adapter;
mod ruv_fann_impl;

use clap::{Parser, ValueEnum};
use data::{make_synthetic, to_class};
use metrics::{mse, acc};
use baseline::Baseline;
use mlp::Mlp;
use mlp_optimized::OptimizedMlp;
use mlp_ultra::UltraMlp;
use mlp_classifier::ClassifierMlp;
use ensemble::{EnsembleModel, BoostedEnsemble};
use reservoir::{ReservoirComputer, QuantumReservoir};
use fourier::{FourierFeatures, AdaptiveFourierFeatures};
use sparse::{SparseNetwork, LotteryTicketNetwork};
use mlp_avx512::DynamicAvx512Mlp;
use mlp_quantized::QuantizedMlpBackend;
#[cfg(feature = "ruv-fann")]
use ruv_fann_impl::RuvFannModel;
#[cfg(not(feature = "ruv-fann"))]
use ruv_fann_adapter::ruv_fann_backend::RuvFannModel;

#[derive(Copy, Clone, ValueEnum)]
enum Backend { Mlp, MlpOpt, MlpUltra, MlpAvx512, MlpQuantized, MlpClassifier, Ensemble, Boosted, Reservoir, QuantumReservoir, Fourier, AdaptiveFourier, Sparse, LotteryTicket, RuvFann, Baseline }

#[derive(Parser)]
struct Args {
    #[arg(long, default_value_t=32)]
    window: usize,
    #[arg(long, default_value_t=5000)]
    n: usize,
    #[arg(long, default_value_t=42)]
    seed: u64,
    #[arg(long, value_enum, default_value_t=Backend::Mlp)]
    backend: Backend,
    #[arg(long, default_value_t=64)]
    hidden: usize,
    #[arg(long, default_value_t=8)]
    epochs: usize,
    #[arg(long, default_value_t=0.01)]
    lr: f32,
    #[arg(long, default_value_t=false)]
    classify: bool
}

fn main() {
    let args = Args::parse();
    let ds = make_synthetic(args.window, args.n, args.seed);

    // prepare tensors
    let to_xy = |v: &Vec<data::Sample>| {
        let x: Vec<Vec<f32>> = v.iter().map(|s| s.x.clone()).collect();
        let y_reg: Vec<f32> = v.iter().map(|s| s.y).collect();
        let y_cls: Vec<usize> = v.iter().map(|s| to_class(s.y)).collect();
        (x, y_reg, y_cls)
    };
    let (xtr, ytr, ytrc) = to_xy(&ds.train);
    let (xva, yva, yvac) = to_xy(&ds.val);
    let (xte, yte, ytec) = to_xy(&ds.test);

    match args.backend {
        Backend::Baseline => {
            let yhat = Baseline::predict_reg(&ds.test, args.window);
            let yhatc = Baseline::predict_cls(&ds.test, args.window);
            println!("baseline_mse_test={:.6}", mse(&yte, &yhat));
            println!("baseline_acc_test={:.4}", acc(&ytec, &yhatc));
        }
        Backend::Mlp => {
            let mut model = if args.classify { Mlp::new(xtr[0].len(), args.hidden, 3) }
                            else { Mlp::new(xtr[0].len(), args.hidden, 1) };
            if args.classify {
                // train via regression to continuous y, then map to buckets
                model.train_regression(&xtr, &ytr, args.epochs, args.lr);
                let yhat = model.predict_cls3(&xte);
                println!("mlp_acc_test={:.4}", acc(&ytec, &yhat));
            } else {
                model.train_regression(&xtr, &ytr, args.epochs, args.lr);
                let yhat = model.predict_reg(&xte);
                println!("mlp_mse_val={:.6}", mse(&yva, &model.predict_reg(&xva)));
                println!("mlp_mse_test={:.6}", mse(&yte, &yhat));
            }
        }
        Backend::MlpOpt => {
            let mut model = if args.classify { OptimizedMlp::new(xtr[0].len(), args.hidden, 3) }
                            else { OptimizedMlp::new(xtr[0].len(), args.hidden, 1) };
            if args.classify {
                model.train_batch(&xtr, &ytr, args.epochs, args.lr, 32);
                let yhat = model.predict_cls3(&xte);
                println!("mlp_opt_acc_test={:.4}", acc(&ytec, &yhat));
            } else {
                model.train_batch(&xtr, &ytr, args.epochs, args.lr, 32);
                let yhat = model.predict_reg(&xte);
                println!("mlp_opt_mse_val={:.6}", mse(&yva, &model.predict_reg(&xva)));
                println!("mlp_opt_mse_test={:.6}", mse(&yte, &yhat));
            }
        }
        Backend::MlpUltra => {
            let mut model = if args.classify { UltraMlp::new(xtr[0].len(), args.hidden, 3) }
                            else { UltraMlp::new(xtr[0].len(), args.hidden, 1) };
            if args.classify {
                model.train_batch_parallel(&xtr, &ytr, args.epochs, args.lr, 32);
                let yhat = model.predict_cls3_parallel(&xte);
                println!("mlp_ultra_acc_test={:.4}", acc(&ytec, &yhat));
            } else {
                model.train_batch_parallel(&xtr, &ytr, args.epochs, args.lr, 32);
                let yhat = model.predict_parallel(&xte);
                println!("mlp_ultra_mse_val={:.6}", mse(&yva, &model.predict_parallel(&xva)));
                println!("mlp_ultra_mse_test={:.6}", mse(&yte, &yhat));
            }
        }
        Backend::MlpAvx512 => {
            let model = DynamicAvx512Mlp::new(xtr[0].len(), args.hidden, if args.classify { 3 } else { 1 });
            if args.classify {
                // Note: AVX512 model doesn't have train method yet, using predict only
                let yhat = model.predict_class(&xte);
                println!("mlp_avx512_acc_test={:.4} (inference only)", acc(&ytec, &yhat));
            } else {
                let yhat = model.predict(&xte);
                println!("mlp_avx512_mse_test={:.6} (inference only)", mse(&yte, &yhat));
            }
        }
        Backend::MlpQuantized => {
            let mut model = QuantizedMlpBackend::new(xtr[0].len(), args.hidden, if args.classify { 3 } else { 1 });

            // Train in FP32
            model.train(&xtr, &ytr, args.epochs, args.lr);

            // Benchmark INT8 vs FP32
            println!("\n=== INT8 Quantization Performance ===");
            model.benchmark_inference(&xte[..100.min(xte.len())], 100);

            if args.classify {
                let yhat = model.predict_class(&xte);
                println!("\nmlp_quantized_acc_test={:.4}", acc(&ytec, &yhat));
            } else {
                // Compare FP32 vs INT8 accuracy
                let yhat_fp32 = model.predict_fp32(&xte);
                let yhat_int8 = model.predict(&xte);

                println!("\nmlp_quantized_mse_test_fp32={:.6}", mse(&yte, &yhat_fp32));
                println!("mlp_quantized_mse_test_int8={:.6}", mse(&yte, &yhat_int8));

                // Calculate accuracy loss
                let mse_fp32 = mse(&yte, &yhat_fp32);
                let mse_int8 = mse(&yte, &yhat_int8);
                let accuracy_loss = ((mse_int8 - mse_fp32).abs() / mse_fp32) * 100.0;
                println!("Quantization accuracy loss: {:.2}%", accuracy_loss);
            }

            let (orig, quant, ratio) = model.get_compression_stats();
            println!("\nModel compression: {} -> {} bytes ({:.2}x)", orig, quant, ratio);
        }
        Backend::MlpClassifier => {
            let mut model = ClassifierMlp::new(xtr[0].len(), 3);
            if args.classify {
                model.train_classification(&xtr, &ytr, args.epochs, 32);
                let yhat = model.predict_cls3(&xte);
                let yhat_val = model.predict_cls3(&xva);
                println!("mlp_classifier_acc_val={:.4}", acc(&yvac, &yhat_val));
                println!("mlp_classifier_acc_test={:.4}", acc(&ytec, &yhat));
            } else {
                println!("MlpClassifier is for classification only, use --classify flag");
            }
        }
        Backend::Ensemble => {
            if args.classify {
                let mut ensemble = EnsembleModel::new();
                let input_dim = xtr[0].len();

                // Add diverse models
                ensemble.add_model_simple(input_dim, args.hidden, 3);
                ensemble.add_model_optimized(input_dim, args.hidden * 2, 3);
                ensemble.add_model_ultra(input_dim, args.hidden, 3);
                ensemble.add_model_classifier(input_dim, 3);

                ensemble.train_ensemble(&xtr, &ytr, args.epochs, args.lr, &xva, &yvac);
                let yhat = ensemble.predict_ensemble(&xte);
                println!("ensemble_acc_test={:.4}", acc(&ytec, &yhat));
            } else {
                println!("Ensemble is for classification only, use --classify flag");
            }
        }
        Backend::Boosted => {
            if args.classify {
                let mut boosted = BoostedEnsemble::new(xtr[0].len(), args.hidden);
                boosted.train_boosted(&xtr, &ytrc, 10, args.epochs / 2);
                let yhat = boosted.predict_boosted(&xte);
                println!("boosted_acc_test={:.4}", acc(&ytec, &yhat));
            } else {
                println!("Boosted is for classification only, use --classify flag");
            }
        }
        Backend::Reservoir => {
            let mut model = ReservoirComputer::new(xtr[0].len(), 100, 1);
            if args.classify {
                model.train_ridge(&xtr, &ytr, 0.001);
                let yhat = model.predict_class(&xte);
                println!("reservoir_acc_test={:.4}", acc(&ytec, &yhat));
            } else {
                model.train_ridge(&xtr, &ytr, 0.001);
                let yhat = model.predict(&xte);
                println!("reservoir_mse_test={:.6}", mse(&yte, &yhat));
            }
        }
        Backend::QuantumReservoir => {
            let mut model = QuantumReservoir::new(xtr[0].len(), 100, 1);
            if args.classify {
                model.train(&xtr, &ytr);
                let yhat = model.predict_quantum(&xte);
                println!("quantum_reservoir_acc_test={:.4}", acc(&ytec, &yhat));
            } else {
                model.train(&xtr, &ytr);
                // For regression, use classical prediction
                let yhat = model.classical_reservoir.predict(&xte);
                println!("quantum_reservoir_mse_test={:.6}", mse(&yte, &yhat));
            }
        }
        Backend::Fourier => {
            let mut model = FourierFeatures::new(xtr[0].len(), 500, 1.0);
            if args.classify {
                model.train(&xtr, &ytr, 0.001);
                let yhat = model.predict_class(&xte);
                println!("fourier_acc_test={:.4}", acc(&ytec, &yhat));
            } else {
                model.train(&xtr, &ytr, 0.001);
                let yhat = model.predict(&xte);
                println!("fourier_mse_test={:.6}", mse(&yte, &yhat));
            }
        }
        Backend::AdaptiveFourier => {
            let mut model = AdaptiveFourierFeatures::new(xtr[0].len(), 500, 1.0);
            if args.classify {
                model.train_adaptive(&xtr, &ytr, 50);
                let yhat = model.predict_class(&xte);
                println!("adaptive_fourier_acc_test={:.4}", acc(&ytec, &yhat));
            } else {
                model.train_adaptive(&xtr, &ytr, 50);
                let yhat = model.predict(&xte);
                println!("adaptive_fourier_mse_test={:.6}", mse(&yte, &yhat));
            }
        }
        Backend::Sparse => {
            let mut model = SparseNetwork::new(xtr[0].len(), args.hidden, if args.classify { 3 } else { 1 }, 0.1);
            if args.classify {
                model.train(&xtr, &ytr, args.epochs * 10, 0.01);
                let yhat = model.predict_class(&xte);
                let (active, pruned, sparsity) = model.get_sparsity_stats();
                println!("sparse_acc_test={:.4} (active={}, pruned={}, sparsity={:.1}%)",
                         acc(&ytec, &yhat), active, pruned, sparsity * 100.0);
            } else {
                model.train(&xtr, &ytr, args.epochs * 10, 0.01);
                let yhat = model.predict(&xte);
                let (active, pruned, sparsity) = model.get_sparsity_stats();
                println!("sparse_mse_test={:.6} (active={}, pruned={}, sparsity={:.1}%)",
                         mse(&yte, &yhat), active, pruned, sparsity * 100.0);
            }
        }
        Backend::LotteryTicket => {
            let mut model = LotteryTicketNetwork::new(xtr[0].len(), args.hidden, if args.classify { 3 } else { 1 });
            if args.classify {
                model.find_winning_ticket(&xtr, &ytr, 0.2, 5);
                let yhat = model.predict_class(&xte);
                println!("lottery_ticket_acc_test={:.4}", acc(&ytec, &yhat));
            } else {
                model.find_winning_ticket(&xtr, &ytr, 0.2, 5);
                let yhat = model.predict(&xte);
                println!("lottery_ticket_mse_test={:.6}", mse(&yte, &yhat));
            }
        }
        Backend::RuvFann => {
            let mut model = if args.classify { RuvFannModel::new(xtr[0].len(), args.hidden, 3) }
                            else { RuvFannModel::new(xtr[0].len(), args.hidden, 1) };
            model.train_regression(&xtr, &ytr, args.epochs, args.lr);
            if args.classify {
                let yhat = model.predict_cls3(&xte);
                println!("ruv_fann_acc_test={:.4}", acc(&ytec, &yhat));
            } else {
                let yhat = model.predict_reg(&xte);
                println!("ruv_fann_mse_test={:.6}", mse(&yte, &yhat));
            }
        }
    }
}