Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
293
vendor/ruvector/examples/mincut/neural_optimizer/README.md
vendored
Normal file
293
vendor/ruvector/examples/mincut/neural_optimizer/README.md
vendored
Normal file
@@ -0,0 +1,293 @@
|
||||
# Neural Temporal Graph Optimization
|
||||
|
||||
This example demonstrates how to use simple neural networks to learn and predict optimal graph configurations over time for minimum cut problems.
|
||||
|
||||
## 🎯 What This Example Does
|
||||
|
||||
The neural optimizer learns from graph evolution history to predict which modifications will lead to better minimum cut values. It uses reinforcement learning principles to guide graph transformations.
|
||||
|
||||
## 🧠 Core Concepts
|
||||
|
||||
### 1. **Temporal Graph Optimization**
|
||||
|
||||
Graphs often evolve over time (social networks, infrastructure, etc.). The challenge is predicting how changes will affect properties like minimum cut:
|
||||
|
||||
```
|
||||
Time t0: Graph A → mincut = 5.0
|
||||
Time t1: Add edge (3,7) → mincut = 3.2 ✓ Better!
|
||||
Time t2: Remove edge (1,4) → mincut = 8.1 ✗ Worse!
|
||||
```
|
||||
|
||||
**Goal**: Learn which actions improve the mincut.
|
||||
|
||||
### 2. **Why Neural Networks?**
|
||||
|
||||
Graph optimization is **NP-hard** because:
|
||||
- Combinatorially many possible modifications
|
||||
- Non-linear relationship between structure and mincut
|
||||
- Need to predict long-term effects
|
||||
|
||||
Neural networks can:
|
||||
- **Learn patterns** from historical data
|
||||
- **Generalize** to unseen graph configurations
|
||||
- **Make fast predictions** without solving mincut repeatedly
|
||||
|
||||
### 3. **Reinforcement Learning Basics**
|
||||
|
||||
Our optimizer uses a simple RL approach:
|
||||
|
||||
```
|
||||
State (S): Current graph features
|
||||
├─ Node count
|
||||
├─ Edge count
|
||||
├─ Density
|
||||
└─ Average degree
|
||||
|
||||
Action (A): Graph modification
|
||||
├─ Add random edge
|
||||
├─ Remove random edge
|
||||
└─ Do nothing
|
||||
|
||||
Reward (R): Change in mincut quality
|
||||
Policy (π): Neural network that chooses actions
|
||||
Value (V): Neural network that predicts future mincut
|
||||
```
|
||||
|
||||
**RL Loop**:
|
||||
```
|
||||
1. Observe current state S
|
||||
2. Policy π predicts best action A
|
||||
3. Apply action A to graph
|
||||
4. Observe new mincut value R
|
||||
5. Learn: Update π and V based on R
|
||||
6. Repeat
|
||||
```
|
||||
|
||||
### 4. **Simple Neural Network**
|
||||
|
||||
We implement a basic feedforward network **without external dependencies**:
|
||||
|
||||
```rust
|
||||
Input Layer (4 features)
|
||||
↓
|
||||
Hidden Layer (8 neurons, ReLU activation)
|
||||
↓
|
||||
Output Layer (3 actions for policy, 1 value for predictor)
|
||||
```
|
||||
|
||||
**Forward Pass**:
|
||||
```
|
||||
hidden = ReLU(input × W1 + b1)
|
||||
output = hidden × W2 + b2
|
||||
```
|
||||
|
||||
**Training**: Evolutionary strategy (mutation-based)
|
||||
- Create population of networks with small random changes
|
||||
- Evaluate fitness on training data
|
||||
- Select best performer
|
||||
- Repeat
|
||||
|
||||
## 🔍 How It Works
|
||||
|
||||
### Phase 1: Training Data Generation
|
||||
|
||||
```rust
|
||||
// Generate random graphs and record their mincuts
|
||||
for _ in 0..20 {
|
||||
let graph = generate_random_graph(10, 0.3);
|
||||
let mincut = calculate_mincut(&graph);
|
||||
optimizer.record_observation(&graph, mincut);
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 2: Neural Network Training
|
||||
|
||||
```rust
|
||||
// Train using evolutionary strategy
|
||||
optimizer.train(generations: 50, population_size: 20);
|
||||
|
||||
// Each generation:
|
||||
// 1. Create population by mutating current network
|
||||
// 2. Evaluate fitness (prediction accuracy)
|
||||
// 3. Select best network
|
||||
```
|
||||
|
||||
### Phase 3: Optimization Loop
|
||||
|
||||
```rust
|
||||
// Neural-guided optimization
|
||||
for step in 0..30 {
|
||||
// 1. Extract features from current graph
|
||||
let features = extract_features(&graph);
|
||||
|
||||
// 2. Policy network predicts best action
|
||||
let action = policy_network.forward(&features);
|
||||
|
||||
// 3. Apply action (add/remove edge)
|
||||
apply_action(&mut graph, action);
|
||||
|
||||
// 4. Calculate new mincut
|
||||
let mincut = calculate_mincut(&graph);
|
||||
|
||||
// 5. Record for continuous learning
|
||||
optimizer.record_observation(&graph, mincut);
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Comparison
|
||||
|
||||
```rust
|
||||
// Compare neural-guided vs random actions
|
||||
Neural-Guided: Average mincut = 4.2
|
||||
Random Baseline: Average mincut = 5.8
|
||||
Improvement: 27.6%
|
||||
```
|
||||
|
||||
## 🚀 Running the Example
|
||||
|
||||
```bash
|
||||
# From the ruvector root directory
|
||||
cargo run --example mincut_neural_optimizer --release -p ruvector-mincut
|
||||
|
||||
# Expected output:
|
||||
# ╔════════════════════════════════════════════════════════════╗
|
||||
# ║ Neural Temporal Graph Optimization Example ║
|
||||
# ║ Learning to Predict Optimal Graph Configurations ║
|
||||
# ╚════════════════════════════════════════════════════════════╝
|
||||
#
|
||||
# 📊 Initializing Neural Graph Optimizer
|
||||
# 🔬 Generating Training Data
|
||||
# 🧠 Training Neural Networks
|
||||
# ⚖️ Comparing Optimization Strategies
|
||||
# 📈 Results Comparison
|
||||
# 🔮 Prediction vs Actual
|
||||
```
|
||||
|
||||
**Note**: This example uses a simplified mincut approximation for demonstration purposes. In production, you would use the full `DynamicMinCut` algorithm from the `ruvector-mincut` crate. The approximation is based on graph statistics (minimum degree × average edge weight) to keep the example focused on neural optimization concepts without computational overhead.
|
||||
|
||||
## 📊 Key Components
|
||||
|
||||
### 1. **NeuralNetwork**
|
||||
|
||||
Simple feedforward network with:
|
||||
- Linear transformations (matrix multiplication)
|
||||
- ReLU activation
|
||||
- Gradient-free optimization (evolutionary)
|
||||
|
||||
```rust
|
||||
struct NeuralNetwork {
|
||||
weights_hidden: Vec<Vec<f64>>,
|
||||
bias_hidden: Vec<f64>,
|
||||
weights_output: Vec<Vec<f64>>,
|
||||
bias_output: Vec<f64>,
|
||||
}
|
||||
```
|
||||
|
||||
### 2. **NeuralGraphOptimizer**
|
||||
|
||||
Main optimizer combining:
|
||||
- **Policy Network**: Decides which action to take
|
||||
- **Value Network**: Predicts future mincut value
|
||||
- **Training History**: Stores (state, mincut) pairs
|
||||
|
||||
```rust
|
||||
struct NeuralGraphOptimizer {
|
||||
policy_network: NeuralNetwork,
|
||||
value_network: NeuralNetwork,
|
||||
history: Vec<(Vec<f64>, f64)>,
|
||||
}
|
||||
```
|
||||
|
||||
### 3. **Feature Extraction**
|
||||
|
||||
Converts graph to feature vector:
|
||||
|
||||
```rust
|
||||
fn extract_features(graph: &Graph) -> Vec<f64> {
|
||||
vec![
|
||||
normalized_node_count,
|
||||
normalized_edge_count,
|
||||
graph_density,
|
||||
normalized_avg_degree,
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 🎓 Educational Insights
|
||||
|
||||
### Why This Matters
|
||||
|
||||
1. **Predictive Power**: Learn from past to predict future
|
||||
2. **Computational Efficiency**: Fast predictions vs repeated mincut calculations
|
||||
3. **Adaptive Strategy**: Improves with more data
|
||||
4. **Transferable Knowledge**: Patterns learned generalize
|
||||
|
||||
### When to Use Neural Optimization
|
||||
|
||||
✅ **Good for**:
|
||||
- Dynamic graphs that evolve over time
|
||||
- Repeated optimization on similar graphs
|
||||
- Need for fast approximate solutions
|
||||
- Learning from historical patterns
|
||||
|
||||
❌ **Not ideal for**:
|
||||
- One-time optimization (use exact algorithms)
|
||||
- Very small graphs (overhead not worth it)
|
||||
- Guaranteed optimal solutions required
|
||||
|
||||
### Limitations of This Simple Approach
|
||||
|
||||
1. **Linear Model**: Real problems may need deeper networks
|
||||
2. **Gradient-Free Training**: Slower than gradient descent
|
||||
3. **Feature Engineering**: Hand-crafted features may miss patterns
|
||||
4. **Small Training Set**: More data = better predictions
|
||||
|
||||
### Extensions
|
||||
|
||||
**Easy Improvements**:
|
||||
- Add more graph features (clustering coefficient, centrality)
|
||||
- Larger networks (more layers, neurons)
|
||||
- Better training (gradient descent with backpropagation)
|
||||
- Experience replay (store and reuse good/bad examples)
|
||||
|
||||
**Advanced Extensions**:
|
||||
- Graph Neural Networks (GNNs) for structure learning
|
||||
- Deep Q-Learning with temporal difference
|
||||
- Multi-agent optimization (parallel learners)
|
||||
- Transfer learning across graph families
|
||||
|
||||
## 🔗 Related Examples
|
||||
|
||||
- `basic_mincut.rs` - Simple minimum cut calculation
|
||||
- `comparative_algorithms.rs` - Compare different algorithms
|
||||
- `real_world_networks.rs` - Apply to real network data
|
||||
|
||||
## 📚 Further Reading
|
||||
|
||||
### Reinforcement Learning
|
||||
- **Sutton & Barto**: "Reinforcement Learning: An Introduction"
|
||||
- **Policy Gradient Methods**: Learn action selection directly
|
||||
- **Value Function Approximation**: Neural networks for RL
|
||||
|
||||
### Graph Optimization
|
||||
- **Combinatorial Optimization**: NP-hard problems
|
||||
- **Graph Neural Networks**: Deep learning on graphs
|
||||
- **Temporal Networks**: Time-evolving graph analysis
|
||||
|
||||
### Minimum Cut Applications
|
||||
- Network reliability
|
||||
- Image segmentation
|
||||
- Community detection
|
||||
- Circuit design
|
||||
|
||||
## 💡 Key Takeaways
|
||||
|
||||
1. **Neural networks learn patterns** that guide graph optimization
|
||||
2. **Simple linear models** can be effective for basic tasks
|
||||
3. **Reinforcement learning** naturally fits sequential decision making
|
||||
4. **Training on history** enables future prediction
|
||||
5. **Evolutionary strategies** work without gradient computation
|
||||
|
||||
---
|
||||
|
||||
**Remember**: This is a pedagogical example showing concepts. Production systems would use more sophisticated techniques (deep learning libraries, gradient descent, GNNs), but the core ideas remain the same!
|
||||
505
vendor/ruvector/examples/mincut/neural_optimizer/main.rs
vendored
Normal file
505
vendor/ruvector/examples/mincut/neural_optimizer/main.rs
vendored
Normal file
@@ -0,0 +1,505 @@
|
||||
//! Neural Temporal Graph Optimization Example
|
||||
//!
|
||||
//! This example demonstrates how to use simple neural networks to learn
|
||||
//! optimal graph configurations over time. The neural optimizer learns from
|
||||
//! historical graph evolution to predict which modifications will lead to
|
||||
//! better minimum cut values.
|
||||
|
||||
use ruvector_mincut::prelude::*;
|
||||
|
||||
/// Simple neural network for graph optimization
|
||||
/// Uses linear transformations without external deep learning dependencies
|
||||
struct NeuralNetwork {
|
||||
/// Weight matrix for hidden layer (input_size × hidden_size)
|
||||
weights_hidden: Vec<Vec<f64>>,
|
||||
/// Bias vector for hidden layer
|
||||
bias_hidden: Vec<f64>,
|
||||
/// Weight matrix for output layer (hidden_size × output_size)
|
||||
weights_output: Vec<Vec<f64>>,
|
||||
/// Bias vector for output layer
|
||||
bias_output: Vec<f64>,
|
||||
}
|
||||
|
||||
impl NeuralNetwork {
|
||||
fn new(input_size: usize, hidden_size: usize, output_size: usize) -> Self {
|
||||
use std::f64::consts::PI;
|
||||
|
||||
// Initialize with small random weights (Xavier initialization)
|
||||
let scale_hidden = (2.0 / input_size as f64).sqrt();
|
||||
let scale_output = (2.0 / hidden_size as f64).sqrt();
|
||||
|
||||
let weights_hidden = (0..input_size)
|
||||
.map(|i| {
|
||||
(0..hidden_size)
|
||||
.map(|j| {
|
||||
let angle = (i * 7 + j * 13) as f64;
|
||||
(angle * PI / 180.0).sin() * scale_hidden
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let bias_hidden = vec![0.0; hidden_size];
|
||||
|
||||
let weights_output = (0..hidden_size)
|
||||
.map(|i| {
|
||||
(0..output_size)
|
||||
.map(|j| {
|
||||
let angle = (i * 11 + j * 17) as f64;
|
||||
(angle * PI / 180.0).cos() * scale_output
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let bias_output = vec![0.0; output_size];
|
||||
|
||||
Self {
|
||||
weights_hidden,
|
||||
bias_hidden,
|
||||
weights_output,
|
||||
bias_output,
|
||||
}
|
||||
}
|
||||
|
||||
/// Forward pass through the network
|
||||
fn forward(&self, input: &[f64]) -> Vec<f64> {
|
||||
// Hidden layer: input × weights_hidden + bias
|
||||
let hidden: Vec<f64> = (0..self.bias_hidden.len())
|
||||
.map(|j| {
|
||||
let sum: f64 = input
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &x)| x * self.weights_hidden[i][j])
|
||||
.sum();
|
||||
relu(sum + self.bias_hidden[j])
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Output layer: hidden × weights_output + bias
|
||||
(0..self.bias_output.len())
|
||||
.map(|j| {
|
||||
let sum: f64 = hidden
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &x)| x * self.weights_output[i][j])
|
||||
.sum();
|
||||
sum + self.bias_output[j]
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Mutate weights for evolutionary optimization
|
||||
fn mutate(&mut self, mutation_rate: f64, mutation_strength: f64) {
|
||||
let mut rng_state = 42u64;
|
||||
|
||||
for i in 0..self.weights_hidden.len() {
|
||||
for j in 0..self.weights_hidden[i].len() {
|
||||
if simple_random(&mut rng_state) < mutation_rate {
|
||||
let delta = (simple_random(&mut rng_state) - 0.5) * mutation_strength;
|
||||
self.weights_hidden[i][j] += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..self.weights_output.len() {
|
||||
for j in 0..self.weights_output[i].len() {
|
||||
if simple_random(&mut rng_state) < mutation_rate {
|
||||
let delta = (simple_random(&mut rng_state) - 0.5) * mutation_strength;
|
||||
self.weights_output[i][j] += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clone the network
|
||||
fn clone_network(&self) -> Self {
|
||||
Self {
|
||||
weights_hidden: self.weights_hidden.clone(),
|
||||
bias_hidden: self.bias_hidden.clone(),
|
||||
weights_output: self.weights_output.clone(),
|
||||
bias_output: self.bias_output.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ReLU activation function
|
||||
fn relu(x: f64) -> f64 {
|
||||
x.max(0.0)
|
||||
}
|
||||
|
||||
/// Simple random number generator (LCG)
|
||||
fn simple_random(state: &mut u64) -> f64 {
|
||||
*state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
|
||||
(*state >> 32) as f64 / u32::MAX as f64
|
||||
}
|
||||
|
||||
/// Extract features from a graph for neural network input
|
||||
fn extract_features(graph: &DynamicGraph) -> Vec<f64> {
|
||||
let stats = graph.stats();
|
||||
let node_count = stats.num_vertices as f64;
|
||||
let edge_count = stats.num_edges as f64;
|
||||
let max_possible_edges = node_count * (node_count - 1.0) / 2.0;
|
||||
let density = if max_possible_edges > 0.0 {
|
||||
edge_count / max_possible_edges
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// Calculate average degree
|
||||
let avg_degree = stats.avg_degree;
|
||||
|
||||
vec![
|
||||
node_count / 100.0, // Normalized node count
|
||||
edge_count / 500.0, // Normalized edge count
|
||||
density, // Graph density
|
||||
avg_degree / 10.0, // Normalized average degree
|
||||
]
|
||||
}
|
||||
|
||||
/// Neural Graph Optimizer using reinforcement learning
|
||||
struct NeuralGraphOptimizer {
|
||||
/// Policy network: decides which action to take
|
||||
policy_network: NeuralNetwork,
|
||||
/// Value network: predicts future mincut value
|
||||
value_network: NeuralNetwork,
|
||||
/// Training history
|
||||
history: Vec<(Vec<f64>, f64)>, // (state, actual_mincut)
|
||||
}
|
||||
|
||||
impl NeuralGraphOptimizer {
|
||||
fn new() -> Self {
|
||||
let input_size = 4; // Feature vector size
|
||||
let hidden_size = 8;
|
||||
let policy_output = 3; // Add edge, remove edge, do nothing
|
||||
let value_output = 1; // Predicted mincut value
|
||||
|
||||
Self {
|
||||
policy_network: NeuralNetwork::new(input_size, hidden_size, policy_output),
|
||||
value_network: NeuralNetwork::new(input_size, hidden_size, value_output),
|
||||
history: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Predict the best action for current graph state
|
||||
fn predict_action(&self, graph: &DynamicGraph) -> usize {
|
||||
let features = extract_features(graph);
|
||||
let policy_output = self.policy_network.forward(&features);
|
||||
|
||||
// Find action with highest probability
|
||||
policy_output
|
||||
.iter()
|
||||
.enumerate()
|
||||
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
|
||||
.map(|(idx, _)| idx)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Predict the mincut value for current state
|
||||
fn predict_value(&self, graph: &DynamicGraph) -> f64 {
|
||||
let features = extract_features(graph);
|
||||
let value_output = self.value_network.forward(&features);
|
||||
value_output[0].max(0.0)
|
||||
}
|
||||
|
||||
/// Apply an action to the graph
|
||||
fn apply_action(&self, graph: &mut DynamicGraph, action: usize, rng_state: &mut u64) {
|
||||
let stats = graph.stats();
|
||||
match action {
|
||||
0 => {
|
||||
// Add a random edge
|
||||
let n = stats.num_vertices;
|
||||
if n > 1 {
|
||||
let u = (simple_random(rng_state) * n as f64) as u64;
|
||||
let v = (simple_random(rng_state) * (n - 1) as f64) as u64;
|
||||
let v = if v >= u { v + 1 } else { v };
|
||||
let weight = 1.0 + simple_random(rng_state) * 10.0;
|
||||
let _ = graph.insert_edge(u, v, weight);
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
// Remove a random edge (simplified - would need edge list in real impl)
|
||||
// For this example, we'll skip actual removal
|
||||
}
|
||||
_ => {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Train the networks using evolutionary strategy
|
||||
fn train(&mut self, generations: usize, population_size: usize) {
|
||||
println!("\n🧠 Training Neural Networks");
|
||||
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
|
||||
for gen in 0..generations {
|
||||
// Create population by mutating current networks
|
||||
let mut population = Vec::new();
|
||||
|
||||
for _ in 0..population_size {
|
||||
let mut policy = self.policy_network.clone_network();
|
||||
let mut value = self.value_network.clone_network();
|
||||
|
||||
policy.mutate(0.1, 0.5);
|
||||
value.mutate(0.1, 0.5);
|
||||
|
||||
population.push((policy, value));
|
||||
}
|
||||
|
||||
// Evaluate fitness on training data
|
||||
let mut fitness_scores = Vec::new();
|
||||
|
||||
for (_policy, value) in &population {
|
||||
let mut total_error = 0.0;
|
||||
|
||||
for (state, actual_mincut) in &self.history {
|
||||
let predicted = value.forward(state)[0];
|
||||
let error = (predicted - actual_mincut).abs();
|
||||
total_error += error;
|
||||
}
|
||||
|
||||
let fitness = if self.history.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
-total_error / self.history.len() as f64
|
||||
};
|
||||
|
||||
fitness_scores.push(fitness);
|
||||
}
|
||||
|
||||
// Select best network
|
||||
if let Some((best_idx, &best_fitness)) = fitness_scores
|
||||
.iter()
|
||||
.enumerate()
|
||||
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
|
||||
{
|
||||
if gen % 10 == 0 {
|
||||
println!("Generation {}: Best fitness = {:.4}", gen, -best_fitness);
|
||||
}
|
||||
|
||||
self.policy_network = population[best_idx].0.clone_network();
|
||||
self.value_network = population[best_idx].1.clone_network();
|
||||
}
|
||||
}
|
||||
|
||||
println!("✓ Training complete");
|
||||
}
|
||||
|
||||
/// Record a state observation for training
|
||||
fn record_observation(&mut self, graph: &DynamicGraph, mincut: f64) {
|
||||
let features = extract_features(graph);
|
||||
self.history.push((features, mincut));
|
||||
|
||||
// Keep only recent history (last 100 observations)
|
||||
if self.history.len() > 100 {
|
||||
self.history.remove(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a random graph for testing
|
||||
fn generate_random_graph(nodes: usize, edge_prob: f64, rng_state: &mut u64) -> DynamicGraph {
|
||||
let graph = DynamicGraph::new();
|
||||
|
||||
for i in 0..nodes {
|
||||
graph.add_vertex(i as u64);
|
||||
}
|
||||
|
||||
for i in 0..nodes {
|
||||
for j in i + 1..nodes {
|
||||
if simple_random(rng_state) < edge_prob {
|
||||
let weight = 1.0 + simple_random(rng_state) * 10.0;
|
||||
let _ = graph.insert_edge(i as u64, j as u64, weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
graph
|
||||
}
|
||||
|
||||
/// Calculate minimum cut value for a graph
|
||||
/// This is a simplified approximation for demonstration purposes
|
||||
fn calculate_mincut(graph: &DynamicGraph) -> Option<f64> {
|
||||
let stats = graph.stats();
|
||||
if stats.num_edges == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// For this example, we'll use a simple approximation based on graph properties
|
||||
// Real implementation would use the full MinCut algorithm
|
||||
// This approximation: mincut ≈ min_degree * (total_weight / num_edges)
|
||||
let min_cut_approx = stats.min_degree as f64 * (stats.total_weight / stats.num_edges as f64);
|
||||
|
||||
Some(min_cut_approx.max(1.0))
|
||||
}
|
||||
|
||||
/// Run optimization loop with neural guidance
|
||||
fn optimize_with_neural(
|
||||
optimizer: &mut NeuralGraphOptimizer,
|
||||
initial_graph: &DynamicGraph,
|
||||
steps: usize,
|
||||
rng_state: &mut u64,
|
||||
) -> Vec<f64> {
|
||||
let mut graph = initial_graph.clone();
|
||||
let mut mincut_history = Vec::new();
|
||||
|
||||
for _ in 0..steps {
|
||||
// Predict and apply action
|
||||
let action = optimizer.predict_action(&graph);
|
||||
optimizer.apply_action(&mut graph, action, rng_state);
|
||||
|
||||
// Calculate current mincut
|
||||
if let Some(mincut) = calculate_mincut(&graph) {
|
||||
mincut_history.push(mincut);
|
||||
optimizer.record_observation(&graph, mincut);
|
||||
}
|
||||
}
|
||||
|
||||
mincut_history
|
||||
}
|
||||
|
||||
/// Run optimization with random actions (baseline)
|
||||
fn optimize_random(initial_graph: &DynamicGraph, steps: usize, rng_state: &mut u64) -> Vec<f64> {
|
||||
let mut graph = initial_graph.clone();
|
||||
let mut mincut_history = Vec::new();
|
||||
|
||||
for _ in 0..steps {
|
||||
// Random action
|
||||
let action = (simple_random(rng_state) * 3.0) as usize;
|
||||
|
||||
// Apply action
|
||||
let stats = graph.stats();
|
||||
match action {
|
||||
0 => {
|
||||
let n = stats.num_vertices;
|
||||
if n > 1 {
|
||||
let u = (simple_random(rng_state) * n as f64) as u64;
|
||||
let v = (simple_random(rng_state) * (n - 1) as f64) as u64;
|
||||
let v = if v >= u { v + 1 } else { v };
|
||||
let weight = 1.0 + simple_random(rng_state) * 10.0;
|
||||
let _ = graph.insert_edge(u, v, weight);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Calculate mincut
|
||||
if let Some(mincut) = calculate_mincut(&graph) {
|
||||
mincut_history.push(mincut);
|
||||
}
|
||||
}
|
||||
|
||||
mincut_history
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("╔════════════════════════════════════════════════════════════╗");
|
||||
println!("║ Neural Temporal Graph Optimization Example ║");
|
||||
println!("║ Learning to Predict Optimal Graph Configurations ║");
|
||||
println!("╚════════════════════════════════════════════════════════════╝");
|
||||
|
||||
let mut rng_state = 12345u64;
|
||||
|
||||
// Initialize neural optimizer
|
||||
println!("\n📊 Initializing Neural Graph Optimizer");
|
||||
let mut optimizer = NeuralGraphOptimizer::new();
|
||||
|
||||
// Generate initial training data
|
||||
println!("\n🔬 Generating Training Data");
|
||||
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
|
||||
for i in 0..20 {
|
||||
let graph = generate_random_graph(10, 0.3, &mut rng_state);
|
||||
if let Some(mincut) = calculate_mincut(&graph) {
|
||||
optimizer.record_observation(&graph, mincut);
|
||||
if i % 5 == 0 {
|
||||
println!("Sample {}: Mincut = {:.2}", i, mincut);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Train the neural networks
|
||||
optimizer.train(50, 20);
|
||||
|
||||
// Compare neural-guided vs random optimization
|
||||
println!("\n⚖️ Comparing Optimization Strategies");
|
||||
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
|
||||
let test_graph = generate_random_graph(15, 0.25, &mut rng_state);
|
||||
let steps = 30;
|
||||
|
||||
println!("\n🤖 Neural-Guided Optimization ({} steps)", steps);
|
||||
let neural_history = optimize_with_neural(&mut optimizer, &test_graph, steps, &mut rng_state);
|
||||
|
||||
println!("\n🎲 Random Action Baseline ({} steps)", steps);
|
||||
rng_state = 12345u64; // Reset for fair comparison
|
||||
let random_history = optimize_random(&test_graph, steps, &mut rng_state);
|
||||
|
||||
// Calculate statistics
|
||||
println!("\n📈 Results Comparison");
|
||||
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
|
||||
if !neural_history.is_empty() {
|
||||
let neural_avg: f64 = neural_history.iter().sum::<f64>() / neural_history.len() as f64;
|
||||
let neural_min = neural_history.iter().cloned().fold(f64::INFINITY, f64::min);
|
||||
let neural_max = neural_history
|
||||
.iter()
|
||||
.cloned()
|
||||
.fold(f64::NEG_INFINITY, f64::max);
|
||||
|
||||
println!("\nNeural-Guided:");
|
||||
println!(" Average Mincut: {:.2}", neural_avg);
|
||||
println!(" Min Mincut: {:.2}", neural_min);
|
||||
println!(" Max Mincut: {:.2}", neural_max);
|
||||
}
|
||||
|
||||
if !random_history.is_empty() {
|
||||
let random_avg: f64 = random_history.iter().sum::<f64>() / random_history.len() as f64;
|
||||
let random_min = random_history.iter().cloned().fold(f64::INFINITY, f64::min);
|
||||
let random_max = random_history
|
||||
.iter()
|
||||
.cloned()
|
||||
.fold(f64::NEG_INFINITY, f64::max);
|
||||
|
||||
println!("\nRandom Baseline:");
|
||||
println!(" Average Mincut: {:.2}", random_avg);
|
||||
println!(" Min Mincut: {:.2}", random_min);
|
||||
println!(" Max Mincut: {:.2}", random_max);
|
||||
}
|
||||
|
||||
// Show improvement
|
||||
if !neural_history.is_empty() && !random_history.is_empty() {
|
||||
let neural_avg: f64 = neural_history.iter().sum::<f64>() / neural_history.len() as f64;
|
||||
let random_avg: f64 = random_history.iter().sum::<f64>() / random_history.len() as f64;
|
||||
let improvement = ((random_avg - neural_avg) / random_avg * 100.0).abs();
|
||||
|
||||
println!("\n✨ Improvement: {:.1}%", improvement);
|
||||
}
|
||||
|
||||
// Prediction demonstration
|
||||
println!("\n🔮 Prediction vs Actual");
|
||||
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
|
||||
for i in 0..5 {
|
||||
let test_graph = generate_random_graph(12, 0.3, &mut rng_state);
|
||||
let predicted = optimizer.predict_value(&test_graph);
|
||||
|
||||
if let Some(actual) = calculate_mincut(&test_graph) {
|
||||
let error = ((predicted - actual) / actual * 100.0).abs();
|
||||
println!(
|
||||
"Test {}: Predicted = {:.2}, Actual = {:.2}, Error = {:.1}%",
|
||||
i + 1,
|
||||
predicted,
|
||||
actual,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n✅ Example Complete");
|
||||
println!("\nKey Insights:");
|
||||
println!("• Neural networks can learn graph optimization patterns");
|
||||
println!("• Simple linear models work for basic prediction tasks");
|
||||
println!("• Reinforcement learning helps guide graph modifications");
|
||||
println!("• Training on historical data improves future predictions");
|
||||
}
|
||||
Reference in New Issue
Block a user