From bc2f2a809cf2649160f08c19c58b084713f63fb0 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Tue, 30 May 2023 09:51:04 -0400 Subject: [PATCH 01/17] Start to clean up Rust docs --- CONTRIBUTING.md | 3 +++ README.md | 5 +++++ rust/README.md | 2 +- rust/examples/{make_model.rs => example.rs} | 0 4 files changed, 9 insertions(+), 1 deletion(-) rename rust/examples/{make_model.rs => example.rs} (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 31f06a83..56cadf7b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -73,6 +73,9 @@ We use [Gnu make](https://www.gnu.org/software/make/) for builds. If you have p * Julia code is formatted using [JuliaFormatter](https://github.com/domluna/JuliaFormatter.jl). +### Rust development + +* Rust development is based on `cargo`, which should handle dependencies, testing, and formatting. ## Proposing a new interface language diff --git a/README.md b/README.md index 39492a2b..35ae5f10 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,8 @@ in Python, Julia, R, and C. * From C: [`example.c`](c-example/example.c) +* From Rust: [`example.rs`](rust/examples/example.c) + Examples of other functionality can be found in the `test` folder for each interface. ## Acknowledgements @@ -81,3 +83,6 @@ API, which in turn was derived from Thanks to Sebastian Weber (GitHub [@wds15](https://github.com/wds15)) for enabling multi-threaded calls from Julia to a single Stan model instance. + +Thanks to Adrian Seyboldt (GitHub [@aseyboldt](https://github.com/aseyboldt)) +for providing the Rust wrapper. diff --git a/rust/README.md b/rust/README.md index afdebc2d..bdb58c88 100644 --- a/rust/README.md +++ b/rust/README.md @@ -11,7 +11,7 @@ or Python bindings. ## Usage: ```rust -use std::ffi::{OsStr, CString}; +use std::ffi::CString; use std::path::Path; use bridgestan::{BridgeStanError, Model, open_library}; diff --git a/rust/examples/make_model.rs b/rust/examples/example.rs similarity index 100% rename from rust/examples/make_model.rs rename to rust/examples/example.rs From 958682e00e7a427bd823c8b564f31df806263237 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Thu, 15 Jun 2023 10:10:29 -0400 Subject: [PATCH 02/17] Update CI --- .github/workflows/main.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 17deebe0..349b34d8 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -275,5 +275,5 @@ jobs: run: | cargo clippy cargo fmt --check - cargo run --example=make_model - cargo test --verbose \ No newline at end of file + cargo run --example=example + cargo test --verbose From 5dd6e2e9dd50f54794bf71499569e649f2903c17 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Thu, 15 Jun 2023 15:25:28 -0400 Subject: [PATCH 03/17] Update release action (needs testing) --- .github/workflows/release.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index bef1b8b4..18921314 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -33,12 +33,19 @@ jobs: - name: Set up Julia uses: julia-actions/setup-julia@v1 + - name: Install LLVM and Clang + uses: KyleMayes/install-llvm-action@v1 + with: + version: "15.0" + directory: ${{ runner.temp }}/llvm + - name: Update version numbers if: ${{ !inputs.is_rerun }} run: | sed -i 's/Version:.*/Version: ${{ inputs.new_version }}/' R/DESCRIPTION sed -i 's/version = .*/version = "${{ inputs.new_version }}"/' julia/Project.toml sed -i 's/__version__ = .*/__version__ = "${{ inputs.new_version }}"/' python/bridgestan/__version.py + sed -i 's/^version = .*/version = "${{ inputs.new_version }}"/' rust/Cargo.toml sed -i 's/#define BRIDGESTAN_MAJOR .*/#define BRIDGESTAN_MAJOR '"$(echo ${{ inputs.new_version }} | cut -d. -f1)"'/' src/version.hpp sed -i 's/#define BRIDGESTAN_MINOR .*/#define BRIDGESTAN_MINOR '"$(echo ${{ inputs.new_version }} | cut -d. -f2)"'/' src/version.hpp @@ -103,6 +110,14 @@ jobs: packages_dir: python/dist/ skip_existing: true + - name: Publish Rust crate + if: ${{ !inputs.dry_run }} + run: cargo publish --token ${CRATES_TOKEN} + env: + LIBCLANG_PATH: ${{ runner.temp }}/llvm/lib + LLVM_CONFIG_PATH: ${{ runner.temp }}/llvm/bin/llvm-config + CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} + - name: Create JuliaRegistration comment if: ${{ !inputs.dry_run }} uses: peter-evans/commit-comment@v2 From b12d1c671fcfe6e184d8bf25b81af7ed1627a17d Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Thu, 15 Jun 2023 15:43:16 -0400 Subject: [PATCH 04/17] Sketch docs --- .github/workflows/release.yaml | 5 +++- docs/languages.rst | 1 + docs/languages/rust.rst | 44 ++++++++++++++++++++++++++++++++++ rust/README.md | 6 +++-- 4 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 docs/languages/rust.rst diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 18921314..a41b8cb4 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -112,8 +112,11 @@ jobs: - name: Publish Rust crate if: ${{ !inputs.dry_run }} - run: cargo publish --token ${CRATES_TOKEN} + run: | + cd rust/ + cargo publish --token ${CRATES_TOKEN} env: + # clang is necessary unless we want to do --no-verify LIBCLANG_PATH: ${{ runner.temp }}/llvm/lib LLVM_CONFIG_PATH: ${{ runner.temp }}/llvm/bin/llvm-config CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} diff --git a/docs/languages.rst b/docs/languages.rst index 3dc0c64b..e1d2008c 100644 --- a/docs/languages.rst +++ b/docs/languages.rst @@ -12,6 +12,7 @@ written in C. languages/python languages/julia languages/r + languages/rust languages/c-api diff --git a/docs/languages/rust.rst b/docs/languages/rust.rst new file mode 100644 index 00000000..32d48136 --- /dev/null +++ b/docs/languages/rust.rst @@ -0,0 +1,44 @@ +Rust Interface +============== + +`See the BridgeStan Crate documentation on docs.rs `__ + +---- + +Installation +------------ + +The BridgeStan Rust client is availble on crates.io and via ``cargo``: + +.. code-block:: shell + + cargo add bridgestan + +To build and use BridgeStan models, a copy of the BridgeStan C++ source code +is required. Please follow the :doc:`Getting Started guide <../getting-started>` +or use the Rust client in tandem with an interface such as :doc:`Python <./python>` +which automates this process. + +Example Program +--------------- + +An example program is provided alongside the Rust crate in ``examples/example.rs``: + +.. raw:: html + +
+ Show example.rs + + +.. literalinclude:: ../../rust/examples/example.rs + :language: Rust + +.. raw:: html + +
+ + +API Reference +------------- + +See docs.rs for the full API reference: diff --git a/rust/README.md b/rust/README.md index bdb58c88..6bed6e44 100644 --- a/rust/README.md +++ b/rust/README.md @@ -1,10 +1,12 @@ # BridgeStan from Rust -This is a Rust wrapper for [BridgeStan](https://roualdes.github.io/bridgestan/latest/). +[*View the BridgeStan documentation on Github Pages*](https://roualdes.github.io/bridgestan/latest/languages/rust.html). + +This is a Rust wrapper for [BridgeStan](https://github.com/roualdes/bridgestan). It relies on [`bindgen`](https://docs.rs/bindgen/) and [`libloading`](https://docs.rs/libloading/). -The Rust wrapper does not have any functionality to compile Stan models. +The Rust wrapper does not currently have any functionality to compile Stan models. Compiled shared libraries need to be built manually using `make` or with the Julia or Python bindings. From b560e51ec6af1b7fa37da8ef2b5106330460dd69 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Thu, 15 Jun 2023 15:46:03 -0400 Subject: [PATCH 05/17] Add non-working link --- docs/languages/rust.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/languages/rust.rst b/docs/languages/rust.rst index 32d48136..8a509cec 100644 --- a/docs/languages/rust.rst +++ b/docs/languages/rust.rst @@ -41,4 +41,4 @@ An example program is provided alongside the Rust crate in ``examples/example.rs API Reference ------------- -See docs.rs for the full API reference: +See docs.rs for the full API reference: ``__ From 04020d1bef1382091e73184bf44c554394a0c564 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Thu, 15 Jun 2023 15:51:57 -0400 Subject: [PATCH 06/17] Clean up some docs, un-mark Rng as send/sync --- rust/src/bs_safe.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/rust/src/bs_safe.rs b/rust/src/bs_safe.rs index 1823165a..7e3a52e6 100644 --- a/rust/src/bs_safe.rs +++ b/rust/src/bs_safe.rs @@ -143,15 +143,15 @@ pub struct Model> { unsafe impl> Sync for Model {} unsafe impl> Send for Model {} -/// A random number generator for Stan +/// A random number generator for Stan models. +/// This is only used in the `param_contrain` method +/// of the model when requesting values from the `generated quantities` block. +/// Different threads should use different instances. pub struct Rng> { rng: NonNull, lib: T, } -unsafe impl> Sync for Rng {} -unsafe impl> Send for Rng {} - impl> Drop for Rng { fn drop(&mut self) { unsafe { @@ -264,6 +264,9 @@ impl> Model { self.lib.borrow() } + /// 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. pub fn new_rng(&self, seed: u32) -> Result> { Rng::new(self.ref_library(), seed) } From f84576b0e4c34a7e67558752c50e7be43fe0e055 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Fri, 16 Jun 2023 10:52:42 -0400 Subject: [PATCH 07/17] Re-mark rng as send+sync --- rust/src/bs_safe.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rust/src/bs_safe.rs b/rust/src/bs_safe.rs index 7e3a52e6..6cf835f1 100644 --- a/rust/src/bs_safe.rs +++ b/rust/src/bs_safe.rs @@ -152,6 +152,12 @@ pub struct Rng> { lib: T, } +// Use sites require exclusive reference which guarantees +// that the rng is not used in multiple threads concurrently. +unsafe impl> Sync for Rng {} +unsafe impl> Send for Rng {} + + impl> Drop for Rng { fn drop(&mut self) { unsafe { From 3cbb3091e15a5d931bfec51ff4e3e2d8f93a2617 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Fri, 16 Jun 2023 12:19:26 -0400 Subject: [PATCH 08/17] cargo fmt --- rust/src/bs_safe.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust/src/bs_safe.rs b/rust/src/bs_safe.rs index 6cf835f1..092a1be2 100644 --- a/rust/src/bs_safe.rs +++ b/rust/src/bs_safe.rs @@ -157,7 +157,6 @@ pub struct Rng> { unsafe impl> Sync for Rng {} unsafe impl> Send for Rng {} - impl> Drop for Rng { fn drop(&mut self) { unsafe { From 11f3389f36e5e1d439fe16683c054bc95796fff6 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Mon, 26 Jun 2023 14:58:56 -0400 Subject: [PATCH 09/17] three languages -> four --- docs/languages.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/languages.rst b/docs/languages.rst index e1d2008c..139d7026 100644 --- a/docs/languages.rst +++ b/docs/languages.rst @@ -2,7 +2,7 @@ Language Interfaces =================== -BridgeStan currently has clients in three languages, a public C API +BridgeStan currently has clients in four languages, a public C API which underlies all the clients, and an example of a standalone program written in C. From 2e6fbc6ac0aaedf45a8723cf5e70397f3dcc4ddb Mon Sep 17 00:00:00 2001 From: Adrian Seyboldt Date: Fri, 16 Jun 2023 14:15:11 -0500 Subject: [PATCH 10/17] Improve rust API docs --- rust/README.md | 7 +++- rust/src/bs_safe.rs | 95 ++++++++++++++++++++++++++------------------- 2 files changed, 61 insertions(+), 41 deletions(-) diff --git a/rust/README.md b/rust/README.md index 6bed6e44..13984f20 100644 --- a/rust/README.md +++ b/rust/README.md @@ -2,9 +2,12 @@ [*View the BridgeStan documentation on Github Pages*](https://roualdes.github.io/bridgestan/latest/languages/rust.html). -This is a Rust wrapper for [BridgeStan](https://github.com/roualdes/bridgestan). +This is a Rust wrapper for [BridgeStan](https://github.com/roualdes/bridgestan). It +allows users to evaluate the log likelihood and related functions for Stan models +natively from Rust. -It relies on [`bindgen`](https://docs.rs/bindgen/) and [`libloading`](https://docs.rs/libloading/). +Internally, it relies on [`bindgen`](https://docs.rs/bindgen/) and +[`libloading`](https://docs.rs/libloading/). The Rust wrapper does not currently have any functionality to compile Stan models. Compiled shared libraries need to be built manually using `make` or with the Julia diff --git a/rust/src/bs_safe.rs b/rust/src/bs_safe.rs index 092a1be2..75ffea68 100644 --- a/rust/src/bs_safe.rs +++ b/rust/src/bs_safe.rs @@ -80,18 +80,25 @@ pub struct LoadingError(#[from] libloading::Error); #[derive(Error, Debug)] #[non_exhaustive] pub enum BridgeStanError { + /// The prodided library could not be loaded. #[error(transparent)] InvalidLibrary(#[from] LoadingError), + /// The version of the Stan library does not match the version of the rust crate. #[error("Bad Stan library version: Got {0} but expected {1}")] BadLibraryVersion(String, String), + /// The Stan library could not be loaded because it was compiled without threading support. #[error("The Stan library was compiled without threading support. Config was {0}")] StanThreads(String), + /// Stan returned a string that couldn't be decoded using UTF8. #[error("Failed to decode string to UTF8")] InvalidString(#[from] Utf8Error), + /// The model could not be instanciated, possibly because if incorrect data. #[error("Failed to construct model: {0}")] ConstructFailed(String), + /// Stan returned an error while computing the density. #[error("Failed during evaluation: {0}")] EvaluationFailed(String), + /// Setting a print-callback failed. #[error("Failed to set a print-callback: {0}")] SetCallbackFailed(String), } @@ -209,7 +216,7 @@ impl<'lib> ErrorMsg<'lib> { /// Return the error message as a String. /// - /// Panics if there was no error message. + /// *Panics* if there was no error message. fn message(&self) -> String { NonNull::new(self.msg) .map(|msg| { @@ -270,8 +277,13 @@ impl> Model { } /// 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. pub fn new_rng(&self, seed: u32) -> Result> { Rng::new(self.ref_library(), seed) } @@ -293,14 +305,14 @@ impl> Model { /// /// The parameters are returned in the order they are declared. /// Multivariate parameters are return in column-major (more - /// generally last-index major) order. Parameters 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. + /// 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. /// - /// # Arguments - /// - /// `include_tp`: Include transformed parameters - /// `include_gp`: Include generated quantities + /// 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. pub fn param_names(&self, include_tp: bool, include_gq: bool) -> &str { let cstr = unsafe { CStr::from_ptr(self.ffi_lib().bs_param_names( @@ -319,7 +331,7 @@ impl> Model { /// /// The parameters are returned in the order they are declared. /// Multivariate parameters are return in column-major (more - /// generally last-index major) order. Parameters are separated with + /// 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. pub fn param_unc_names(&mut self) -> &str { @@ -330,7 +342,9 @@ impl> Model { } /// Number of parameters in the model on the constrained scale. - /// Will also count transformed parameters and generated quantities if requested + /// + /// Will also count transformed parameters (`include_tp`) and generated + /// quantities (`include_gq`) if requested. pub fn param_num(&self, include_tp: bool, include_gq: bool) -> usize { unsafe { self.ffi_lib() @@ -341,6 +355,7 @@ impl> Model { } /// Return the number of parameters on the unconstrained scale. + /// /// In particular, this is the size of the slice required by the log_density functions. pub fn param_unc_num(&self) -> usize { unsafe { self.ffi_lib().bs_param_unc_num(self.model.as_ptr()) } @@ -350,8 +365,9 @@ impl> Model { /// Compute the log of the prior times likelihood density /// - /// Drop jacobian determinant terms if `jacobian == false` and - /// drop constant terms of the density if `propto == true`. + /// 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`. pub fn log_density(&self, theta_unc: &[f64], propto: bool, jacobian: bool) -> Result { let n = self.param_unc_num(); assert_eq!( @@ -382,9 +398,14 @@ impl> Model { /// Compute the log of the prior times likelihood density and its gradient /// - /// Drop jacobian determinant terms if `jacobian == false` and - /// drop constant terms of the density if `propto == true`. - /// The gradient of the log density is stored in `grad`. + /// 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()`. pub fn log_density_gradient( &self, theta_unc: &[f64], @@ -426,12 +447,18 @@ impl> Model { } } - /// Compute the log of the prior times likelihood density and gradient and hessian. + /// 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`. /// - /// Drop jacobian determinant terms if `jacobian == false` and - /// drop constant terms of the density if `propto == true`. - /// The gradient of the log density is stored in `grad`, the + /// 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()`. pub fn log_density_hessian( &self, theta_unc: &[f64], @@ -480,20 +507,19 @@ impl> Model { } } - /// Map a point in unconstrained parameter space to the constrained space - /// - /// # Arguments + /// Map a point in unconstrained parameter space to the constrained space. /// - /// `theta_unc`: The point in the unconstained parameter space. + /// `theta_unc` must contain the point in the unconstained parameter space. /// - /// `include_tp`: Include transformed parameters + /// 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. /// - /// `include_gq`: Include generated quantities + /// The length of the provided buffer `out` must have length + /// `self.param_num(include_tp, include_gq)`, this function panics + /// if that is not the case. /// - /// `out`: Array of length `self.param_num(include_tp, include_gp)`, where - /// the constrained parameters will be stored. - /// - /// `rng`: A Stan random number generator. Has to be provided if `include_gp`. + /// A Stan random number generator has to be provided if `include_gp`. pub fn param_constrain>( &self, theta_unc: &[f64], @@ -549,16 +575,7 @@ impl> Model { } } - /// Set the sequence of unconstrained parameters based on the - /// specified constrained parameters, and return a return code of 0 - /// for success and -1 for failure. Parameter order is as declared - /// in the Stan program, with multivariate parameters given in - /// last-index-major order. - /// - /// # Arguments - /// `theta`: The vector of constrained parameters - /// - /// `theta_unc` Vector of unconstrained parameters + /// Map a point in constrained parameter space to the unconstrained space. pub fn param_unconstrain(&self, theta: &[f64], theta_unc: &mut [f64]) -> Result<()> { assert_eq!( theta_unc.len(), From 8ff3342d1d0780d1f6e757ffff899bf5d5db92be Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Tue, 27 Jun 2023 09:46:51 -0400 Subject: [PATCH 11/17] Capitalization fixes --- rust/README.md | 4 ++-- rust/examples/example.rs | 4 ++-- rust/src/bs_safe.rs | 18 +++++++++--------- rust/tests/common.rs | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/rust/README.md b/rust/README.md index 13984f20..437e0b8f 100644 --- a/rust/README.md +++ b/rust/README.md @@ -27,7 +27,7 @@ let path = Path::new(env!["CARGO_MANIFEST_DIR"]) .unwrap() .join("test_models/simple/simple_model.so"); -let lib = open_library(path).expect("Could not load compiled stan model."); +let lib = open_library(path).expect("Could not load compiled Stan model."); // The dataset as json let data = r#"{"N": 7}"#; @@ -40,7 +40,7 @@ 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) + panic!("Model initialization failed. Error message from Stan was {}", msg) }, _ => { panic!("Unexpected error") }, }; diff --git a/rust/examples/example.rs b/rust/examples/example.rs index f45cfa69..0e32d213 100644 --- a/rust/examples/example.rs +++ b/rust/examples/example.rs @@ -10,7 +10,7 @@ fn main() { .unwrap() .join("test_models/simple/simple_model.so"); - let lib = open_library(path).expect("Could not load compiled stan model."); + let lib = open_library(path).expect("Could not load compiled Stan model."); // The dataset as json let data = r#"{"N": 7}"#; @@ -24,7 +24,7 @@ fn main() { Ok(model) => model, Err(BridgeStanError::ConstructFailed(msg)) => { panic!( - "Model initialization failed. Error message from stan was {}", + "Model initialization failed. Error message from Stan was {}", msg ) } diff --git a/rust/src/bs_safe.rs b/rust/src/bs_safe.rs index 75ffea68..848f8292 100644 --- a/rust/src/bs_safe.rs +++ b/rust/src/bs_safe.rs @@ -18,7 +18,7 @@ use std::time::Instant; // This is more or less equivalent to manually defining Display and From use thiserror::Error; -/// A loaded shared library for a stan model +/// A loaded shared library for a Stan model pub struct StanLibrary { lib: ManuallyDrop, id: u64, @@ -35,11 +35,11 @@ impl Drop for StanLibrary { } } -/// A callback for print statements in stan models +/// A callback for print statements in Stan models pub type StanPrintCallback = extern "C" fn(*const c_char, usize); impl StanLibrary { - /// Provide a callback function to be called when stan prints a message + /// Provide a callback function to be called when Stan prints a message /// /// # Safety /// @@ -58,7 +58,7 @@ impl StanLibrary { } } - /// Unload the stan library. + /// Unload the Stan library. /// /// # Safety /// @@ -105,7 +105,7 @@ pub enum BridgeStanError { type Result = std::result::Result; -/// Open a compiled stan library. +/// Open a compiled Stan library. /// /// The library should have been compiled with bridgestan, /// with the same version as the rust library. @@ -271,7 +271,7 @@ impl> Model { } } - /// Return a reference to the underlying stan library + /// Return a reference to the underlying Stan library pub fn ref_library(&self) -> &StanLibrary { self.lib.borrow() } @@ -282,7 +282,7 @@ impl> Model { /// block are desired. /// /// This instance can only be used with models from the same - /// stan library. Invalid usage will otherwise result in a + /// Stan library. Invalid usage will otherwise result in a /// panic. pub fn new_rng(&self, seed: u32) -> Result> { Rng::new(self.ref_library(), seed) @@ -551,7 +551,7 @@ impl> Model { if let Some(rng) = &rng { assert!( rng.lib.borrow().id == self.lib.borrow().id, - "Rng and model must come from the same stan library" + "Rng and model must come from the same Stan library" ); } @@ -636,7 +636,7 @@ impl> Model { } impl + Clone> Model { - /// Return a clone of the underlying stan library + /// Return a clone of the underlying Stan library pub fn clone_library_ref(&self) -> T { self.lib.clone() } diff --git a/rust/tests/common.rs b/rust/tests/common.rs index 8e54d0aa..5d8253e7 100644 --- a/rust/tests/common.rs +++ b/rust/tests/common.rs @@ -12,7 +12,7 @@ pub fn model_dir() -> PathBuf { .join("test_models") } -/// Load stan library and corresponding data if available +/// Load Stan library and corresponding data if available pub fn get_model>(name: S) -> (StanLibrary, Option) { let name = name.as_ref(); let mut base = model_dir(); From 61680543f8eb64b57bf53722d695e98c836e261e Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Tue, 27 Jun 2023 09:52:36 -0400 Subject: [PATCH 12/17] Consistency edits --- rust/src/bs_safe.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/rust/src/bs_safe.rs b/rust/src/bs_safe.rs index 848f8292..c3713e5a 100644 --- a/rust/src/bs_safe.rs +++ b/rust/src/bs_safe.rs @@ -515,11 +515,9 @@ impl> Model { /// parameters of the Stan model after the parameters. If `include_gq` is /// set, we also include the generated quantities at the very end. /// - /// The length of the provided buffer `out` must have length - /// `self.param_num(include_tp, include_gq)`, this function panics - /// if that is not the case. - /// - /// A Stan random number generator has to be provided if `include_gp`. + /// *Panics* if the provided buffer has incorrect shape. The length of the `out` buffer + /// `self.param_num(include_tp, include_gq)`. + /// *Panics* if `include_gq` is set but no random number generator is provided. pub fn param_constrain>( &self, theta_unc: &[f64], From 9d091bedd878bd5af25138e4aa55a34a7d44b479 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Tue, 27 Jun 2023 14:15:34 -0400 Subject: [PATCH 13/17] Update ffi.rst --- docs/internals/ffi.rst | 17 ++++++++++++++++- docs/languages/rust.rst | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/internals/ffi.rst b/docs/internals/ffi.rst index 9b244c42..528d7d85 100644 --- a/docs/internals/ffi.rst +++ b/docs/internals/ffi.rst @@ -63,6 +63,18 @@ as well as an extended Note: One quirk of the ``.C`` interface is the requirement that all inputs and return values are passed by pointers. This is the reason for the ``bridgestan_R`` files in the source. +Rust +____ + +The Rust interface uses two crates in addition to the built-in +`FFI types `__. +Some general guidance on Rust FFI can be found in the +`Rust Book `__. + +These are `bindgen `__, which generates the Rust bindings from the C headers, +and `libloading `__, which provides easy dynamic library loading functionality. + +Care is taken to provide "safe" Rust wrappers so that users of the crate do not need to use the `unsafe` keyword. General Problems ---------------- @@ -75,7 +87,8 @@ must also be freed on that side. This means special consideration is needed to pass strings back and forth between the languages, and inspired some of the design decisions behind ideas like returning the parameter names as a comma separated list, rather than the more "natural" -array of strings. +array of strings, and this is why error messages must be passed back to the +library in order to be freed. Output Streams ______________ @@ -85,3 +98,5 @@ This is particularly relevant for error messaging, which is printed to the stand error output ``stderr`` from C++. This does *not*, for example, correspond to the ``sys.stderr`` stream available from Python. +We tackle this problem through the use of an interface-provided callback function +when necessary. diff --git a/docs/languages/rust.rst b/docs/languages/rust.rst index 8a509cec..6ea07f46 100644 --- a/docs/languages/rust.rst +++ b/docs/languages/rust.rst @@ -8,7 +8,7 @@ Rust Interface Installation ------------ -The BridgeStan Rust client is availble on crates.io and via ``cargo``: +The BridgeStan Rust client is availble on `crates.io `__ and via ``cargo``: .. code-block:: shell From c7d4c408a3b5db151129f4bd3d11cf3717128751 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Tue, 27 Jun 2023 14:33:16 -0400 Subject: [PATCH 14/17] Typo fixes --- docs/languages/rust.rst | 2 +- rust/src/bs_safe.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/languages/rust.rst b/docs/languages/rust.rst index 6ea07f46..b91cfb64 100644 --- a/docs/languages/rust.rst +++ b/docs/languages/rust.rst @@ -8,7 +8,7 @@ Rust Interface Installation ------------ -The BridgeStan Rust client is availble on `crates.io `__ and via ``cargo``: +The BridgeStan Rust client is available on `crates.io `__ and via ``cargo``: .. code-block:: shell diff --git a/rust/src/bs_safe.rs b/rust/src/bs_safe.rs index c3713e5a..79726d5a 100644 --- a/rust/src/bs_safe.rs +++ b/rust/src/bs_safe.rs @@ -45,7 +45,7 @@ impl StanLibrary { /// /// The provided function must never panic. /// - /// Since the call is proteted by a mutex internally, it does not + /// Since the call is protected by a mutex internally, it does not /// need to be thread safe. pub unsafe fn set_print_callback(&mut self, callback: StanPrintCallback) -> Result<()> { let mut err = ErrorMsg::new(self); @@ -80,7 +80,7 @@ pub struct LoadingError(#[from] libloading::Error); #[derive(Error, Debug)] #[non_exhaustive] pub enum BridgeStanError { - /// The prodided library could not be loaded. + /// The provided library could not be loaded. #[error(transparent)] InvalidLibrary(#[from] LoadingError), /// The version of the Stan library does not match the version of the rust crate. @@ -107,8 +107,8 @@ type Result = std::result::Result; /// Open a compiled Stan library. /// -/// The library should have been compiled with bridgestan, -/// with the same version as the rust library. +/// The library should have been compiled with BridgeStan, +/// with the same version as the Rust library. pub fn open_library>(path: P) -> Result { let library = unsafe { libloading::Library::new(&path) }.map_err(LoadingError)?; let major: libloading::Symbol<*const c_int> = @@ -256,7 +256,7 @@ impl> Model { if let Some(model) = NonNull::new(model) { drop(err); let model = Self { model, lib }; - // If STAN_THREADS is not true, the safty guaranties we are + // If STAN_THREADS is not true, the safety guaranties we are // making would be incorrect let info = model.info(); if !info.to_string_lossy().contains("STAN_THREADS=true") { @@ -509,7 +509,7 @@ impl> Model { /// Map a point in unconstrained parameter space to the constrained space. /// - /// `theta_unc` must contain the point in the unconstained parameter 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 From 13e1a5eda84d1359574b68a180083e5a0efde45e Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Wed, 28 Jun 2023 15:25:47 -0400 Subject: [PATCH 15/17] Update README for Rust example --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 43e2e5a2..011b6d83 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ to download the appropriate Stan compiler for your platform into ### Example programs This repository includes examples of calling Stan through BridgeStan -in Python, Julia, R, and C. +in Python, Julia, R, Rust, and C. * From Python: [`example.py`](python/example.py) @@ -68,9 +68,9 @@ in Python, Julia, R, and C. * From R: [`example.r`](R/example.R) -* From C: [`example.c`](c-example/example.c) +* From Rust: [`example.rs`](rust/examples/example.rs) -* From Rust: [`example.rs`](rust/examples/example.c) +* From C: [`example.c`](c-example/example.c) Examples of other functionality can be found in the `test` folder for each interface. From de940248137ee5e7e7e2870f14ed07cd71513837 Mon Sep 17 00:00:00 2001 From: Adrian Seyboldt Date: Wed, 28 Jun 2023 18:23:06 -0500 Subject: [PATCH 16/17] Add section about STAN_THREADS to rust docs --- docs/languages/rust.rst | 3 +++ rust/README.md | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/docs/languages/rust.rst b/docs/languages/rust.rst index b91cfb64..a7652cce 100644 --- a/docs/languages/rust.rst +++ b/docs/languages/rust.rst @@ -19,6 +19,9 @@ is required. Please follow the :doc:`Getting Started guide <../getting-started>` or use the Rust client in tandem with an interface such as :doc:`Python <./python>` which automates this process. +``STAN_THREADS=true`` needs to be specified when compiling a model, for more +details see the `API reference `__. + Example Program --------------- diff --git a/rust/README.md b/rust/README.md index 437e0b8f..fdfeddde 100644 --- a/rust/README.md +++ b/rust/README.md @@ -9,10 +9,29 @@ natively from Rust. Internally, it relies on [`bindgen`](https://docs.rs/bindgen/) and [`libloading`](https://docs.rs/libloading/). +## Compiling the model + The Rust wrapper does not currently have any functionality to compile Stan models. Compiled shared libraries need to be built manually using `make` or with the Julia or Python bindings. +For safety reasons all Stan models need to be installed with `STAN_THREADS=true`. +When compiling a model using `make`, set the environment variable: + +```bash +STAN_THREADS=true make some_model +``` + +When compiling a Stan model in python, this has to be specified in the `make_args` +argument: + +```python +path = bridgestan.compile_model("stan_model.stan", make_args=["STAN_THREADS=true"]) +``` + +If `STAN_THREADS` was not specified while building the model, the Rust wrapper +will throw an error when loading the model. + ## Usage: ```rust From edc1e6f46cd4e9bbe4cfcbdc575fc1dd90ecb722 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Thu, 29 Jun 2023 10:05:35 -0400 Subject: [PATCH 17/17] Add how to run the example --- rust/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rust/README.md b/rust/README.md index fdfeddde..c5a9fede 100644 --- a/rust/README.md +++ b/rust/README.md @@ -34,6 +34,8 @@ will throw an error when loading the model. ## Usage: +Run this example with `cargo run --example=example`. + ```rust use std::ffi::CString; use std::path::Path;