ferric 0.1.3

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.

Usage

Add this to your Cargo.toml:

[dependencies]
ferric = "0.1"

Example

use std::time::Instant;
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 && !rain { 0.9 }
        else if !sprinkler && rain { 0.8 }
        else { 0.0 }
    );

    observe grass_wet;
    query rain;
    query sprinkler;
}

fn main() {
    let model = grass::Model {grass_wet: true};
    let mut num_rain = 0;
    let mut num_sprinkler = 0;
    let num_samples = 100000;
    let start = Instant::now();
    for sample in model.sample_iter().take(num_samples) {
        if sample.rain {
            num_rain += 1;
        }
        if sample.sprinkler {
            num_sprinkler += 1;
        }
    }
    let num_samples = num_samples as f64;
    println!(
        "posterior: rain = {} sprinkler = {}. Elapsed {} millisec for {} samples",
        (num_rain as f64) / num_samples,
        (num_sprinkler as f64) / num_samples,
        start.elapsed().as_millis(),
        num_samples,
    );
}

License

How it works

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

  • Define random variables and their generative distributions.
  • Mark variables with observe to indicate data the model will be conditioned on.
  • Mark variables with query to indicate values you want returned in posterior samples.

After expansion, the macro produces a module containing a Model type. You construct the model by supplying values for the observed fields (for example let model = grass::Model { grass_wet: true };). Use the model's sampling API (for example model.sample_iter()) to draw samples from the posterior; each sample contains the queried variables as fields that you can inspect to estimate posterior probabilities.

Refer to the Example above for a canonical, end-to-end usage sample.

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.

🛠 Development & Testing

1. Install Rust

If you haven't installed Rust yet, follow the official installation instructions here: Install Rust and Cargo Once installed, ensure your toolchain is up to date:

rustup update stable

2. Install Coverage Tools

We use cargo-llvm-cov for accurate, source-based code coverage. You will need to install the tool and the required LLVM components:

# Install the LLVM tools component
rustup component add llvm-tools-preview

# Install cargo-llvm-cov
cargo install cargo-llvm-cov --locked

3. Running Tests

To run the standard test suite:

cargo test

4. Generating Coverage Reports

You can generate coverage data in multiple formats to match our CI environment or for local inspection:

  • View in Browser (HTML):
cargo +nightly llvm-cov --workspace --doctests --html --open
  • Generate LCOV (for Coveralls/IDEs):
cargo +nightly llvm-cov --workspace --doctests --lcov --output-path lcov.info
  • Console Summary:
cargo +nightly llvm-cov --workspace --doctests

5. IDE Integration (Optional)

If you use VS Code, install the Coverage Gutters extension. After generating an lcov.info file, click the Watch button in the bottom status bar to see line-by-line coverage (green/red highlights) directly in your editor.

Developer

If you're working on the Ferric codebase itself and need to expand the make_model! macro for debugging or inspection, install the following developer tools:

# Install the rust source component (required by some expansion tools)
rustup component add rust-src

# Install cargo-expand to make `cargo expand` available
cargo install --locked cargo-expand

Then you can expand the grass example (or any example) with:

# Expand the `grass` example and print expanded Rust to stdout
cargo expand --example grass --package ferric

# Or save the expanded output to a file for easier reading
cargo expand --example grass --package ferric > expanded_grass.rs

Note: cargo expand is a separate tool (provided by the cargo-expand crate) and is not included in the default Cargo installation.

Publishing to crates.io

Follow this updated sequence to publish the crate and any internal dependencies (recommended):

    1. Run tests & build locally:
cargo test --workspace
cargo build --release
    1. Ensure versions are synced and path deps include version:
    • In ferric-macros/Cargo.toml set the release version (e.g. 0.1.3).
    • In ferric/Cargo.toml reference the local path and published version:
ferric-macros = { path = "../ferric-macros", version = "0.1.3" }
    1. Obtain crates.io authentication (one-time or CI):
cargo login <CRATES_IO_TOKEN>
# or (for CI/session)
export CARGO_REGISTRY_TOKEN=<CRATES_IO_TOKEN>
    1. Publish internal/proc-macro crates first (if any). For ferric-macros:
cargo package --manifest-path ferric-macros/Cargo.toml
cargo publish --manifest-path ferric-macros/Cargo.toml --dry-run
cargo publish --manifest-path ferric-macros/Cargo.toml
- Wait a short time (30–60s) after publishing for the crates.io index to update.
    1. Package & dry-run the main crate, then publish:
cargo package --manifest-path ferric/Cargo.toml
cargo publish --manifest-path ferric/Cargo.toml --dry-run
cargo publish --manifest-path ferric/Cargo.toml
- If `cargo package` fails with a dependency version error, it means a required dependency version is not yet available on crates.io — publish that dependency first.
    1. Tag and push git commits:
git tag -a v0.1.3 -m "Release v0.1.3"
git push origin HEAD
git push origin --tags
    1. Post-release housekeeping:
    • Move Unreleased changes into the released section of CHANGELOG.md and add a new Unreleased heading.
    • Bump the next development version if you follow a X.Y.Z-dev workflow.
    • Publish docs or update the website and announce the release.

Notes and tips:

  • Always publish internal dependencies (proc macros, helper crates) before crates that depend on them.
  • Keep path dependencies during development but include a version so cargo package validates.
  • If the main crate cannot find the new dependency version immediately after publishing, wait a short time and retry.
  • Verify readme, license, and repository fields in each Cargo.toml to ensure crates.io displays metadata correctly.