selen 0.15.5

Constraint Satisfaction Problem (CSP) solver
Documentation
# Memory Management & Resource Limits

**Status**: ✅ Implemented  
**Version**: Added in v0.6.0  
**Date**: September 2025

## Overview

The CSP Solver now includes comprehensive memory management and resource limiting to prevent system memory exhaustion during model building and solving.

## 🚨 Problem Solved

**Before**: Large models could consume 28GB+ of system RAM and crash the IDE/system during variable creation.

**After**: Memory limits are enforced during variable creation with early failure and clear error messages.

## 🎛️ Default Configuration

Starting with version 0.6.0, all models have sensible default limits:

```rust
// Default limits applied automatically
let m = Model::default();
// ↳ Memory limit: 2GB
// ↳ Timeout: 60000 milliseconds (60 seconds)
```

### Default Values

| Resource | Default Limit | Purpose |
|----------|---------------|---------|
| **Memory** | **2GB** | Prevents system memory exhaustion |
| **Timeout** | **60000 ms (60 sec)** | Prevents infinite solver runs |

## 🔧 Configuration

### Custom Limits

```rust
use selen::prelude::*;

// Configure custom limits
let config = SolverConfig::default()
    .with_max_memory_mb(512)      // 512MB memory limit
    .with_timeout_ms(30000);      // 30000ms = 30 second timeout

let mut m = Model::with_config(config);
```

### No Limits (Use with Caution)

```rust
// Remove all limits - USE CAREFULLY!
let config = SolverConfig::unlimited();
let mut m = Model::with_config(config);
```

### Production Recommended Limits

```rust
// Conservative for shared environments
let config = SolverConfig::default()
    .with_max_memory_mb(1024)     // 1GB limit
    .with_timeout_ms(120000);     // 120000ms = 2 minute timeout

// Generous for dedicated systems
let config = SolverConfig::default()
    .with_max_memory_mb(4096)     // 4GB limit  
    .with_timeout_ms(300000);     // 300000ms = 5 minute timeout
```

## 📊 Memory Estimation

### How It Works

The solver estimates memory usage during variable creation:

- **Float variables**: ~64 bytes each (interval storage)
- **Small integer domains** (<1000 values): ~96 + domain_size×8 bytes
- **Large integer domains** (≥1000 values): ~96 + domain_size×4 bytes (sparse)
- **Custom domains**: Based on number of specific values

### Memory Tracking

```rust
let mut m = Model::with_config(
    SolverConfig::default().with_max_memory_mb(100)
);

// Create variables - memory is tracked automatically
for i in 0..1000 {
    let var = m.float(0.0, 100.0);
    
    // Check current usage
    if i % 100 == 0 {
        println!("Memory usage: {:.2} MB", m.estimated_memory_mb());
    }
}

// Get detailed breakdown
println!("{}", m.memory_breakdown());
```

## 🛡️ Failure Behavior

### Memory Limit Exceeded

When memory limit is exceeded during variable creation:

```
thread 'main' panicked at src/model/core.rs:460:17:
Memory limit exceeded during variable creation: Memory limit exceeded (used: 1MB, limit: 1MB).
Configured limit: Some(1) MB. 
Current usage: 1.0001220703125 MB.
This prevents system memory exhaustion.
```

### Solve-Time Checks

If memory limit was exceeded during model building:

```rust
match m.solve() {
    Err(SolverError::MemoryLimit { usage_mb, limit_mb }) => {
        println!("Memory limit exceeded: used {}MB, limit {}MB", 
                 usage_mb.unwrap_or(0), limit_mb.unwrap_or(0));
    }
    // ... other cases
}
```

## 🎯 Best Practices

### 1. Choose Appropriate Limits

```rust
// For testing/development
let config = SolverConfig::default().with_max_memory_mb(100);

// For production batch processing
let config = SolverConfig::default()
    .with_max_memory_mb(4096)
    .with_timeout_ms(600000);       // 600000ms = 10 minutes

// For real-time applications
let config = SolverConfig::default()
    .with_max_memory_mb(256)
    .with_timeout_ms(5000);         // 5000ms = 5 seconds
```

### 2. Monitor Memory Usage

```rust
let mut m = Model::with_config(config);

// Check memory periodically during model building
if m.estimated_memory_mb() > 100.0 {
    println!("Warning: High memory usage detected");
    println!("{}", m.memory_breakdown());
}
```

### 3. Handle Failures Gracefully

```rust
use std::panic;

let result = panic::catch_unwind(|| {
    let mut m = Model::with_config(config);
    
    // This might panic if memory limit exceeded
    for i in 0..10000 {
        let var = m.float(0.0, 1000.0);
    }
    
    m.solve()
});

match result {
    Ok(solve_result) => { /* Handle solve result */ }
    Err(_) => {
        println!("Model building failed - likely memory limit exceeded");
        // Fallback strategy or reduce problem size
    }
}
```

## 🔍 Accuracy & Performance

### Memory Estimation Accuracy

- **Float variables**: Very accurate (~64 bytes actual vs ~64 bytes estimated)
- **Integer variables**: Good accuracy for typical domains
- **Large sparse domains**: Conservative estimation (usually under-estimates by 20-30%)

### Performance Impact

- **Variable creation**: <1% overhead for memory tracking
- **Memory checking**: O(1) per variable creation
- **No runtime overhead**: Zero impact during constraint propagation/solving

### Calibration

Based on testing with real workloads:

- **1MB limit**: ~8,000 float variables or ~1,000 integer variables (domain size 100)
- **100MB limit**: ~800,000 float variables  
- **1GB limit**: ~8,000,000 float variables

## ⚙️ Advanced Configuration

### Environment-Specific Configs

```rust
fn create_solver_config() -> SolverConfig {
    // Detect environment and adjust limits
    match std::env::var("ENVIRONMENT") {
        Ok(env) if env == "production" => {
            SolverConfig::default()
                .with_max_memory_mb(2048)
                .with_timeout_ms(300000)    // 300000ms = 5 minutes
        }
        Ok(env) if env == "testing" => {
            SolverConfig::default()
                .with_max_memory_mb(256)
                .with_timeout_ms(10000)     // 10000ms = 10 seconds
        }
        _ => SolverConfig::default() // Development defaults
    }
}
```

### Integration with Monitoring

```rust
struct ModelMetrics {
    memory_used: f64,
    variables_created: usize,
    constraints_added: usize,
}

impl ModelMetrics {
    fn collect(model: &Model) -> Self {
        Self {
            memory_used: model.estimated_memory_mb(),
            variables_created: model.var_count(),
            constraints_added: model.constraint_count(),
        }
    }
}
```

## 🏗️ Architecture Details

### Memory Tracking Location

Memory limits are enforced at the lowest level during variable creation:

1. **Variable Creation** (`Model::float`, `Model::int`, etc.)
2. **Memory Estimation** (based on domain type/size)  
3. **Limit Checking** (cumulative usage vs configured limit)
4. **Early Failure** (panic with clear message if exceeded)

### Design Principles

- **Fail Fast**: Detect memory issues during model building, not solving
- **Clear Errors**: Descriptive messages with actual vs limit usage
- **Zero Overhead**: No performance impact during normal operation
- **Conservative**: Better to under-estimate than over-estimate memory needs

### Alternative Approaches Considered

1. **OS-level monitoring**: Too slow and platform-dependent
2. **Periodic checks**: Could miss rapid memory growth
3. **Lazy evaluation**: Would allow memory buildup before detection
4. **Real memory tracking**: Significant performance overhead

## 📚 Related Documentation

- **[SolverConfig API]../src/utils/config.rs** - Complete configuration options
- **[Error Handling]../src/core/error.rs** - Memory limit error types  
- **[Performance Guide]performance.md** - Performance optimization strategies
- **[Production Deployment]production.md** - Production environment setup