fugue-evo 0.1.1

A Probabilistic Genetic Algorithm Library for Rust
Documentation
# Fugue Integration

Fugue-evo integrates deeply with [fugue-ppl](https://github.com/fugue-ppl/fugue), a probabilistic programming library. This enables novel trace-based genetic operators.

## Core Concept: Genomes as Traces

In fugue-ppl, a **trace** records all random choices made during program execution. Fugue-evo represents genomes as traces:

```rust,ignore
// A genome's genes become trace entries
genome.to_trace() → {
    addr!("gene", 0) → 1.234,
    addr!("gene", 1) → -0.567,
    addr!("gene", 2) → 3.890,
    // ...
}
```

## Why Traces?

### 1. Selective Resampling

Mutation becomes selective resampling of addresses:

```rust,ignore
// Traditional mutation: perturb random genes
genes[i] += noise;

// Trace mutation: resample selected addresses
let addresses_to_mutate = select_addresses(&trace, probability);
let mutated_trace = resample(trace, addresses_to_mutate);
```

### 2. Structured Crossover

Crossover can respect genome structure:

```rust,ignore
// Exchange subtrees of traces
let child_trace = merge_traces(
    parent1_trace,
    parent2_trace,
    &crossover_points,
);
```

### 3. Probabilistic Interpretation

Genetic operators have probabilistic semantics:

- **Selection** = Conditioning on high fitness
- **Mutation** = Partial resampling from prior
- **Crossover** = Trace merging

## The EvolutionaryGenome Trait

```rust,ignore
pub trait EvolutionaryGenome: Clone + Send + Sync {
    /// Convert genome to Fugue trace
    fn to_trace(&self) -> Trace;

    /// Reconstruct genome from trace
    fn from_trace(trace: &Trace) -> Result<Self, GenomeError>;
}
```

### Example: RealVector Implementation

```rust,ignore
impl EvolutionaryGenome for RealVector {
    fn to_trace(&self) -> Trace {
        let mut trace = Trace::new();
        for (i, &gene) in self.genes.iter().enumerate() {
            trace.insert(addr!("gene", i), gene);
        }
        trace
    }

    fn from_trace(trace: &Trace) -> Result<Self, GenomeError> {
        let mut genes = Vec::new();
        let mut i = 0;
        while let Some(&value) = trace.get(&addr!("gene", i)) {
            genes.push(value);
            i += 1;
        }
        Ok(RealVector::new(genes))
    }
}
```

## Trace-Based Operators

### Trace Mutation

```rust,ignore
use fugue_evo::fugue_integration::trace_operators::TraceMutation;

let mutation = TraceMutation::new(mutation_probability);
let mutated_genome = mutation.mutate_via_trace(&genome, &bounds, &mut rng);
```

How it works:
1. Convert genome to trace
2. For each address, decide whether to resample
3. Resample selected addresses from prior (uniform within bounds)
4. Reconstruct genome from mutated trace

### Trace Crossover

```rust,ignore
use fugue_evo::fugue_integration::trace_operators::TraceCrossover;

let crossover = TraceCrossover::new(crossover_type);
let (child1, child2) = crossover.crossover_via_trace(&p1, &p2, &mut rng);
```

How it works:
1. Convert both parents to traces
2. For each address, select source parent
3. Merge into child traces
4. Reconstruct child genomes

## Effect Handlers

Fugue uses **effect handlers** (poutine-style) for program transformation. Fugue-evo provides evolution-specific handlers:

### Conditioning Handler

Interprets selection as conditioning:

```rust,ignore
use fugue_evo::fugue_integration::effect_handlers::ConditioningHandler;

// Selection biases toward high fitness
let handler = ConditioningHandler::new(fitness_function);
let selected_trace = handler.condition(trace, fitness_threshold);
```

### Resampling Handler

Implements mutation as partial resampling:

```rust,ignore
use fugue_evo::fugue_integration::effect_handlers::ResamplingHandler;

let handler = ResamplingHandler::new(resample_probability);
let mutated_trace = handler.resample(trace, &prior, &mut rng);
```

## Evolution Model

The full probabilistic evolution model:

```rust,ignore
use fugue_evo::fugue_integration::evolution_model::EvolutionModel;

let model = EvolutionModel::new()
    .with_prior(UniformPrior::new(&bounds))
    .with_likelihood(fitness_function)
    .with_mutation_kernel(GaussianKernel::new(sigma));

// One generation = one inference step
let posterior_population = model.step(prior_population, &mut rng);
```

## Advanced: Custom Trace Structures

For complex genomes, design meaningful trace structures:

```rust,ignore
// Neural network genome
impl EvolutionaryGenome for NeuralNetwork {
    fn to_trace(&self) -> Trace {
        let mut trace = Trace::new();

        // Layer structure
        for (layer_idx, layer) in self.layers.iter().enumerate() {
            for (neuron_idx, neuron) in layer.neurons.iter().enumerate() {
                // Weight addresses
                for (weight_idx, &weight) in neuron.weights.iter().enumerate() {
                    trace.insert(
                        addr!("layer", layer_idx, "neuron", neuron_idx, "weight", weight_idx),
                        weight,
                    );
                }
                // Bias address
                trace.insert(
                    addr!("layer", layer_idx, "neuron", neuron_idx, "bias"),
                    neuron.bias,
                );
            }
        }

        trace
    }
}
```

This enables:
- Layer-aware mutation (mutate one layer at a time)
- Structural crossover (exchange layers between parents)
- Hierarchical analysis of evolved networks

## See Also

- [Custom Genome Types]../how-to/custom-genome.md
- [Design Philosophy]./philosophy.md