Interactive Genetic Algorithms (IGA) incorporate human feedback into the evolutionary process. Instead of an automated fitness function, users evaluate candidates through ratings, comparisons, or selections.
Ideal for:
- Aesthetic optimization (art, design, music)
- Subjective preferences (user interfaces)
- Hard-to-formalize objectives
- Creative exploration
Challenges:
- User fatigue limits evaluations
- Noisy, inconsistent feedback
- Slower convergence than automated GA
Fugue-evo supports multiple ways to gather user feedback:
| Mode | User Action | Best For |
|---|---|---|
| Rating | Score each candidate 1-10 | Absolute quality assessment |
| Pairwise | Choose better of two | Relative comparisons |
| Batch Selection | Pick best N from batch | Quick approximate ranking |
{{#include ../../../examples/interactive_evolution.rs}}cargo run --example interactive_evolutionThis example simulates user feedback. In a real application, you would replace the simulation with actual UI interaction.
let mut iga = InteractiveGABuilder::<RealVector, (), (), ()>::new()
.population_size(12)
.elitism_count(2)
.evaluation_mode(EvaluationMode::Rating)
.batch_size(4)
.min_coverage(0.8)
.max_generations(5)
.aggregation_model(AggregationModel::DirectRating {
default_rating: 5.0,
})
.bounds(bounds)
.selection(TournamentSelection::new(2))
.crossover(SbxCrossover::new(15.0))
.mutation(PolynomialMutation::new(20.0))
.build()?;Key parameters:
evaluation_mode: How users provide feedbackbatch_size: Candidates shown per evaluation roundmin_coverage: Fraction of population needing evaluationaggregation_model: How to combine multiple evaluations
loop {
match iga.step(&mut rng) {
StepResult::NeedsEvaluation(request) => {
// Present to user, get feedback
let response = get_user_response(&request);
iga.provide_response(response);
}
StepResult::GenerationComplete { generation, best_fitness, coverage } => {
println!("Generation {} complete", generation);
}
StepResult::Complete(result) => {
println!("Evolution complete!");
break;
}
}
}Rating Mode:
EvaluationRequest::RateCandidates { candidates, .. } => {
// Show candidates to user
for candidate in candidates {
display_candidate(&candidate.genome);
}
// Collect ratings
let ratings: Vec<(CandidateId, f64)> = /* user input */;
EvaluationResponse::ratings(ratings)
}Pairwise Mode:
EvaluationRequest::PairwiseComparison { candidate_a, candidate_b, .. } => {
// Show both candidates
display_comparison(&candidate_a.genome, &candidate_b.genome);
// Get user's choice
let winner = /* user choice */;
EvaluationResponse::winner(winner)
}Batch Selection:
EvaluationRequest::BatchSelection { candidates, select_count, .. } => {
// Show all candidates
for c in candidates { display_candidate(&c.genome); }
// User selects best N
let selected: Vec<CandidateId> = /* user picks */;
EvaluationResponse::selected(selected)
}How to combine feedback into fitness estimates:
AggregationModel::DirectRating { default_rating: 5.0 }Uses ratings directly as fitness. Unevaluated candidates get the default.
AggregationModel::ImplicitRanking {
selected_bonus: 1.0,
not_selected_penalty: 0.3,
base_fitness: 5.0,
}For batch selection mode:
- Selected candidates get bonus
- Non-selected get penalty
- Accumulates over evaluations
For pairwise comparisons, estimates latent "skill" from win/loss records using the Bradley-Terry statistical model.
.population_size(12)Fewer candidates = fewer evaluations needed.
.min_coverage(0.5) // Only evaluate 50% of populationNot every candidate needs evaluation each generation.
.batch_size(4) // Show 4 at a time- Too small: Many rounds, tedious
- Too large: Overwhelming, poor decisions
.max_evaluations(100) // Stop after 100 user interactionsFor testing, simulate user preferences:
struct SimulatedUser {
target: Vec<f64>,
}
impl SimulatedUser {
fn rate(&self, genome: &RealVector) -> f64 {
let distance: f64 = genome.genes().iter()
.zip(self.target.iter())
.map(|(g, t)| (g - t).powi(2))
.sum::<f64>()
.sqrt();
// Closer to target = higher rating
10.0 - distance.min(9.0)
}
}This allows testing IGA logic without human interaction.
// Pseudocode for web integration
async fn evolution_endpoint(state: &mut IgaState) -> Response {
match state.iga.step(&mut state.rng) {
StepResult::NeedsEvaluation(request) => {
// Return candidates to frontend
Json(CandidatesForEvaluation::from(request))
}
// ...
}
}
async fn feedback_endpoint(feedback: UserFeedback, state: &mut IgaState) {
let response = EvaluationResponse::from(feedback);
state.iga.provide_response(response);
}fn update(&mut self, message: Message) {
match message {
Message::NextStep => {
if let StepResult::NeedsEvaluation(req) = self.iga.step(&mut self.rng) {
self.current_request = Some(req);
}
}
Message::UserRated(ratings) => {
let response = EvaluationResponse::ratings(ratings);
self.iga.provide_response(response);
}
}
}- Different modes: Compare Rating vs Batch Selection modes
- Noisy preferences: Add randomness to simulated user, observe robustness
- Visualization: Display RealVector as colors/shapes for visual evaluation
- Hyperparameter Learning - Adaptive parameter tuning
- Custom Fitness Functions - Combine interactive with automated fitness