Rust Interface#


Installation#

The BridgeStan Rust client is available on crates.io and via cargo:

cargo add bridgestan

A copy of the BridgeStan C++ sources is needed to compile models. This can be downloaded to ~/.bridgestan/ automatically if you use the download-bridgestan-src feature. Otherwise, it can be downloaded manually (see Getting Started).

Note that the system pre-requisites from the Getting Started guide are still required and will not be automatically installed by this method.

Example Program#

An example program is provided alongside the Rust crate in examples/example.rs:

Show example.rs
use bridgestan::{compile_model, open_library, BridgeStanError, Model};
use std::ffi::CString;
use std::path::{Path, PathBuf};

fn main() {
    // Set up logging - optional
    if std::env::var("RUST_LOG").is_err() {
        std::env::set_var("RUST_LOG", "bridgestan=info");
    }
    env_logger::init();

    // The path to the Stan model
    let path = Path::new(env!["CARGO_MANIFEST_DIR"])
        .parent()
        .unwrap()
        .join("test_models")
        .join("simple")
        .join("simple.stan");

    // You can manually set the BridgeStan src path or
    // automatically download it (but remember to
    // enable the download-bridgestan-src feature first)
    let bs_path: PathBuf = "..".into();
    // let bs_path = bridgestan::download_bridgestan_src().unwrap();

    // The path to the compiled model
    let path = compile_model(&bs_path, &path, &[], &[]).expect("Could not compile Stan model.");
    println!("Compiled model: {:?}", path);

    let lib = open_library(path).expect("Could not load compiled Stan model.");

    // The dataset as json
    let data = r#"{"N": 7}"#;
    let data = CString::new(data.to_string().into_bytes()).unwrap();

    // The seed is used in case the model contains a transformed data section
    // that uses rng functions.
    let seed = 42;

    let model = match Model::new(&lib, Some(data), seed) {
        Ok(model) => model,
        Err(BridgeStanError::ConstructFailed(msg)) => {
            panic!("Model initialization failed. Error message from Stan was {msg}")
        }
        Err(e) => {
            panic!("Unexpected error:\n{e}")
        }
    };

    let n_dim = model.param_unc_num();
    assert_eq!(n_dim, 7);
    let point = vec![1f64; n_dim];
    let mut gradient_out = vec![0f64; n_dim];
    let logp = model
        .log_density_gradient(&point[..], true, true, &mut gradient_out[..])
        .expect("Stan failed to evaluate the logp function.");
    println!("logp: {}\ngrad: {:?}", logp, gradient_out);
}

API Reference#

This is also available on docs.rs

crate bridgestan#

Variables

const VERSION: &str#

Functions

fn compile_model(bs_path: &Path, stan_file: &Path, stanc_args: &[&str], make_args: &[&str]) -> Result<PathBuf>#

Compile a Stan Model. Requires a path to the BridgeStan sources (can be downloaded with download_bridgestan_src if that feature is enabled), a path to the .stan file, and additional arguments for the Stan compiler and the make command.

fn download_bridgestan_src() -> Result<PathBuf>#

Download and unzip the BridgeStan source distribution for this version to ~/.bridgestan/bridgestan-$VERSION. Requires feature download-bridgestan-src.

fn open_library<P: AsRef<OsStr>>(path: P) -> Result<StanLibrary>#

Open a compiled Stan library.

The library should have been compiled with BridgeStan, with the same version as the Rust library.

Enums

enum BridgeStanError#

Error type for bridgestan interface

InvalidLibrary(LoadingError)#

The provided library could not be loaded.

BadLibraryVersion(String, String)#

The version of the Stan library does not match the version of the rust crate.

StanThreads(String)#

The Stan library could not be loaded because it was compiled without threading support.

InvalidString(Utf8Error)#

Stan returned a string that couldn’t be decoded using UTF8.

ConstructFailed(String)#

The model could not be instantiated, possibly because if incorrect data.

EvaluationFailed(String)#

Stan returned an error while computing the density.

SetCallbackFailed(String)#

Setting a print-callback failed.

ModelCompilingFailed(String)#

Compilation of the Stan model shared object failed.

DownloadFailed(String)#

Downloading BridgeStan’s C++ source code from GitHub failed.

Structs and Unions

struct Model<T: Borrow<StanLibrary>>#

A Stan model instance with data

Implementations

impl<T: Borrow<StanLibrary>> Model<T>#

Functions

fn info(&self) -> &CStr#

Return information about the compiled model

fn log_density(&self, theta_unc: &[f64], propto: bool, jacobian: bool) -> Result<f64>#

Compute the log of the prior times likelihood density

Drop jacobian determinant terms of the transformation from unconstrained to the constrained space if jacobian == false and drop terms of the density that do not depend on the parameters if propto == true.

fn log_density_gradient(&self, theta_unc: &[f64], propto: bool, jacobian: bool, grad: &mut [f64]) -> Result<f64>#

Compute the log of the prior times likelihood density and its gradient

Drop jacobian determinant terms of the transformation from unconstrained to the constrained space if jacobian == false and drop terms of the density that do not depend on the parameters if propto == true.

The gradient of the log density will be stored in grad.

Panics if the provided buffer has incorrect shape. The gradient buffer grad must have length self.param_unc_num().

fn log_density_hessian(&self, theta_unc: &[f64], propto: bool, jacobian: bool, grad: &mut [f64], hessian: &mut [f64]) -> Result<f64>#

Compute the log of the prior times likelihood density and its gradient and hessian.

Drop jacobian determinant terms of the transformation from unconstrained to the constrained space if jacobian == false and drop terms of the density that do not depend on the parameters if propto == true.

The gradient of the log density will be stored in grad, the hessian is stored in hessian.

Panics if the provided buffers have incorrect shapes. The gradient buffer grad must have length self.param_unc_num() and the hessian buffer must have length self.param_unc_num() * self.param_unc_num().

fn log_density_hessian_vector_product(&self, theta_unc: &[f64], v: &[f64], propto: bool, jacobian: bool, hvp: &mut [f64]) -> Result<f64>#

Compute the log of the prior times likelihood density the product of the Hessian and specified vector.

Drop jacobian determinant terms of the transformation from unconstrained to the constrained space if jacobian == false and drop terms of the density that do not depend on the parameters if propto == true.

The product of the Hessian of the log density and the provided vector will be stored in hvp.

Panics if the provided buffer has incorrect shape. The buffer hvp must have length self.param_unc_num().

fn name(&self) -> Result<&str>#

Return the name of the model or error if UTF decode fails

fn new<D: AsRef<CStr>>(lib: T, data: Option<D>, seed: u32) -> Result<Self>#

Create a new instance of the compiled Stan model.

Data is specified as a JSON file at the given path, a JSON string literal, or empty for no data. The seed is used if the model has RNG functions in the transformed data section.

fn new_rng(&self, seed: u32) -> Result<Rng<&StanLibrary>>#

Create a new Rng random number generator from the library underlying this model.

This can be used in param_constrain() when values from the generated quantities block are desired.

This instance can only be used with models from the same Stan library. Invalid usage will otherwise result in a panic.

fn param_constrain<R: Borrow<StanLibrary>>(&self, theta_unc: &[f64], include_tp: bool, include_gq: bool, out: &mut [f64], rng: Option<&mut Rng<R>>) -> Result<()>#

Map a point in unconstrained parameter space to the constrained space.

theta_unc must contain the point in the unconstrained parameter space.

If include_tp is set the output will also include the transformed parameters of the Stan model after the parameters. If include_gq is set, we also include the generated quantities at the very end.

Panics if the provided buffer has incorrect shape. The length of the out buffer must be self.param_num(include_tp, include_gq).

Panics if include_gq is set but no random number generator is provided.

fn param_names(&self, include_tp: bool, include_gq: bool) -> &str#

Return a comma-separated sequence of indexed parameter names, including the transformed parameters and/or generated quantities as specified.

The parameters are returned in the order they are declared. Multivariate parameters are return in column-major (more generally last-index major) order. Parameter indices are separated with periods (.). For example, a[3] is written a.3 and b[2, 3] as b.2.3. The numbering follows Stan and is indexed from 1.

If include_tp is set the names will also include the transformed parameters of the Stan model after the parameters. If include_gq is set, we also include the names of the generated quantities at the very end.

fn param_num(&self, include_tp: bool, include_gq: bool) -> usize#

Number of parameters in the model on the constrained scale.

Will also count transformed parameters (include_tp) and generated quantities (include_gq) if requested.

fn param_unc_names(&mut self) -> &str#

Return a comma-separated sequence of unconstrained parameters. Only parameters are unconstrained, so there are no unconstrained transformed parameters or generated quantities.

The parameters are returned in the order they are declared. Multivariate parameters are return in column-major (more generally last-index major) order. Parameter indices are separated with periods (.). For example, a[3] is written a.3 and b[2, 3] as b.2.3. The numbering follows Stan and is indexed from 1.

fn param_unc_num(&self) -> usize#

Return the number of parameters on the unconstrained scale.

In particular, this is the size of the slice required by the log_density functions.

fn param_unconstrain(&self, theta: &[f64], theta_unc: &mut [f64]) -> Result<()>#

Map a point in constrained parameter space to the unconstrained space.

fn param_unconstrain_json<S: AsRef<CStr>>(&self, json: S, theta_unc: &mut [f64]) -> Result<()>#

Map a constrained point in json format to the unconstrained space.

The JSON is expected to be in the JSON Format for CmdStan. A value for each parameter in the Stan program should be provided, with dimensions and size corresponding to the Stan program declarations.

fn ref_library(&self) -> &StanLibrary#

Return a reference to the underlying Stan library

impl<T: Borrow<StanLibrary> + Clone> Model<T>#

Functions

fn clone_library_ref(&self) -> T#

Return a clone of the underlying Stan library

Traits implemented

unsafe impl<T: Sync + Borrow<StanLibrary>> Sync for Model<T>#
unsafe impl<T: Send + Borrow<StanLibrary>> Send for Model<T>#
impl<T: Borrow<StanLibrary>> Drop for Model<T>#
struct Rng<T: Borrow<StanLibrary>>#

A random number generator for Stan models. This is only used in the Model::param_constrain() method of the model when requesting values from the generated quantities block. Different threads should use different instances.

Implementations

impl<T: Borrow<StanLibrary>> Rng<T>#

Functions

fn new(lib: T, seed: u32) -> Result<Self>#

Traits implemented

unsafe impl<T: Sync + Borrow<StanLibrary>> Sync for Rng<T>#
unsafe impl<T: Send + Borrow<StanLibrary>> Send for Rng<T>#
impl<T: Borrow<StanLibrary>> Drop for Rng<T>#
struct StanLibrary#

A loaded shared library for a Stan model

Implementations

impl StanLibrary#

Functions

unsafe fn set_print_callback(&mut self, callback: StanPrintCallback) -> Result<()>#

Provide a callback function to be called when Stan prints a message

Safety

The provided function must never panic.

Since the call is protected by a mutex internally, it does not need to be thread safe.

unsafe fn unload_library(mut self)#

Unload the Stan library.

Safety

There seem to be issues around unloading libraries in threaded code that are not fully understood: roualdes/bridgestan#111

Traits implemented

unsafe impl Send for StanLibrary#
unsafe impl Sync for StanLibrary#
impl Drop for StanLibrary#