use fugue::*;
use rand::thread_rng;
fn main() {
let _rng = thread_rng();
println!("=== Building Complex Models with Fugue ===\n");
println!("1. Basic prob! Macro Usage");
println!("-------------------------");
let _simple_model = prob!(
let x <- sample(addr!("x"), Normal::new(0.0, 1.0).unwrap());
let y <- sample(addr!("y"), Normal::new(x, 0.5).unwrap());
let sum = x + y; pure(sum)
);
println!("✅ Created simple model with prob! macro");
println!(" - Uses <- for probabilistic binding");
println!(" - Uses = for regular assignments");
println!(" - Returns final value with pure()");
println!();
println!("2. Plate Notation for Independent Samples");
println!("------------------------------------------");
let _vector_model = plate!(i in 0..5 => {
sample(addr!("sample", i), Normal::new(0.0, 1.0).unwrap())
});
println!("✅ Created vectorized model with {} samples", 5);
let observations = [1.2, -0.5, 2.1, 0.8, -1.0];
let n_obs = observations.len();
let _observed_model = plate!(i in 0..n_obs => {
observe(addr!("obs", i), Normal::new(0.0, 1.0).unwrap(), observations[i])
});
println!("✅ Created observation model for {} data points", n_obs);
println!(" - plate! automatically handles indexing");
println!(" - Each iteration gets unique address");
println!();
println!("3. Hierarchical Models with Scoped Addresses");
println!("--------------------------------------------");
let _hierarchical_model = prob!(
let global_mu <- sample(addr!("global_mu"), Normal::new(0.0, 10.0).unwrap());
let group_mu <- sample(scoped_addr!("group", "mu", "{}", 0),
Normal::new(global_mu, 1.0).unwrap());
pure((global_mu, group_mu))
);
println!("✅ Created hierarchical model with scoped addresses");
println!(" - scoped_addr! creates organized parameter names");
println!(" - Hierarchical parameter structure");
println!();
println!("4. Model Composition with Functions");
println!("----------------------------------");
fn create_normal_component(name: &str, mean: f64, std: f64) -> Model<f64> {
sample(addr!(name), Normal::new(mean, std).unwrap())
}
let _composition_model = prob! {
let param1 <- create_normal_component("param1", 0.0, 1.0);
let param2 <- create_normal_component("param2", 2.0, 0.5);
let combined = param1 * param2;
pure(combined)
};
println!("✅ Created composed model with reusable components");
println!(" - Functions return Model<T> for reuse");
println!(" - Clean separation of concerns");
println!();
println!("5. Sequential Dependencies");
println!("-------------------------");
let _sequential_model = prob! {
let states <- plate!(t in 0..3 => {
sample(addr!("x", t), Normal::new(0.0, 1.0).unwrap())
.bind(move |x_t| {
observe(addr!("y", t), Normal::new(x_t, 0.5).unwrap(), 1.0 + t as f64)
.map(move |_| x_t)
})
});
pure(states)
};
println!("✅ Created sequential model with observations");
println!(" - Each time step depends on previous");
println!(" - Observations condition the model");
println!();
println!("6. Mixture Models");
println!("----------------");
let _mixture_model = prob! {
let component <- sample(addr!("component"), Bernoulli::new(0.3).unwrap());
let mu = if component { -2.0 } else { 2.0 };
let x <- sample(addr!("x"), Normal::new(mu, 1.0).unwrap());
pure((component, x))
};
println!("✅ Created mixture model with 2 components");
println!(" - Boolean component selection");
println!(" - Natural if/else branching");
println!();
println!("7. Advanced Address Management");
println!("-----------------------------");
let _neural_layer_model = plate!(layer in 0..3 => {
let layer_size = match layer {
0 => 4,
1 => 8,
2 => 1,
_ => 1,
};
plate!(i in 0..layer_size => {
sample(
scoped_addr!("layer", "weight", "{}_{}", layer, i),
Normal::new(0.0, 0.1).unwrap()
)
})
});
println!("✅ Created neural network parameter structure");
println!(" - Systematic parameter organization");
println!(" - Hierarchical scoping prevents conflicts");
println!();
println!("8. Bayesian Linear Regression");
println!("----------------------------");
let x_data = [1.0, 2.0, 3.0, 4.0, 5.0];
let y_data = [2.1, 3.9, 6.2, 8.1, 9.8];
let n = x_data.len();
let _regression_model = prob! {
let intercept <- sample(addr!("intercept"), Normal::new(0.0, 10.0).unwrap());
let slope <- sample(addr!("slope"), Normal::new(0.0, 10.0).unwrap());
let precision <- sample(addr!("precision"), Gamma::new(1.0, 1.0).unwrap());
let sigma = (1.0 / precision).sqrt();
let _likelihood <- plate!(i in 0..n => {
let predicted = intercept + slope * x_data[i];
observe(addr!("y", i), Normal::new(predicted, sigma).unwrap(), y_data[i])
});
pure((intercept, slope, sigma))
};
println!("✅ Created Bayesian linear regression model");
println!(" - Proper priors for all parameters");
println!(" - Vectorized likelihood computation");
println!();
println!("9. Multi-level Hierarchy");
println!("-----------------------");
let _multilevel_model = prob!(
let pop_mean <- sample(addr!("pop_mean"), Normal::new(0.0, 10.0).unwrap());
let _pop_precision <- sample(addr!("pop_precision"), Gamma::new(2.0, 0.5).unwrap());
let group_mean <- sample(scoped_addr!("group", "mean", "{}", 0),
Normal::new(pop_mean, 1.0).unwrap());
pure((pop_mean, group_mean))
);
println!("✅ Created hierarchical model structure");
println!(" - Population -> Groups hierarchy");
println!(" - Demonstrates scoped addressing");
println!();
println!("=== All model composition patterns demonstrated! ===");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_model_composition() {
let _simple = prob! {
let x <- sample(addr!("test_x"), Normal::new(0.0, 1.0).unwrap());
pure(x)
};
let _plate_model = plate!(i in 0..3 => {
sample(addr!("plate_test", i), Normal::new(0.0, 1.0).unwrap())
});
let addr1 = scoped_addr!("test", "param");
let addr2 = scoped_addr!("test", "param", "{}", 42);
assert_ne!(addr1.as_str(), addr2.as_str());
assert!(addr2.as_str().contains("42"));
let _hierarchical = prob! {
let global <- sample(addr!("global"), Normal::new(0.0, 1.0).unwrap());
let locals <- plate!(i in 0..2 => {
sample(scoped_addr!("local", "param", "{}", i),
Normal::new(global, 0.1).unwrap())
});
pure((global, locals))
};
}
}