| Crates.io | fx-durable-ga |
| lib.rs | fx-durable-ga |
| version | 0.10.0 |
| created_at | 2025-10-26 07:15:07.481896+00 |
| updated_at | 2025-12-01 06:11:00.374835+00 |
| description | Durable GA event driven optimization loop on PostgreSQL |
| homepage | |
| repository | https://github.com/jakob-lilliemarck/fx-durable-ga |
| max_upload_size | |
| id | 1901040 |
| size | 468,147 |
A durable, auditable genetic algorithm optimization library built on PostgreSQL.
fx-durable-ga is designed for long-running genetic algorithm optimizations where durability and auditability matter more than framework speed. It's built for scenarios where fitness evaluations are expensive (seconds to hours) and you need:
Well suited for:
Not ideal for:
The library uses PostgreSQL for both storage and coordination:
Network latency to the database is the primary overhead, but this is negligible when fitness evaluations take seconds or longer.
use fx_durable_ga::{bootstrap, models::*};
use futures::future::BoxFuture;
#[derive(Debug, Clone)]
struct MyParams {
learning_rate: f64,
batch_size: i64,
}
// 2. Implement genetic encoding
impl Encodeable for MyParams {
const NAME: &'static str = "MyParams";
type Phenotype = MyParams;
fn morphology() -> Vec<GeneBounds> {
vec![
GeneBounds::decimal(0.001, 1.0, 1000, 3).unwrap(),
GeneBounds::integer(16, 512, 32).unwrap(),
]
}
fn encode(&self) -> Vec<i64> {
let bounds = Self::morphology();
vec![
bounds[0].encode_f64(self.learning_rate).expect("learning_rate within bounds"),
bounds[1].encode_f64(self.batch_size as f64).expect("batch_size within bounds"),
]
}
fn decode(genes: &[i64]) -> Self::Phenotype {
let bounds = Self::morphology();
MyParams {
learning_rate: bounds[0].decode_f64(genes[0]),
batch_size: bounds[1].decode_f64(genes[1]) as i64,
}
}
}
// 3. Implement fitness evaluation
struct MyEvaluator;
impl Evaluator<MyParams> for MyEvaluator {
fn fitness<'a>(&self, params: MyParams, _terminated: &'a Box<dyn Terminated>) -> BoxFuture<'a, Result<f64, anyhow::Error>> {
Box::pin(async move {
// Your expensive evaluation goes here - make it non-blocking, or run the listener on a separate thread.
let accuracy = train_model(params).await?;
Ok(accuracy)
})
}
}
// 4. Start optimization
let service = bootstrap(pool).await?
.register::<MyParams, _>(MyEvaluator).await?
.build();
service.new_optimization_request(
MyParams::NAME,
MyParams::HASH,
FitnessGoal::maximize(0.95)?, // Fitness value to stop at while trying to maximize
Schedule::generational(100, 10), // 100 generations, 10 per generation.
Selector::tournament(3, 25), // Tournament selection
Mutagen::new(
Temperature::constant(0.5)?,
MutationRate::constant(0.1)?,
),
Crossover::uniform(0.5)?,
Distribution::latin_hypercube(50), // Better than random for most cases
).await?;
cargo doc --openexamples/point_search.rs - basic exampleexamples/regression_model.rs - hyperparameter optimization for a regression modelRun migrations using the provided sqlx migrator.
fx_durable_ga::migrations::run_migrationsfx_durable_ga::migrations::run_migrations. Note that this will use the default schema name for fx-mq-jobs, if you wish to use another schema, you will need call each migrator.Set DATABASE_URL in your env and create the database. If you're using sqlx cli call sqlx database create. Then do:
SQLX_OFFLINE=true cargo run --bin migrate --features migration
That runs migrations for this crate and its dependencies fx-event-bus and fx-mq-jobs. The migration binary uses the feature flag "migration" to exclude all code that is statically typechecked by sqlx. Once it has been run you may set SQLX_OFFLINE=false and everything should work as normally.
Set DATABASE_URL in your environment and create the database (e.g., sqlx database create). Run migrations with:
SQLX_OFFLINE=true cargo run --bin migrate --features migration
Contributions are welcome! Please feel free to submit pull requests for bug fixes, improvements, or new features.