| Crates.io | rustygraph |
| lib.rs | rustygraph |
| version | 0.4.2 |
| created_at | 2025-11-21 14:25:51.429792+00 |
| updated_at | 2025-11-21 14:25:51.429792+00 |
| description | A high-performance library for visibility graph computation from time series data |
| homepage | |
| repository | https://github.com/paulmagos/rustygraph |
| max_upload_size | |
| id | 1943639 |
| size | 1,950,062 |
A blazingly fast, cross-platform visibility graph library for time series analysis.
RustyGraph is a high-performance Rust library for computing visibility graphs from time series data, featuring automatic multi-core parallelization, SIMD acceleration on x86_64 (AVX2) and ARM64 (NEON).
README.md - You are here! Main overview and quick start
VISUAL_GUIDE.md - Visual diagrams and quick reference
f32 and f64parallel): Multi-threaded feature computation with rayon (2-4x speedup)simd): AVX2 (x86_64) and NEON (ARM64) optimizations (5-8x speedup)metal): Apple Silicon GPU support via Metal (best for graphs > 20k nodes)csv-import): Load time series from CSV filesEnable with:
[dependencies]
rustygraph = { version = "0.4.0", features = ["parallel", "csv-import", "advanced-features", "npy-export", "parquet-export"] }
Add this to your Cargo.toml:
[dependencies]
rustygraph = "0.4.0"
# Or with optional features:
# rustygraph = { version = "0.4.0", features = ["parallel", "csv-import"] }
use rustygraph::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a time series
let series = TimeSeries::from_raw(vec![1.0, 3.0, 2.0, 4.0, 1.0]);
// Build a natural visibility graph
let graph = VisibilityGraph::from_series(&series)
.natural_visibility()?;
// Access the results
println!("Number of edges: {}", graph.edges().len());
println!("Degree sequence: {:?}", graph.degree_sequence());
Ok(())
}
use rustygraph::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create time series with missing data
let series = TimeSeries::new(
vec![0.0, 1.0, 2.0, 3.0, 4.0],
vec![Some(1.0), None, Some(3.0), Some(2.0), Some(4.0)]
)?;
// Handle missing data
let cleaned = series.handle_missing(
MissingDataStrategy::LinearInterpolation
.with_fallback(MissingDataStrategy::ForwardFill)
)?;
// Create graph with node features
let graph = VisibilityGraph::from_series(&cleaned)
.with_features(
FeatureSet::new()
.add_builtin(BuiltinFeature::DeltaForward)
.add_builtin(BuiltinFeature::LocalSlope)
.add_function("squared", |series, idx| {
series[idx].map(|v| v * v)
})
)
.horizontal_visibility()?;
// Inspect node features
for i in 0..graph.node_count {
if let Some(features) = graph.node_features(i) {
println!("Node {}: {:?}", i, features);
}
}
Ok(())
}
use rustygraph::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let series = TimeSeries::from_raw(vec![1.0, 3.0, 2.0, 4.0, 3.0])?;
let graph = VisibilityGraph::from_series(&series)
.with_direction(GraphDirection::Directed) // Directed graph
.natural_visibility()?;
// Export to different formats
let json = graph.to_json(ExportOptions::default());
std::fs::write("graph.json", json)?;
let csv = graph.to_edge_list_csv(true);
std::fs::write("edges.csv", csv)?;
let dot = graph.to_dot(); // GraphViz visualization
std::fs::write("graph.dot", dot)?;
// Compute graph metrics
println!("Clustering: {:.4}", graph.average_clustering_coefficient());
println!("Diameter: {}", graph.diameter());
println!("Density: {:.4}", graph.density());
// Get comprehensive statistics
let stats = graph.compute_statistics();
println!("{}", stats);
Ok(())
}
use rustygraph::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Import from CSV (requires 'csv-import' feature)
let series1 = TimeSeries::<f64>::from_csv_string(
"time,value\n0,1.0\n1,2.0\n2,3.0",
CsvImportOptions::default()
)?;
let series2 = TimeSeries::from_raw(vec![2.0, 1.0, 3.0])?;
let series3 = TimeSeries::from_raw(vec![1.0, 3.0, 2.0])?;
// Batch process multiple series
let results = BatchProcessor::new()
.add_series(&series1, "Stock A")
.add_series(&series2, "Stock B")
.add_series(&series3, "Stock C")
.process_natural()?;
## Architecture
The library is organized into several modules:
- **`time_series`**: Time series data structures and preprocessing
- **`visibility_graph`**: Visibility graph construction and representation
- **`features`**: Node feature computation framework
- **`features::missing_data`**: Missing data handling strategies
- **`algorithms`**: Core visibility graph algorithms
## Built-in Features
The library includes several pre-defined node features:
### Temporal Derivatives
- `DeltaForward`: y[i+1] - y[i]
- `DeltaBackward`: y[i] - y[i-1]
- `DeltaSymmetric`: (y[i+1] - y[i-1]) / 2
- `LocalSlope`: (y[i+1] - y[i-1]) / (t[i+1] - t[i-1])
- `Acceleration`: Second derivative approximation
### Local Statistics
- `LocalMean`: Average over local window
- `LocalVariance`: Variance over local window
- `ZScore`: (y[i] - mean) / std
### Extrema Detection
- `IsLocalMax`: Detects peaks
- `IsLocalMin`: Detects valleys
## Missing Data Strategies
- **LinearInterpolation**: Average of neighboring valid values
- **ForwardFill**: Use last valid value
- **BackwardFill**: Use next valid value
- **NearestNeighbor**: Use closest valid value
- **MeanImputation**: Local window mean
- **MedianImputation**: Local window median
- **ZeroFill**: Replace with zero
- **Drop**: Skip missing values
Strategies can be chained with fallbacks:
```rust
let strategy = MissingDataStrategy::LinearInterpolation
.with_fallback(MissingDataStrategy::ForwardFill)
.with_fallback(MissingDataStrategy::ZeroFill);
let features = FeatureSet::new()
.add_function("log", |series, idx| {
series[idx].map(|v| v.ln())
});
use rustygraph::features::{Feature, MissingDataHandler};
struct RangeFeature {
window: usize,
}
impl Feature<f64> for RangeFeature {
fn compute(
&self,
series: &[Option<f64>],
index: usize,
missing_handler: &dyn MissingDataHandler<f64>,
) -> Option<f64> {
let start = index.saturating_sub(self.window / 2);
let end = (index + self.window / 2).min(series.len());
let valid: Vec<f64> = series[start..end]
.iter()
.filter_map(|&v| v)
.collect();
if valid.is_empty() {
return None;
}
let min = valid.iter().fold(f64::INFINITY, |a, &b| a.min(b));
let max = valid.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
Some(max - min)
}
fn name(&self) -> &str {
"range"
}
fn requires_neighbors(&self) -> bool {
true
}
fn window_size(&self) -> Option<usize> {
Some(self.window)
}
}
f32 and f64The library is feature-complete and production-ready! All core algorithms, features, missing data handling, and advanced optional features are fully implemented and tested.
Version: v0.4.0
Test Status: 30/30 passing (100%)
Code Quality:
Export Formats: 9/9
Optional Features: 26/26
Integrations: 4/4 (petgraph, ndarray, Python, Polars)
Python Bindings: 85% coverage
Examples: 13 complete
Quality: maintainable, Python-friendly
Roadmap: ✅
| Category | Status | Count |
|---|---|---|
| Core Algorithms | ✅ Complete | 2/2 |
| Built-in Features | ✅ Complete | 10/10 |
| Missing Data Strategies | ✅ Complete | 8/8 |
| Graph Metrics | ✅ Complete | 9/9 |
| Export Formats | ✅ Complete | 9/9 |
| Optional Features | ✅ Complete | 26/26 |
| Integrations | ✅ Complete | 4/4 (petgraph, ndarray, Python, Polars) |
| Examples | ✅ Complete | 13/13 ⬆️ |
| Tests | ✅ Passing | 30/30 ⬆️ |
| Benchmarks | ✅ Complete | 6 groups |
| Example Datasets | ✅ Complete | 8 |
| Advanced Features | ✅ Complete | 7 |
| Performance Optimizations | ✅ Complete | 3 |
| Code Quality | ✅ Refactored | 75% complexity reduction |
| Python API Coverage | ✅ Enhanced | 85% ⬆️ (from 31%) |
| Documentation | ✅ Complete | 100% (7 technical docs) |
The library now includes advanced optional features beyond the core functionality:
rayon - 2-4x speedup (feature: parallel)
csv-import)basic_usage.rs - Core functionalityweighted_graphs.rs - Custom edge weightswith_features.rs - Feature computationadvanced_features.rs - Export, metrics, directed graphsperformance_io.rs - Statistics, CSV import, paralleladvanced_analytics.rs - Betweenness, GraphViz, batch processingcommunity_detection.rs - Community detection, GraphML exportbenchmarking_validation.rs - Benchmarking, validation, datasetsadvanced_optimization.rs - Lazy evaluation, wavelet, FFT, complexitysimd_and_motifs.rs - SIMD acceleration, motif detectionexport_formats.rs - NPY, Parquet, HDF5 exports for data scienceintegrations.rs - petgraph, ndarray, Python bindings integrationThe library provides seamless integration with major Rust and Python ecosystems:
// Use petgraph algorithms
let graph = VisibilityGraph::from_series(&series).natural_visibility()?;
let pg = graph.to_petgraph();
let distances = graph.dijkstra_shortest_paths(0);
let mst = graph.minimum_spanning_tree();
// Matrix operations with ndarray
let adj = graph.to_ndarray_adjacency();
let lap = graph.to_ndarray_laplacian();
let eigenvalue = graph.dominant_eigenvalue(100);
let stationary = graph.random_walk_stationary(100);
/docs/PYTHON_BINDINGS_ENHANCED.md)✨ Enhanced November 20, 2025 - Massive Expansion!
What's Exposed:
Still Not Exposed (15%):
# Install with maturin
# pip install maturin
# maturin develop --features python-bindings
import rustygraph as rg
import numpy as np
# 1. Handle missing data (NEW!)
series = rg.TimeSeries.with_missing(
timestamps=[0.0, 1.0, 2.0, 3.0, 4.0],
values=[1.0, None, 3.0, None, 2.0]
)
strategy = rg.MissingDataStrategy.linear_interpolation()
cleaned = series.handle_missing(strategy)
# 2. Import from CSV (NEW!)
series = rg.TimeSeries.from_csv_file("data.csv", "time", "value")
# 3. Build visibility graph with features
features = rg.FeatureSet()
features.add_builtin(rg.BuiltinFeature("DeltaForward"))
features.add_builtin(rg.BuiltinFeature("LocalSlope"))
graph = series.natural_visibility_with_features(features)
# 4. Advanced metrics (NEW!)
print(f"Nodes: {graph.node_count()}")
print(f"Connected: {graph.is_connected()}")
print(f"Components: {graph.count_components()}")
print(f"Avg Path Length: {graph.average_path_length():.2f}")
print(f"Assortativity: {graph.assortativity():.4f}")
# 5. Comprehensive statistics (NEW!)
stats = graph.compute_statistics()
print(stats) # Pretty formatted table
# 6. Motif detection (NEW!)
motifs = graph.detect_motifs()
print(f"Triangles: {motifs.get('triangle')}")
# 7. Export to multiple formats (NEW!)
graph.save_edge_list_csv("edges.csv", include_weights=True)
graph.save_dot("graph.dot") # GraphViz
graph.save_graphml("graph.graphml") # Gephi, Cytoscape
# 8. NumPy integration
adj = graph.adjacency_matrix() # Zero-copy NumPy array
features_array = graph.get_all_features() # (nodes x features)
# 9. Centrality for all nodes (NEW!)
betweenness = graph.betweenness_centrality_all()
degree_cent = graph.degree_centrality()
💡 85% API coverage! Most Rust features now available in Python.
See/docs/PYTHON_BINDINGS_ENHANCED.mdfor complete feature list.
use rustygraph::integrations::polars::*;
use polars::prelude::*;
// Create DataFrame with time series data
let df = df! {
"time" => &[0.0, 1.0, 2.0, 3.0, 4.0],
"value" => &[1.0, 3.0, 2.0, 4.0, 1.0],
"sensor_id" => &["A", "A", "A", "A", "A"],
}?;
// Convert to TimeSeries
let series = TimeSeries::from_polars_df(&df, "time", "value")?;
// Build graph
let graph = VisibilityGraph::from_series(&series)
.natural_visibility()?;
// Export graph properties to DataFrame
let graph_df = graph.to_polars_df()?;
println!("{}", graph_df);
// Batch process multiple sensors
let batch_results = BatchProcessor::from_polars_df(&df, "time", "value", "sensor_id")?
.process_natural()?;
// Export to Polars for further analysis
let results_df = batch_results.to_polars_df()?;
These features could be added in future versions but are not required for production use:
GPU_FAIR_COMPARISON_RESULTS.md for detailed analysispetgraph integration for advanced algorithms (IMPLEMENTED - petgraph-integration feature)ndarray support for matrix operations (IMPLEMENTED - ndarray-support feature)polars integration for DataFrames (IMPLEMENTED - polars-integration feature)proptest (IMPLEMENTED - 11 property tests)criterion (IMPLEMENTED - 6 benchmark groups)The library is ready for production use in:
Pre-built wheels are automatically published for Linux, macOS, and Windows:
pip install rustygraph
Supports Python 3.9+ on:
# Install build tool
pip install maturin
# Build and install (development mode)
cd rustygraph
maturin develop --release --features python-bindings
# Verify installation
python -c "import rustygraph as rg; print(rg.__version__)"
Note for Maintainers: Python packages are automatically built and published to PyPI via GitHub Actions. See
.github/workflows/README.mdfor details.
import rustygraph as rg
# Load data with missing values
series = rg.TimeSeries.with_missing(
timestamps=[0.0, 1.0, 2.0, 3.0],
values=[1.0, None, 3.0, 2.0]
)
# Handle missing data
strategy = rg.MissingDataStrategy.linear_interpolation()
cleaned = series.handle_missing(strategy)
# Build graph and analyze
graph = cleaned.natural_visibility()
stats = graph.compute_statistics()
print(stats)
# Export
graph.save_dot("graph.dot")
See /docs/PYTHON_BUILD_GUIDE.md for complete build instructions and python/examples/comprehensive_example.py for full examples.
Generate and view the full documentation:
cargo doc --open
The documentation provides complete API specifications with working examples for all features.
Lacasa, L., Luque, B., Ballesteros, F., Luque, J., & Nuno, J. C. (2008). "From time series to complex networks: The visibility graph." Proceedings of the National Academy of Sciences, 105(13), 4972-4975.
Luque, B., Lacasa, L., Ballesteros, F., & Luque, J. (2009). "Horizontal visibility graphs: Exact results for random time series." Physical Review E, 80(4), 046103.
Contributions are welcome! Please feel free to submit a Pull Request.