mod compressor; mod gpu; mod parquet_handler; mod shifters; use crate::compressor::Compressor; use crate::gpu::GpuContext; use crate::shifters::{get_shifter, intrinsic_load}; use clap::Parser; use indicatif::{ProgressBar, ProgressStyle}; use rayon::prelude::*; use serde_json::json; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use sysinfo::System; use tokio::sync::Semaphore; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { #[arg( short, long, default_value = "3-Mathematical-Models/equations_parquet_tagged" )] input_dir: String, #[arg( short, long, default_value = "3-Mathematical-Models/equations_compressed" )] output_dir: String, #[arg(short, long, default_value_t = 8192)] memory_limit_mb: u64, #[arg(short, long, default_value_t = 4)] threads: usize, #[arg(long, default_value_t = false)] use_gpu: bool, } struct Stats { total_raw_bytes: usize, total_compressed_bytes: usize, files: Vec, } fn main() -> anyhow::Result<()> { let args = Args::parse(); let base_path = Path::new("/home/allaun/Documents/Research Stack"); let input_path = base_path.join(&args.input_dir); let output_path = base_path.join(&args.output_dir); std::fs::create_dir_all(&output_path)?; let mut sys = System::new_all(); sys.refresh_memory(); let available_memory_mb = sys.available_memory() / 1024 / 1024; println!("Available Memory: {} MB", available_memory_mb); let gpu_context = if args.use_gpu { println!("Initializing GPU..."); Some(Arc::new(pollster::block_on(GpuContext::new())?)) } else { None }; let pattern = format!("{}/*_equations_*.parquet", input_path.display()); let files: Vec = glob::glob(&pattern)? .filter_map(Result::ok) .filter(|p| !p.to_string_lossy().contains("tagging_summary")) .collect(); println!("Found {} Parquet files", files.len()); let mp = indicatif::MultiProgress::new(); let pb = mp.add(ProgressBar::new(files.len() as u64)); pb.set_style(ProgressStyle::default_bar() .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta}) {msg}")? .progress_chars("#>-")); pb.enable_steady_tick(std::time::Duration::from_millis(100)); let stats = Arc::new(Mutex::new(Stats { total_raw_bytes: 0, total_compressed_bytes: 0, files: Vec::new(), })); let semaphore = Arc::new(Semaphore::new(args.threads)); let rt = tokio::runtime::Builder::new_multi_thread() .worker_threads(args.threads) .enable_all() .build()?; files.par_iter().for_each(|file_path| { let _permit = rt.block_on(semaphore.acquire()).expect("Semaphore closed"); let fname = file_path.file_stem().unwrap().to_str().unwrap(); pb.set_message(format!("Current: {}", fname)); let sub_pb = mp.add(indicatif::ProgressBar::new_spinner()); sub_pb.set_style( indicatif::ProgressStyle::default_spinner() .template("{spinner:.yellow} {msg}") .unwrap(), ); sub_pb.set_message(format!("Starting {}", fname)); sub_pb.enable_steady_tick(std::time::Duration::from_millis(100)); match process_file_with_progress( file_path, &output_path, &stats, gpu_context.clone(), &sub_pb, ) { Ok(_) => { pb.inc(1); sub_pb.finish_and_clear(); } Err(e) => { eprintln!("Error processing {}: {}", fname, e); sub_pb.finish_with_message(format!("Failed: {}", fname)); } } }); pb.finish_with_message("Compression complete"); // Finalize stats and manifest let stats = stats.lock().unwrap(); let manifest = json!({ "version": 3, "timestamp": chrono::Local::now().to_rfc3339(), "total_size_raw_mb": stats.total_raw_bytes as f64 / 1024.0 / 1024.0, "total_size_compressed_mb": stats.total_compressed_bytes as f64 / 1024.0 / 1024.0, "compression_ratio": stats.total_raw_bytes as f64 / stats.total_compressed_bytes.max(1) as f64, "files": stats.files, }); let manifest_path = output_path.join("compression_manifest.json"); std::fs::write(&manifest_path, serde_json::to_string_pretty(&manifest)?)?; let mut index_files = json!({}); for f in &stats.files { let name = f["name"].as_str().unwrap(); index_files[name] = json!({ "compressed": format!("{}.compressed", name), "raw_mb": f["raw_bytes"].as_u64().unwrap_or(0) as f64 / 1024.0 / 1024.0, "compressed_mb": f["compressed_bytes"].as_u64().unwrap_or(0) as f64 / 1024.0 / 1024.0, "ratio": f["ratio"].as_f64().unwrap_or(1.0), "chain": f["chain"].as_str().unwrap_or("unknown"), }); } let index = json!({ "manifest": "compression_manifest.json", "files": index_files, }); let index_path = output_path.join("index.json"); std::fs::write(&index_path, serde_json::to_string_pretty(&index)?)?; let parent_index = input_path .parent() .unwrap() .join("equations_NUVMAP_index.json"); std::fs::copy(&index_path, parent_index)?; Ok(()) } fn process_file_with_progress( file_path: &Path, output_dir: &Path, stats: &Arc>, gpu_context: Option>, pb: &ProgressBar, ) -> anyhow::Result<()> { pb.set_message("Serializing Parquet..."); let raw_data = parquet_handler::serialize_parquet_to_bytes(file_path)?; let raw_len = raw_data.len(); pb.set_message("GPU Preprocessing..."); let data_to_compress = if let Some(gpu) = gpu_context { gpu.run_xor_transform(&raw_data, 0x1F)? } else { raw_data }; let chains = vec![ ("best_general", vec!["bwt", "mtf", "lzw"]), ("fast_text", vec!["bwt", "run_length"]), ]; let mut best_data = data_to_compress.clone(); let mut best_ratio = 1.0; let mut best_chain_name = "none"; for (name, chain) in chains { pb.set_message(format!("Trying chain: {}...", name)); let shifters: Vec> = chain.iter().map(|s| get_shifter(s).unwrap()).collect(); if let Ok(compressed) = Compressor::compress(data_to_compress.clone(), shifters) { let ratio = raw_len as f64 / compressed.len().max(1) as f64; if ratio > best_ratio { best_ratio = ratio; best_data = compressed; best_chain_name = name; } } } pb.set_message("Finalizing file..."); let fname = file_path.file_stem().unwrap().to_str().unwrap(); let output_path = output_dir.join(format!("{}.compressed", fname)); std::fs::write(output_path, &best_data)?; let mut s = stats.lock().unwrap(); s.total_raw_bytes += raw_len; s.total_compressed_bytes += best_data.len(); s.files.push(json!({ "name": fname, "raw_bytes": raw_len, "compressed_bytes": best_data.len(), "raw_intrinsic_load": intrinsic_load(&data_to_compress), "ratio": best_ratio, "chain": best_chain_name, })); Ok(()) }