ferric 0.1.4

A Probablistic Programming Language with a declarative syntax for random variables.
Documentation

Github Actions Tests crates.io Coverage Status

Ferric

A Probabilistic Programming Language in Rust with a declarative syntax.

Installation

Add this to your Cargo.toml:

[dependencies]
ferric = "0.1"

How it works

Ferric's make_model! macro declares a Bayesian model and the relationships between random variables. Inside the macro you:

  • Define random variables and their distributions using let name : Type ~ Distribution;.
  • Mark variables with observe to condition the model on observed data.
  • Mark variables with query to include variables in posterior samples.

After expansion the macro produces a module containing a Model struct. Construct the model by supplying values for the observed fields, then draw from the posterior using one of the two sampling strategies below.

Sampling strategies

Rejection sampling — sample_iter

Valid for discrete observations only. Each call to next() draws from the prior and discards the sample if the discrete observations don't match. Every returned Sample is an exact draw from the posterior.

use ferric::make_model;

make_model! {
    mod grass;
    use ferric::distributions::Bernoulli;

    let rain       : bool ~ Bernoulli::new(0.2);
    let sprinkler  : bool ~ if rain { Bernoulli::new(0.01) } else { Bernoulli::new(0.4) };
    let grass_wet  : bool ~ Bernoulli::new(
        if sprinkler && rain  { 0.99 }
        else if sprinkler     { 0.90 }
        else if rain          { 0.80 }
        else                  { 0.00 }
    );

    observe grass_wet;
    query rain;
    query sprinkler;
}

fn main() {
    let model = grass::Model { grass_wet: true };
    let num_samples = 100_000;
    let mut num_rain = 0;
    let mut num_sprinkler = 0;

    for sample in model.sample_iter().take(num_samples) {
        if sample.rain      { num_rain      += 1; }
        if sample.sprinkler { num_sprinkler += 1; }
    }

    println!(
        "P(rain | wet) ≈ {:.3}   P(sprinkler | wet) ≈ {:.3}",
        num_rain      as f64 / num_samples as f64,
        num_sprinkler as f64 / num_samples as f64,
    );
}

Weighted sampling — weighted_sample_iter

Valid for any model, including those with continuous observations. Each call to next() draws from the prior and returns a WeightedSample { log_weight, sample } pair where log_weight is the sum of the log-likelihoods of all observations given the draw.

Use ferric::weighted_mean and ferric::weighted_std to compute posterior summaries from the weighted samples.

use ferric::make_model;

make_model! {
    mod signal_estimation;
    use ferric::distributions::Normal;

    // prior: true signal unknown
    let true_signal     : f64 ~ Normal::new(0.0, 2.0);
    // likelihood: noisy sensor reading
    let sensor_reading  : f64 ~ Normal::new(true_signal, 1.0);

    observe sensor_reading;
    query  true_signal;
}

fn main() {
    let model = signal_estimation::Model { sensor_reading: 2.5 };
    let num_samples = 100_000;

    let mut signal_vals  = Vec::with_capacity(num_samples);
    let mut log_weights  = Vec::with_capacity(num_samples);

    for ws in model.weighted_sample_iter().take(num_samples) {
        signal_vals.push(ws.sample.true_signal);
        log_weights.push(ws.log_weight);
    }

    let post_mean = ferric::weighted_mean(&signal_vals, &log_weights);
    let post_std  = ferric::weighted_std(&signal_vals,  &log_weights);

    println!("posterior: true_signal = {:.3} ± {:.3}", post_mean, post_std);
    // Analytical answer: mean ≈ 2.0, std ≈ 0.894
}

The WeightedSample type nests query variables under .sample.* and exposes the metadata separately at .log_weight, so there is no naming conflict even if a query variable is named log_weight.

Available distributions

Distribution Domain Parameters
Bernoulli bool p ∈ [0, 1]
Dirac<T> T deterministic value
Empirical<T> T finite weighted support
Categorical usize non-negative category weights
Binomial u64 n ≥ 1, p ∈ (0, 1)
BetaBinomial u64 n ≥ 1, alpha > 0, beta > 0
DiscreteUniform i64 a ≤ b
Geometric u64 p ∈ (0, 1]
Hypergeometric u64 population, successes, draws
NegativeBinomial u64 r > 0, p ∈ (0, 1]
Poisson u64 rate > 0
Uniform f64 low < high
Exponential f64 rate > 0
Normal f64 mean, std_dev > 0
LogNormal f64 mu, sigma > 0
Beta f64 alpha > 0, beta > 0
Cauchy f64 median, scale > 0
Chi f64 k > 0
ChiSquared f64 k > 0
Erlang f64 integer k ≥ 1, lambda > 0
FisherF f64 d1 > 0, d2 > 0
Frechet f64 location, scale > 0, shape > 0
Gamma f64 shape > 0, scale > 0
Gumbel f64 mu, beta > 0
HalfNormal f64 sigma > 0
InverseGamma f64 alpha > 0, beta > 0
InverseGaussian f64 mu > 0, lambda > 0
Laplace f64 mu, b > 0
Logistic f64 mu, s > 0
Pareto f64 x_m > 0, alpha > 0
Rayleigh f64 sigma > 0
StudentT f64 df > 0
Triangular f64 a ≤ c ≤ b, a < b
Weibull f64 lambda > 0, k > 0
Dirichlet Vec<f64> concentration vector, each alpha_i > 0
Multinomial Vec<u64> n ≥ 1, probability vector
MultivariateNormal nalgebra::DVector<f64> mean vector, SPD covariance matrix
MultivariateStudentT nalgebra::DVector<f64> mean vector, SPD scale matrix, df > 0
MatrixNormal nalgebra::DMatrix<f64> mean matrix, SPD row and column covariance matrices
Wishart nalgebra::DMatrix<f64> df > p - 1, SPD scale matrix

Documentation

Ferric's API documentation is published automatically by docs.rs when a new crate version is published to crates.io. There is no separate official Rust documentation host to push to manually; the canonical Rust crate docs live on docs.rs.

For project docs beyond API reference, keep them in this repository unless they grow into a distinct website. A separate Ferric-AI/docs repo would add coordination overhead right now, while docs.rs plus this README gives users the normal Rust discovery path.

License

Licensed under either of

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.

See CONTRIBUTING.md for development setup, coverage tools, macro expansion, and publishing instructions.