hill_descent_lib
A Rust genetic algorithm library for n-dimensional optimization problems.
Quick Start
use ;
use RangeInclusive;
// Define your fitness function (lower scores are better)
;
Features
- N-dimensional optimization - Handles any number of parameters
- Spatial regions - Divides search space into adaptive regions for efficient exploration
- Genetic algorithm - Uses crossover, mutation, and selection for evolution
- Deterministic - Seeded RNG ensures reproducible results
- Zero-copy parallelism - Leverages Rayon for efficient multi-core processing
- Flexible fitness functions - Easy-to-implement trait for custom optimization problems
- Optional tracing - Built-in logging support for debugging (feature:
enable-tracing)
Installation
Add this to your Cargo.toml:
[]
= "0.1.0"
Usage Examples
Basic 2D Optimization
use ;
use RangeInclusive;
;
Higher Dimensional Problems
use ;
use RangeInclusive;
;
Custom Function Floor
By default, the algorithm assumes the optimal fitness is 0. For functions with different optima:
use ;
use RangeInclusive;
;
Machine Learning / Neural Network Optimization
For large-scale parameter optimization (e.g., neural network weights), hill_descent_lib excels at finding good initializations or tuning thousands of parameters:
use ;
use RangeInclusive;
// Simulate a neural network with training data managed internally
Key considerations for large-scale optimization:
-
Parameter count: This library has been tested with 50,000+ parameters. For neural networks, this is suitable for smaller architectures or as an initialization strategy before gradient descent.
-
Population scaling: Use
10-20xthe number of parameters for population size, but cap at a reasonable limit (e.g., 10,000) for memory and performance. -
Region count: A good heuristic is
sqrt(population_size)for balanced exploration/exploitation. -
Hybrid approach: Use hill_descent_lib to find a good initialization, then switch to gradient-based methods (SGD, Adam, etc.) for fine-tuning. Genetic algorithms excel at escaping local minima.
-
Data management: Keep training data inside your fitness function struct to avoid passing large datasets through the API.
Scaling Guidelines
Understanding how hill_descent_lib scales helps you configure it effectively for your problem size:
Parameter Count vs Performance
| Parameters | Recommended Pop Size | Recommended Regions | Memory (approx) | Time per Epoch |
|---|---|---|---|---|
| 2-10 | 100-200 | 10-15 | < 1 MB | < 1ms |
| 10-100 | 500-1,000 | 20-30 | < 10 MB | 10-50ms |
| 100-1,000 | 1,000-5,000 | 30-70 | 10-100 MB | 50-500ms |
| 1,000-10,000 | 5,000-10,000 | 70-100 | 100 MB - 1 GB | 0.5-5s |
| 10,000+ | 10,000 (capped) | 100 | 1-10 GB | 5-30s |
Times measured on modern multi-core CPU (Ryzen/Intel i7+). Actual performance varies with fitness function complexity.
Configuration Guidelines
Population Size:
- Small problems (< 100 params):
10-20xparameter count works well - Medium problems (100-1,000 params): Use
5-10xparameter count - Large problems (1,000+ params): Cap at 10,000 for practical memory/time constraints
- Rule of thumb: More population = better exploration, but diminishing returns past 10,000
Region Count:
- Formula:
sqrt(population_size)provides good balance - Minimum: At least 10 regions for any problem
- Maximum: Diminishing returns past 100 regions
- Trade-off: More regions = finer spatial resolution but more overhead
Epochs (Generations):
- Simple functions: 100-500 epochs usually sufficient
- Complex landscapes: 1,000-5,000 epochs may be needed
- Early stopping: Monitor
get_best_score()- stop if no improvement for 100+ epochs - Convergence: Expect 70-90% of final quality within first 20% of epochs
Memory Usage
Memory consumption is primarily driven by:
Memory ≈ population_size × parameter_count × 24 bytes
+ region_count × 1KB overhead
+ function-specific data
Example calculations:
- 1,000 params, 5,000 pop: ~120 MB
- 10,000 params, 10,000 pop: ~2.4 GB
- 50,000 params, 10,000 pop: ~12 GB
Tips for large problems:
- Use
cargo build --release- debug builds use significantly more memory - Monitor with
get_state()sparingly (serialization copies data) - Keep fitness function data on disk/database if > 1 GB
When to Use vs When Not to Use
✅ Good use cases:
- No gradient information available (black-box optimization)
- Multimodal landscapes with many local minima
- Discrete or mixed parameter spaces (with custom encoding)
- Initial parameter search before fine-tuning with gradient methods
- Robust optimization where avoiding poor local minima is critical
- Small to medium problems (< 10,000 parameters)
❌ Consider alternatives when:
- Smooth, unimodal functions: Use gradient descent (much faster)
- Very large parameter spaces (> 50,000 params): Consider specialized methods
- Real-time constraints: Genetic algorithms need many evaluations
- Analytical solutions exist: Don't optimize if you can solve directly
- Limited compute budget: Each epoch evaluates entire population
Parallel Performance
hill_descent_lib uses Rayon for parallel region processing:
- Scaling: Near-linear speedup up to
min(num_regions, num_cores) - Bottlenecks: Fitness function evaluation time dominates
- Sweet spot: 4-16 cores see excellent utilization
- Diminishing returns: Beyond 32 cores, overhead increases
Optimization tips:
- Ensure fitness function is computationally intensive (> 1μs)
- Avoid I/O or locks in fitness function (breaks parallelism)
- Use
releasebuilds - parallelism overhead matters more in debug
Using the Tracing Feature
For debugging and analysis, enable the optional tracing feature:
[]
= { = "0.1.0", = ["enable-tracing"] }
use ;
How It Works
The library implements a genetic algorithm with spatial partitioning:
- Initialization - Creates a population of random organisms within specified bounds
- Regions - Divides the search space into adaptive regions based on organism distribution
- Evaluation - Each organism is scored using your fitness function
- Selection - Better-performing organisms in each region get more reproduction opportunities
- Reproduction - Sexual reproduction with crossover and mutation creates offspring
- Adaptation - Regions dynamically adjust based on population distribution and fitness landscape
The algorithm automatically manages:
- Region boundaries and subdivision
- Carrying capacity per region based on fitness
- Organism aging and death
- Mutation rates and adjustment parameters
- Search space expansion when needed
API Overview
Core Types
setup_world()- Initialize the optimization environmentGlobalConstants- Configuration (population size, region count, seed)World- Main optimization container with methods:training_run()- Run one generationget_best_score()- Get current best fitnessget_best_organism()- Get the best organismget_state()- Get JSON representation of world state
Traits to Implement
SingleValuedFunction- Define your fitness functionsingle_run(&self, params: &[f64]) -> f64- Requiredfunction_floor(&self) -> f64- Optional (default: 0.0)
Performance Characteristics
- Parallelism: Region processing uses Rayon for multi-core efficiency
- Memory: Allocates based on population size and dimensionality
- Determinism: Seeded RNG ensures reproducible runs with same configuration
- Scalability: Tested with 100+ dimensions and populations of 10,000+
Common Use Cases
- Parameter optimization for machine learning models
- Neural network weight initialization and tuning
- Function minimization/maximization problems
- Engineering design optimization
- Scientific computing and simulation tuning
- Hyperparameter search
Comparison to Other Libraries
Unlike gradient-based optimizers, hill_descent_lib:
- ✅ Doesn't require differentiable functions
- ✅ Handles discrete and continuous parameters
- ✅ Explores multiple regions simultaneously
- ✅ Less likely to get stuck in local minima
- ❌ Generally requires more function evaluations
- ❌ Not as fast for smooth, convex problems
Documentation
Full API documentation is available on docs.rs.
Examples
See the examples/ directory for more usage patterns:
simple_optimization.rs- Basic 2D examplecustom_function.rs- Implementing custom fitness functionsmulti_dimensional.rs- High-dimensional optimization
Run examples with:
Minimum Supported Rust Version (MSRV)
Rust 2024 edition (Rust 1.82.0+)
Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Acknowledgments
Developed on Windows with cross-platform compatibility in mind.
Repository
Source code: https://github.com/cainem/hill_descent