From 5c4808eeba695ca562d5b7fccbc1ead9adeb004b Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Thu, 15 Feb 2024 18:46:03 +0000 Subject: [PATCH 01/31] implement BridgeStan download and module compilation on Rust --- rust/Cargo.toml | 4 ++ rust/examples/example.rs | 7 ++- rust/src/bs_safe.rs | 9 ++- rust/src/download_compile.rs | 108 +++++++++++++++++++++++++++++++++++ rust/src/lib.rs | 2 + rust/tests/model.rs | 12 ++-- 6 files changed, 133 insertions(+), 9 deletions(-) create mode 100644 rust/src/download_compile.rs diff --git a/rust/Cargo.toml b/rust/Cargo.toml index e381ba33..3be7340e 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -11,6 +11,10 @@ homepage = "https://roualdes.github.io/bridgestan/latest/" [dependencies] libloading = "0.8.0" thiserror = "1.0.40" +ureq = "2.7" +tar = "0.4" +flate2 = "1.0" +dirs = "5.0" [build-dependencies] bindgen = "0.69.1" diff --git a/rust/examples/example.rs b/rust/examples/example.rs index defda712..b28c267e 100644 --- a/rust/examples/example.rs +++ b/rust/examples/example.rs @@ -1,4 +1,4 @@ -use bridgestan::{open_library, BridgeStanError, Model}; +use bridgestan::{compile_model, open_library, BridgeStanError, Model}; use std::ffi::CString; use std::path::Path; @@ -8,7 +8,10 @@ fn main() { let path = Path::new(env!["CARGO_MANIFEST_DIR"]) .parent() .unwrap() - .join("test_models/simple/simple_model.so"); + .join("test_models/simple/simple.stan"); + + let path = compile_model(path, None).expect("Could not compile Stan model."); + println!("Compiled model: {:?}", path); let lib = open_library(path).expect("Could not load compiled Stan model."); diff --git a/rust/src/bs_safe.rs b/rust/src/bs_safe.rs index 7515ee69..638ba453 100644 --- a/rust/src/bs_safe.rs +++ b/rust/src/bs_safe.rs @@ -1,3 +1,4 @@ +use crate::download_compile::VERSION; use crate::ffi; use std::borrow::Borrow; use std::collections::hash_map::DefaultHasher; @@ -101,9 +102,15 @@ pub enum BridgeStanError { /// Setting a print-callback failed. #[error("Failed to set a print-callback: {0}")] SetCallbackFailed(String), + /// Setting a compile Stan model failed. + #[error("Failed to compile Stan model: {0}")] + ModelCompilingFailed(String), + /// Setting a download BridgeStan failed. + #[error("Failed to download BridgeStan {VERSION} from github.com: {0}")] + DownloadFailed(String), } -type Result = std::result::Result; +pub(crate) type Result = std::result::Result; /// Open a compiled Stan library. /// diff --git a/rust/src/download_compile.rs b/rust/src/download_compile.rs new file mode 100644 index 00000000..3399a0f9 --- /dev/null +++ b/rust/src/download_compile.rs @@ -0,0 +1,108 @@ +use crate::bs_safe::{BridgeStanError, Result}; +use flate2::read::GzDecoder; +use std::{env::temp_dir, fs, path::PathBuf}; +use tar::Archive; + +pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// Download and unzip the BridgeStan source distribution for this version +/// to ~/.bridgestan/bridgestan-version +pub fn get_bridgestan_src() -> Result { + let homedir = dirs::home_dir().unwrap_or(temp_dir()); + + let bs_path_download_temp = homedir.join(".bridgestan_tmp_dir"); + let bs_path_download = homedir.join(".bridgestan"); + + let bs_path_download_temp_join_version = + bs_path_download_temp.join(format!("bridgestan-{VERSION}")); + let bs_path_download_join_version = bs_path_download.join(format!("bridgestan-{VERSION}")); + + if !bs_path_download_join_version.exists() { + println!("Downloading BridgeStan"); + + fs::remove_dir_all(&bs_path_download_temp).unwrap_or_default(); + fs::create_dir(&bs_path_download_temp).unwrap_or_default(); + fs::create_dir(&bs_path_download).unwrap_or_default(); + + let url = "https://github.com/roualdes/bridgestan/releases/download/".to_owned() + + format!("v{VERSION}/bridgestan-{VERSION}.tar.gz").as_str(); + + let response = ureq::get(url.as_str()) + .call() + .map_err(|e| BridgeStanError::DownloadFailed(e.to_string()))?; + let len = response + .header("Content-Length") + .and_then(|s| s.parse::().ok()) + .unwrap_or(50_000_000); + + let mut bytes: Vec = Vec::with_capacity(len); + response + .into_reader() + .read_to_end(&mut bytes) + .map_err(|e| BridgeStanError::DownloadFailed(e.to_string()))?; + + let tar = GzDecoder::new(bytes.as_slice()); + let mut archive = Archive::new(tar); + archive + .unpack(&bs_path_download_temp) + .map_err(|e| BridgeStanError::DownloadFailed(e.to_string()))?; + + fs::rename( + bs_path_download_temp_join_version, + &bs_path_download_join_version, + ) + .map_err(|e| BridgeStanError::DownloadFailed(e.to_string()))?; + + fs::remove_dir(bs_path_download_temp).unwrap_or_default(); + + println!("Finished downloading BridgeStan"); + } + + Ok(bs_path_download_join_version) +} + +/// Compile a Stan Model given a stan_file and the path to BridgeStan +/// if None, then calls get_bridgestan_src() to download BridgeStan +pub fn compile_model(stan_file: PathBuf, bs_path: Option) -> Result { + let bs_path = match bs_path { + Some(path) => path, + None => get_bridgestan_src()?, + }; + + let stan_file = fs::canonicalize(stan_file) + .map_err(|e| BridgeStanError::ModelCompilingFailed(e.to_string()))?; + + if stan_file.extension().unwrap_or_default() != "stan" { + return Err(BridgeStanError::ModelCompilingFailed( + "File must be a .stan file".to_owned(), + )); + } + + // add _model suffix and change extension to .so + let output = stan_file.with_extension(""); + let output = output.with_file_name(format!( + "{}_model", + output.file_name().unwrap_or_default().to_string_lossy() + )); + let output = output.with_extension("so"); + + let cmd = vec![output.to_str().unwrap_or_default().to_owned()]; + + println!("Compiling model"); + let proc = std::process::Command::new("make") + .args(cmd) + .current_dir(bs_path) + .env("STAN_THREADS", "true") + .output() + .map_err(|e| BridgeStanError::ModelCompilingFailed(e.to_string()))?; + println!("Finished compiling model"); + + if !proc.status.success() { + return Err(BridgeStanError::ModelCompilingFailed(format!( + "{} {}", + String::from_utf8_lossy(proc.stdout.as_slice()).into_owned(), + String::from_utf8_lossy(proc.stderr.as_slice()).into_owned(), + ))); + } + Ok(output) +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 6e0e75bf..a13bb7a7 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,6 +1,8 @@ #![doc = include_str!("../README.md")] mod bs_safe; +mod download_compile; pub(crate) mod ffi; pub use bs_safe::{open_library, BridgeStanError, Model, Rng, StanLibrary}; +pub use download_compile::{compile_model, get_bridgestan_src}; diff --git a/rust/tests/model.rs b/rust/tests/model.rs index d28c5822..529339da 100644 --- a/rust/tests/model.rs +++ b/rust/tests/model.rs @@ -25,8 +25,8 @@ fn throw_data() { fn bad_arglength() { let (lib, data) = get_model("stdnormal"); let model = Model::new(&lib, data, 42).unwrap(); - let theta = vec![]; - let mut grad = vec![]; + let theta = []; + let mut grad = []; let _ = model.log_density_gradient(&theta[..], true, true, &mut grad[..]); } @@ -34,8 +34,8 @@ fn bad_arglength() { fn logp_gradient() { let (lib, data) = get_model("stdnormal"); let model = Model::new(&lib, data, 42).unwrap(); - let theta = vec![1f64]; - let mut grad = vec![0f64]; + let theta = [1f64]; + let mut grad = [0f64]; let logp = model .log_density_gradient(&theta[..], false, true, &mut grad[..]) .unwrap(); @@ -47,8 +47,8 @@ fn logp_gradient() { fn logp_hessian() { let (lib, data) = get_model("stdnormal"); let model = Model::new(&lib, data, 42).unwrap(); - let theta = vec![1f64]; - let mut grad = vec![0f64]; + let theta = [1f64]; + let mut grad = [0f64]; let mut hessian = vec![0f64]; let logp = model .log_density_hessian(&theta[..], false, true, &mut grad[..], &mut hessian) From 6132585fc8bba8970f30183bd55b7d04ce35c106 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Sat, 17 Feb 2024 07:27:29 +0000 Subject: [PATCH 02/31] rust: add feature compile-stan-model --- rust/Cargo.toml | 15 +++++++++++---- rust/src/bs_safe.rs | 3 +++ rust/src/download_compile.rs | 2 +- rust/src/lib.rs | 6 +++++- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 3be7340e..2d07f2fa 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -11,10 +11,13 @@ homepage = "https://roualdes.github.io/bridgestan/latest/" [dependencies] libloading = "0.8.0" thiserror = "1.0.40" -ureq = "2.7" -tar = "0.4" -flate2 = "1.0" -dirs = "5.0" +ureq = { version = "2.7", optional = true } +tar = { version = "0.4", optional = true } +flate2 = { version = "1.0", optional = true } +dirs = { version = "5.0", optional = true } + +[features] +compile-stan-model = ["ureq", "tar", "flate2", "dirs"] [build-dependencies] bindgen = "0.69.1" @@ -22,3 +25,7 @@ bindgen = "0.69.1" [dev-dependencies] approx = "0.5.1" rand = "0.8.5" + +[[example]] +name = "example" +required-features = ["compile-stan-model"] diff --git a/rust/src/bs_safe.rs b/rust/src/bs_safe.rs index 638ba453..df441e91 100644 --- a/rust/src/bs_safe.rs +++ b/rust/src/bs_safe.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "compile-stan-model")] use crate::download_compile::VERSION; use crate::ffi; use std::borrow::Borrow; @@ -102,9 +103,11 @@ pub enum BridgeStanError { /// Setting a print-callback failed. #[error("Failed to set a print-callback: {0}")] SetCallbackFailed(String), + #[cfg(feature = "compile-stan-model")] /// Setting a compile Stan model failed. #[error("Failed to compile Stan model: {0}")] ModelCompilingFailed(String), + #[cfg(feature = "compile-stan-model")] /// Setting a download BridgeStan failed. #[error("Failed to download BridgeStan {VERSION} from github.com: {0}")] DownloadFailed(String), diff --git a/rust/src/download_compile.rs b/rust/src/download_compile.rs index 3399a0f9..7ad688b3 100644 --- a/rust/src/download_compile.rs +++ b/rust/src/download_compile.rs @@ -86,7 +86,7 @@ pub fn compile_model(stan_file: PathBuf, bs_path: Option) -> Result Date: Sat, 17 Feb 2024 08:32:04 +0000 Subject: [PATCH 03/31] rust: allow user defined stanc_args and make_args when compiling model --- rust/.vscode/settings.json | 5 +++++ rust/examples/example.rs | 2 +- rust/src/download_compile.rs | 30 ++++++++++++++++++++++++++---- 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 rust/.vscode/settings.json diff --git a/rust/.vscode/settings.json b/rust/.vscode/settings.json new file mode 100644 index 00000000..e90ec878 --- /dev/null +++ b/rust/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "rust-analyzer.cargo.features": [ + "compile-stan-model" + ] +} diff --git a/rust/examples/example.rs b/rust/examples/example.rs index b28c267e..bf2f8b8f 100644 --- a/rust/examples/example.rs +++ b/rust/examples/example.rs @@ -10,7 +10,7 @@ fn main() { .unwrap() .join("test_models/simple/simple.stan"); - let path = compile_model(path, None).expect("Could not compile Stan model."); + let path = compile_model(path, vec![], vec![], None).expect("Could not compile Stan model."); println!("Compiled model: {:?}", path); let lib = open_library(path).expect("Could not load compiled Stan model."); diff --git a/rust/src/download_compile.rs b/rust/src/download_compile.rs index 7ad688b3..f588fcdb 100644 --- a/rust/src/download_compile.rs +++ b/rust/src/download_compile.rs @@ -1,6 +1,10 @@ use crate::bs_safe::{BridgeStanError, Result}; use flate2::read::GzDecoder; -use std::{env::temp_dir, fs, path::PathBuf}; +use std::{ + env::temp_dir, + fs, + path::{Path, PathBuf}, +}; use tar::Archive; pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -63,9 +67,17 @@ pub fn get_bridgestan_src() -> Result { /// Compile a Stan Model given a stan_file and the path to BridgeStan /// if None, then calls get_bridgestan_src() to download BridgeStan -pub fn compile_model(stan_file: PathBuf, bs_path: Option) -> Result { +pub fn compile_model

( + stan_file: P, + stanc_args: Vec<&str>, + make_args: Vec<&str>, + bs_path: Option

, +) -> Result +where + P: AsRef, +{ let bs_path = match bs_path { - Some(path) => path, + Some(path) => path.as_ref().to_owned(), None => get_bridgestan_src()?, }; @@ -86,7 +98,17 @@ pub fn compile_model(stan_file: PathBuf, bs_path: Option) -> Result Date: Sat, 17 Feb 2024 09:00:05 +0000 Subject: [PATCH 04/31] rust: add model_compiling test --- .github/workflows/main.yaml | 5 ++--- rust/tests/model.rs | 27 ++++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 50668d80..10f4d0a9 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -3,7 +3,7 @@ name: bridgestan tests on: push: branches: - - 'main' + - "main" pull_request: workflow_dispatch: {} @@ -128,7 +128,6 @@ jobs: env: BRIDGESTAN: ${{ github.workspace }} - julia: needs: [build] runs-on: ${{matrix.os}} @@ -291,4 +290,4 @@ jobs: cargo clippy cargo fmt --check cargo run --example=example - cargo test --verbose + cargo test --verbose --features compile-stan-model diff --git a/rust/tests/model.rs b/rust/tests/model.rs index 529339da..325ccbc8 100644 --- a/rust/tests/model.rs +++ b/rust/tests/model.rs @@ -1,11 +1,11 @@ mod common; -use std::{f64::consts::PI, ffi::CString}; +use std::{f64::consts::PI, ffi::CString, fs::remove_file}; -use common::get_model; +use common::{get_model, model_dir}; use approx::{assert_abs_diff_eq, assert_ulps_eq}; -use bridgestan::{BridgeStanError, Model}; +use bridgestan::{compile_model, BridgeStanError, Model}; #[test] fn throw_data() { @@ -43,6 +43,27 @@ fn logp_gradient() { assert_ulps_eq!(grad[0], -1f64); } +#[test] +fn model_compiling() { + let name = "stdnormal"; + let mut base = model_dir(); + base.push(name); + let lib_path = base.join(format!("{}_model.so", name)); + let stan_path = base.join(format!("{}.stan", name)); + remove_file(lib_path).unwrap(); + compile_model(stan_path, vec![], vec![], None).unwrap(); + + let (lib, data) = get_model(name); + let model = Model::new(&lib, data, 42).unwrap(); + let theta = [1f64]; + let mut grad = [0f64]; + let logp = model + .log_density_gradient(&theta[..], false, true, &mut grad[..]) + .unwrap(); + assert_ulps_eq!(logp, (2. * PI).sqrt().recip().ln() - 0.5); + assert_ulps_eq!(grad[0], -1f64); +} + #[test] fn logp_hessian() { let (lib, data) = get_model("stdnormal"); From d9f1cd7758e17be9543e1b8c43ff09b004424b39 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Sat, 17 Feb 2024 09:47:11 +0000 Subject: [PATCH 05/31] rust: updating readme --- rust/README.md | 34 +++++++--------------------------- rust/examples/example.rs | 4 ++-- 2 files changed, 9 insertions(+), 29 deletions(-) diff --git a/rust/README.md b/rust/README.md index c5a9fede..42aa2160 100644 --- a/rust/README.md +++ b/rust/README.md @@ -9,30 +9,7 @@ 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: +## Usage Run this example with `cargo run --example=example`. @@ -41,12 +18,15 @@ use std::ffi::CString; use std::path::Path; use bridgestan::{BridgeStanError, Model, open_library}; -// The path to the compiled model. -// Get for instance from python `bridgestan.compile_model` +// The path to the Stan model let path = Path::new(env!["CARGO_MANIFEST_DIR"]) .parent() .unwrap() - .join("test_models/simple/simple_model.so"); + .join("test_models/simple/simple.stan"); + +// The path to the compiled model +let path = compile_model(path, vec![], vec![], None).expect("Could not compile Stan model."); +println!("Compiled model: {:?}", path); let lib = open_library(path).expect("Could not load compiled Stan model."); diff --git a/rust/examples/example.rs b/rust/examples/example.rs index bf2f8b8f..a7b64c2d 100644 --- a/rust/examples/example.rs +++ b/rust/examples/example.rs @@ -3,13 +3,13 @@ use std::ffi::CString; use std::path::Path; fn main() { - // The path to the compiled model. - // Get for instance from python `bridgestan.compile_model` + // The path to the Stan model let path = Path::new(env!["CARGO_MANIFEST_DIR"]) .parent() .unwrap() .join("test_models/simple/simple.stan"); + // The path to the compiled model let path = compile_model(path, vec![], vec![], None).expect("Could not compile Stan model."); println!("Compiled model: {:?}", path); From e9970cd1cb451dd99a7c4cff66e28c3de433447d Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Sat, 17 Feb 2024 10:44:30 +0000 Subject: [PATCH 06/31] rust: update documentation --- docs/languages/rust.rst | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/languages/rust.rst b/docs/languages/rust.rst index 8ccbe0ef..6c24ecc3 100644 --- a/docs/languages/rust.rst +++ b/docs/languages/rust.rst @@ -14,13 +14,9 @@ The BridgeStan Rust client is available on `crates.io ` -or use the Rust client in tandem with an interface such as :doc:`Python <./python>` -which automates this process. +The first time you compile a model, the BridgeStan source code will be downloaded to `~/.bridgestan`. If you prefer to use a source distribution of BridgeStan, you can pass its path as the `bs_path` argument to `compile_model`. -``STAN_THREADS=true`` needs to be specified when compiling a model, for more -details see the `API reference `__. +Note that the system pre-requisites from the [Getting Started Guide](../getting-started.rst) are still required and will not be automatically installed by this method. Example Program --------------- From c06599ca6d90aea2d6eb89adb2c9ee8703144880 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Sun, 18 Feb 2024 13:27:14 +0000 Subject: [PATCH 07/31] rust: fix tests --- .github/workflows/main.yaml | 2 +- rust/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 10f4d0a9..e8d490a0 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -289,5 +289,5 @@ jobs: run: | cargo clippy cargo fmt --check - cargo run --example=example + cargo run --example=example --features compile-stan-model cargo test --verbose --features compile-stan-model diff --git a/rust/README.md b/rust/README.md index 42aa2160..e56919a3 100644 --- a/rust/README.md +++ b/rust/README.md @@ -16,7 +16,7 @@ Run this example with `cargo run --example=example`. ```rust use std::ffi::CString; use std::path::Path; -use bridgestan::{BridgeStanError, Model, open_library}; +use bridgestan::{BridgeStanError, Model, open_library, compile_model}; // The path to the Stan model let path = Path::new(env!["CARGO_MANIFEST_DIR"]) From 065644b34e2a0f457177061ce2fcca1f9c53a91d Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Sun, 18 Feb 2024 16:43:27 +0000 Subject: [PATCH 08/31] rust: skip model_compiling() test on windows --- rust/tests/model.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/tests/model.rs b/rust/tests/model.rs index 325ccbc8..7836f631 100644 --- a/rust/tests/model.rs +++ b/rust/tests/model.rs @@ -43,6 +43,7 @@ fn logp_gradient() { assert_ulps_eq!(grad[0], -1f64); } +#[cfg(target_family = "unix")] #[test] fn model_compiling() { let name = "stdnormal"; From 30c814e03041f58f9ce1cd9fa63e483e48ff0b62 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Sun, 18 Feb 2024 17:25:59 +0000 Subject: [PATCH 09/31] rust: fix race condition in tests --- .github/workflows/main.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index e8d490a0..dec92b43 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -290,4 +290,4 @@ jobs: cargo clippy cargo fmt --check cargo run --example=example --features compile-stan-model - cargo test --verbose --features compile-stan-model + cargo test --verbose --features compile-stan-model -- --test-threads=1 From c9d1d95a88b9adea62eb1835bec0ee940a13d420 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Sun, 18 Feb 2024 17:48:49 +0000 Subject: [PATCH 10/31] rust: make example path portable --- rust/examples/example.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rust/examples/example.rs b/rust/examples/example.rs index a7b64c2d..bcd8ee81 100644 --- a/rust/examples/example.rs +++ b/rust/examples/example.rs @@ -7,7 +7,9 @@ fn main() { let path = Path::new(env!["CARGO_MANIFEST_DIR"]) .parent() .unwrap() - .join("test_models/simple/simple.stan"); + .join("test_models") + .join("simple") + .join("simple.stan"); // The path to the compiled model let path = compile_model(path, vec![], vec![], None).expect("Could not compile Stan model."); From 6fb0bf1fb7f6cae1feb13138879bb8c2fd66f668 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Sun, 18 Feb 2024 19:43:00 +0000 Subject: [PATCH 11/31] rust: fix windows absolute path resolution --- rust/Cargo.toml | 3 ++- rust/src/download_compile.rs | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 2d07f2fa..1a7de615 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -15,9 +15,10 @@ ureq = { version = "2.7", optional = true } tar = { version = "0.4", optional = true } flate2 = { version = "1.0", optional = true } dirs = { version = "5.0", optional = true } +path-absolutize = { version = "3.1", optional = true } [features] -compile-stan-model = ["ureq", "tar", "flate2", "dirs"] +compile-stan-model = ["ureq", "tar", "flate2", "dirs", "path-absolutize"] [build-dependencies] bindgen = "0.69.1" diff --git a/rust/src/download_compile.rs b/rust/src/download_compile.rs index f588fcdb..1156d4e2 100644 --- a/rust/src/download_compile.rs +++ b/rust/src/download_compile.rs @@ -1,5 +1,6 @@ use crate::bs_safe::{BridgeStanError, Result}; use flate2::read::GzDecoder; +use path_absolutize::Absolutize; use std::{ env::temp_dir, fs, @@ -81,7 +82,9 @@ where None => get_bridgestan_src()?, }; - let stan_file = fs::canonicalize(stan_file) + let stan_file = stan_file + .as_ref() + .absolutize() .map_err(|e| BridgeStanError::ModelCompilingFailed(e.to_string()))?; if stan_file.extension().unwrap_or_default() != "stan" { From a660a813200bff28aea13bf39f05269349450c85 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Wed, 28 Feb 2024 21:23:51 +0000 Subject: [PATCH 12/31] Delete rust/.vscode/settings.json --- rust/.vscode/settings.json | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 rust/.vscode/settings.json diff --git a/rust/.vscode/settings.json b/rust/.vscode/settings.json deleted file mode 100644 index e90ec878..00000000 --- a/rust/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "rust-analyzer.cargo.features": [ - "compile-stan-model" - ] -} From 61c48602deba297e7054eb9cdf69efbe4a8d00fb Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Wed, 28 Feb 2024 21:42:25 +0000 Subject: [PATCH 13/31] rust: mark model_compiling test as ignored --- .github/workflows/main.yaml | 3 ++- rust/tests/model.rs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index dec92b43..286fcd6f 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -290,4 +290,5 @@ jobs: cargo clippy cargo fmt --check cargo run --example=example --features compile-stan-model - cargo test --verbose --features compile-stan-model -- --test-threads=1 + cargo test --verbose --features compile-stan-model + cargo test --verbose --features compile-stan-model model_compiling -- --ignored diff --git a/rust/tests/model.rs b/rust/tests/model.rs index 7836f631..ce361074 100644 --- a/rust/tests/model.rs +++ b/rust/tests/model.rs @@ -45,6 +45,7 @@ fn logp_gradient() { #[cfg(target_family = "unix")] #[test] +#[ignore] fn model_compiling() { let name = "stdnormal"; let mut base = model_dir(); From 2035556bc2ba3528a01774abfbeb0db5f1049f28 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Wed, 28 Feb 2024 22:04:18 +0000 Subject: [PATCH 14/31] rust: use mingw32-make to compile model on windows --- rust/src/download_compile.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rust/src/download_compile.rs b/rust/src/download_compile.rs index 1156d4e2..6574ad9a 100644 --- a/rust/src/download_compile.rs +++ b/rust/src/download_compile.rs @@ -114,7 +114,12 @@ where .concat(); println!("Compiling model"); - let proc = std::process::Command::new("make") + let make = if cfg!(target_os = "windows") { + "mingw32-make" + } else { + "make" + }; + let proc = std::process::Command::new(make) .args(cmd) .current_dir(bs_path) .env("STAN_THREADS", "true") From 0c48c02bce22a5d90124f5623e7cf944894f03f4 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Thu, 29 Feb 2024 19:35:49 +0000 Subject: [PATCH 15/31] rust: change println! to info! --- rust/Cargo.toml | 4 +++- rust/README.md | 2 +- rust/examples/example.rs | 2 ++ rust/src/download_compile.rs | 9 +++++---- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 1a7de615..b41f14b9 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -16,9 +16,10 @@ tar = { version = "0.4", optional = true } flate2 = { version = "1.0", optional = true } dirs = { version = "5.0", optional = true } path-absolutize = { version = "3.1", optional = true } +log = { version = "0.4", optional = true } [features] -compile-stan-model = ["ureq", "tar", "flate2", "dirs", "path-absolutize"] +compile-stan-model = ["ureq", "tar", "flate2", "dirs", "path-absolutize", "log"] [build-dependencies] bindgen = "0.69.1" @@ -26,6 +27,7 @@ bindgen = "0.69.1" [dev-dependencies] approx = "0.5.1" rand = "0.8.5" +env_logger = "0.11" [[example]] name = "example" diff --git a/rust/README.md b/rust/README.md index e56919a3..f8508d22 100644 --- a/rust/README.md +++ b/rust/README.md @@ -11,7 +11,7 @@ Internally, it relies on [`bindgen`](https://docs.rs/bindgen/) and ## Usage -Run this example with `cargo run --example=example`. +Run this example with `RUST_LOG=info cargo run --example=example --features compile-stan-model`. ```rust use std::ffi::CString; diff --git a/rust/examples/example.rs b/rust/examples/example.rs index bcd8ee81..73698fa0 100644 --- a/rust/examples/example.rs +++ b/rust/examples/example.rs @@ -3,6 +3,8 @@ use std::ffi::CString; use std::path::Path; fn main() { + env_logger::init(); + // The path to the Stan model let path = Path::new(env!["CARGO_MANIFEST_DIR"]) .parent() diff --git a/rust/src/download_compile.rs b/rust/src/download_compile.rs index 6574ad9a..91ab405a 100644 --- a/rust/src/download_compile.rs +++ b/rust/src/download_compile.rs @@ -1,5 +1,6 @@ use crate::bs_safe::{BridgeStanError, Result}; use flate2::read::GzDecoder; +use log::info; use path_absolutize::Absolutize; use std::{ env::temp_dir, @@ -23,7 +24,7 @@ pub fn get_bridgestan_src() -> Result { let bs_path_download_join_version = bs_path_download.join(format!("bridgestan-{VERSION}")); if !bs_path_download_join_version.exists() { - println!("Downloading BridgeStan"); + info!("Downloading BridgeStan"); fs::remove_dir_all(&bs_path_download_temp).unwrap_or_default(); fs::create_dir(&bs_path_download_temp).unwrap_or_default(); @@ -60,7 +61,7 @@ pub fn get_bridgestan_src() -> Result { fs::remove_dir(bs_path_download_temp).unwrap_or_default(); - println!("Finished downloading BridgeStan"); + info!("Finished downloading BridgeStan"); } Ok(bs_path_download_join_version) @@ -113,7 +114,7 @@ where ] .concat(); - println!("Compiling model"); + info!("Compiling model"); let make = if cfg!(target_os = "windows") { "mingw32-make" } else { @@ -125,7 +126,7 @@ where .env("STAN_THREADS", "true") .output() .map_err(|e| BridgeStanError::ModelCompilingFailed(e.to_string()))?; - println!("Finished compiling model"); + info!("Finished compiling model"); if !proc.status.success() { return Err(BridgeStanError::ModelCompilingFailed(format!( From 54f9ecdcb6c16ea246a928dcaa88e8ad8af0d379 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Thu, 29 Feb 2024 19:48:02 +0000 Subject: [PATCH 16/31] Update README.md --- rust/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/rust/README.md b/rust/README.md index f8508d22..caf15b43 100644 --- a/rust/README.md +++ b/rust/README.md @@ -9,6 +9,26 @@ natively from Rust. Internally, it relies on [`bindgen`](https://docs.rs/bindgen/) and [`libloading`](https://docs.rs/libloading/). +## Compiling the model + +The Rust wrapper currently have a builtin functionality to compile Stan models (function `compile_model` under the feature flag `compile-stan-model`). For safety reasons all Stan models need to be installed with `STAN_THREADS=true`. However, if you use the Rust wrapper builtin functionality to compile Stan models, this will automatically be set for you. + +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 Run this example with `RUST_LOG=info cargo run --example=example --features compile-stan-model`. From 2a8db3688c5ebc6153f1bd9802ecf7ecd32fd75f Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Tue, 5 Mar 2024 20:00:46 +0000 Subject: [PATCH 17/31] Update Cargo.toml --- rust/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index b41f14b9..c967edc0 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -19,7 +19,7 @@ path-absolutize = { version = "3.1", optional = true } log = { version = "0.4", optional = true } [features] -compile-stan-model = ["ureq", "tar", "flate2", "dirs", "path-absolutize", "log"] +compile-stan-model = ["dep:ureq", "dep:tar", "dep:flate2", "dep:dirs", "dep:path-absolutize", "dep:log"] [build-dependencies] bindgen = "0.69.1" From 86e9ee8e2997caa4b8cd713b2ecb54bc6ce0ca75 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Tue, 5 Mar 2024 20:55:32 +0000 Subject: [PATCH 18/31] rust: single compile error message --- rust/src/download_compile.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/rust/src/download_compile.rs b/rust/src/download_compile.rs index 91ab405a..ff1b229a 100644 --- a/rust/src/download_compile.rs +++ b/rust/src/download_compile.rs @@ -120,20 +120,25 @@ where } else { "make" }; - let proc = std::process::Command::new(make) + std::process::Command::new(make) .args(cmd) .current_dir(bs_path) .env("STAN_THREADS", "true") .output() + .map_err(|e| e.to_string()) + .and_then(|proc| { + if !proc.status.success() { + Err(format!( + "{} {}", + String::from_utf8_lossy(proc.stdout.as_slice()).into_owned(), + String::from_utf8_lossy(proc.stderr.as_slice()).into_owned(), + )) + } else { + Ok(()) + } + }) .map_err(|e| BridgeStanError::ModelCompilingFailed(e.to_string()))?; info!("Finished compiling model"); - if !proc.status.success() { - return Err(BridgeStanError::ModelCompilingFailed(format!( - "{} {}", - String::from_utf8_lossy(proc.stdout.as_slice()).into_owned(), - String::from_utf8_lossy(proc.stderr.as_slice()).into_owned(), - ))); - } Ok(output) } From 31ae1d163f07c75e9e618e2522428fb73a3a5465 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Tue, 5 Mar 2024 21:04:00 +0000 Subject: [PATCH 19/31] rust: run tests without feature compile-stan-model --- .github/workflows/main.yaml | 1 + rust/tests/model.rs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 286fcd6f..b14eb906 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -292,3 +292,4 @@ jobs: cargo run --example=example --features compile-stan-model cargo test --verbose --features compile-stan-model cargo test --verbose --features compile-stan-model model_compiling -- --ignored + cargo test --verbose --all-targets diff --git a/rust/tests/model.rs b/rust/tests/model.rs index ce361074..cae5f32a 100644 --- a/rust/tests/model.rs +++ b/rust/tests/model.rs @@ -5,7 +5,7 @@ use common::{get_model, model_dir}; use approx::{assert_abs_diff_eq, assert_ulps_eq}; -use bridgestan::{compile_model, BridgeStanError, Model}; +use bridgestan::{BridgeStanError, Model}; #[test] fn throw_data() { @@ -43,6 +43,7 @@ fn logp_gradient() { assert_ulps_eq!(grad[0], -1f64); } +#[cfg(feature = "compile-stan-model")] #[cfg(target_family = "unix")] #[test] #[ignore] @@ -53,7 +54,7 @@ fn model_compiling() { let lib_path = base.join(format!("{}_model.so", name)); let stan_path = base.join(format!("{}.stan", name)); remove_file(lib_path).unwrap(); - compile_model(stan_path, vec![], vec![], None).unwrap(); + bridgestan::compile_model(stan_path, vec![], vec![], None).unwrap(); let (lib, data) = get_model(name); let model = Model::new(&lib, data, 42).unwrap(); From 3cab3e4c860d81e9669bc114ae9a4025230807a9 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Thu, 7 Mar 2024 18:25:32 +0000 Subject: [PATCH 20/31] rust: adding comments about std::fs::canonicalize --- rust/src/download_compile.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rust/src/download_compile.rs b/rust/src/download_compile.rs index ff1b229a..8e3b3daf 100644 --- a/rust/src/download_compile.rs +++ b/rust/src/download_compile.rs @@ -83,6 +83,10 @@ where None => get_bridgestan_src()?, }; + // using path_absolutize crate for now since + // std::fs::canonicalize doesn't behave well on windows + // we may switch to std::path::absolute once it stabilizes, see + // https://github.com/roualdes/bridgestan/pull/212#discussion_r1513375667 let stan_file = stan_file .as_ref() .absolutize() From ec957f7f27b4ca1806d500a17ff3ec27466a8559 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Thu, 7 Mar 2024 19:32:28 +0000 Subject: [PATCH 21/31] rust: fix --include-paths to point to model dir --- rust/examples/example.rs | 3 +++ rust/src/download_compile.rs | 20 ++++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/rust/examples/example.rs b/rust/examples/example.rs index 73698fa0..6cf936d1 100644 --- a/rust/examples/example.rs +++ b/rust/examples/example.rs @@ -3,6 +3,9 @@ use std::ffi::CString; use std::path::Path; fn main() { + 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 diff --git a/rust/src/download_compile.rs b/rust/src/download_compile.rs index 8e3b3daf..e80a329e 100644 --- a/rust/src/download_compile.rs +++ b/rust/src/download_compile.rs @@ -92,6 +92,18 @@ where .absolutize() .map_err(|e| BridgeStanError::ModelCompilingFailed(e.to_string()))?; + // get --include-paths=model_dir + let includir_stan_file_dir = stan_file + .parent() + .and_then(Path::to_str) + .map(|x| format!("--include-paths={x}")) + .map(|x| vec![x]) + .unwrap_or_default(); + let includir_stan_file_dir = includir_stan_file_dir + .iter() + .map(String::as_str) + .collect::>(); + if stan_file.extension().unwrap_or_default() != "stan" { return Err(BridgeStanError::ModelCompilingFailed( "File must be a .stan file".to_owned(), @@ -106,7 +118,7 @@ where )); let output = output.with_extension("so"); - let stanc_args = [["--include-paths=."].as_slice(), stanc_args.as_slice()].concat(); + let stanc_args = [includir_stan_file_dir.as_slice(), stanc_args.as_slice()].concat(); let stanc_args = stanc_args.join(" "); let stanc_args = format!("STANCFLAGS={}", stanc_args); let stanc_args = [stanc_args.as_str()]; @@ -118,12 +130,16 @@ where ] .concat(); - info!("Compiling model"); let make = if cfg!(target_os = "windows") { "mingw32-make" } else { "make" }; + info!( + "Compiling model with command: {} \"{}\"", + make, + cmd.join("\" \"") + ); std::process::Command::new(make) .args(cmd) .current_dir(bs_path) From c9517c80bd7562dc951a04f93e83adc3f79b3c54 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Thu, 7 Mar 2024 19:50:57 +0000 Subject: [PATCH 22/31] rust: disable enum variant feature gating --- rust/src/bs_safe.rs | 5 +---- rust/src/download_compile.rs | 3 +-- rust/src/lib.rs | 2 ++ rust/tests/model.rs | 10 +++++++--- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/rust/src/bs_safe.rs b/rust/src/bs_safe.rs index df441e91..617cf74f 100644 --- a/rust/src/bs_safe.rs +++ b/rust/src/bs_safe.rs @@ -1,6 +1,5 @@ -#[cfg(feature = "compile-stan-model")] -use crate::download_compile::VERSION; use crate::ffi; +use crate::VERSION; use std::borrow::Borrow; use std::collections::hash_map::DefaultHasher; use std::ffi::c_char; @@ -103,11 +102,9 @@ pub enum BridgeStanError { /// Setting a print-callback failed. #[error("Failed to set a print-callback: {0}")] SetCallbackFailed(String), - #[cfg(feature = "compile-stan-model")] /// Setting a compile Stan model failed. #[error("Failed to compile Stan model: {0}")] ModelCompilingFailed(String), - #[cfg(feature = "compile-stan-model")] /// Setting a download BridgeStan failed. #[error("Failed to download BridgeStan {VERSION} from github.com: {0}")] DownloadFailed(String), diff --git a/rust/src/download_compile.rs b/rust/src/download_compile.rs index e80a329e..ad892efe 100644 --- a/rust/src/download_compile.rs +++ b/rust/src/download_compile.rs @@ -1,4 +1,5 @@ use crate::bs_safe::{BridgeStanError, Result}; +use crate::VERSION; use flate2::read::GzDecoder; use log::info; use path_absolutize::Absolutize; @@ -9,8 +10,6 @@ use std::{ }; use tar::Archive; -pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION"); - /// Download and unzip the BridgeStan source distribution for this version /// to ~/.bridgestan/bridgestan-version pub fn get_bridgestan_src() -> Result { diff --git a/rust/src/lib.rs b/rust/src/lib.rs index fd621913..6a08330f 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -10,3 +10,5 @@ pub use bs_safe::{open_library, BridgeStanError, Model, Rng, StanLibrary}; #[cfg(feature = "compile-stan-model")] pub use download_compile::{compile_model, get_bridgestan_src}; + +pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/rust/tests/model.rs b/rust/tests/model.rs index cae5f32a..0fca0dc9 100644 --- a/rust/tests/model.rs +++ b/rust/tests/model.rs @@ -1,7 +1,7 @@ mod common; -use std::{f64::consts::PI, ffi::CString, fs::remove_file}; +use std::{f64::consts::PI, ffi::CString}; -use common::{get_model, model_dir}; +use common::get_model; use approx::{assert_abs_diff_eq, assert_ulps_eq}; @@ -48,13 +48,17 @@ fn logp_gradient() { #[test] #[ignore] fn model_compiling() { + use bridgestan::compile_model; + use common::model_dir; + use std::fs::remove_file; + let name = "stdnormal"; let mut base = model_dir(); base.push(name); let lib_path = base.join(format!("{}_model.so", name)); let stan_path = base.join(format!("{}.stan", name)); remove_file(lib_path).unwrap(); - bridgestan::compile_model(stan_path, vec![], vec![], None).unwrap(); + compile_model(stan_path, vec![], vec![], None).unwrap(); let (lib, data) = get_model(name); let model = Model::new(&lib, data, 42).unwrap(); From 8a644723b5e306188c7065292b113987fef25c3a Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Thu, 7 Mar 2024 21:36:40 +0000 Subject: [PATCH 23/31] rust: fix macos build --- .github/workflows/main.yaml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index b14eb906..f877d770 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -269,12 +269,6 @@ jobs: path: ./test_models/ key: ${{ hashFiles('**/*.stan', 'src/*', 'stan/src/stan/version.hpp', 'Makefile') }}-${{ matrix.os }}-v${{ env.CACHE_VERSION }} - - name: Install LLVM and Clang - uses: KyleMayes/install-llvm-action@v1 - with: - version: "15.0" - directory: ${{ runner.temp }}/llvm - - name: Set up TBB if: matrix.os == 'windows-latest' run: | @@ -283,9 +277,6 @@ jobs: - name: Run rust tests working-directory: ./rust timeout-minutes: 60 - env: - LIBCLANG_PATH: ${{ runner.temp }}/llvm/lib - LLVM_CONFIG_PATH: ${{ runner.temp }}/llvm/bin/llvm-config run: | cargo clippy cargo fmt --check From d6851b86572ef0f21225ea2510a2cffd0ce11d45 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Sat, 9 Mar 2024 21:38:14 +0000 Subject: [PATCH 24/31] rust: make bridgestan src download more explicit --- rust/README.md | 5 +++-- rust/examples/example.rs | 5 +++-- rust/src/download_compile.rs | 12 +++--------- rust/src/lib.rs | 2 +- rust/tests/model.rs | 5 +++-- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/rust/README.md b/rust/README.md index caf15b43..ff210bfe 100644 --- a/rust/README.md +++ b/rust/README.md @@ -36,7 +36,7 @@ Run this example with `RUST_LOG=info cargo run --example=example --features comp ```rust use std::ffi::CString; use std::path::Path; -use bridgestan::{BridgeStanError, Model, open_library, compile_model}; +use bridgestan::{BridgeStanError, Model, open_library, compile_model, download_bridgestan_src}; // The path to the Stan model let path = Path::new(env!["CARGO_MANIFEST_DIR"]) @@ -44,8 +44,9 @@ let path = Path::new(env!["CARGO_MANIFEST_DIR"]) .unwrap() .join("test_models/simple/simple.stan"); +let bs_path = download_bridgestan_src().unwrap(); // The path to the compiled model -let path = compile_model(path, vec![], vec![], None).expect("Could not compile Stan model."); +let path = compile_model(bs_path, path, vec![], vec![]).expect("Could not compile Stan model."); println!("Compiled model: {:?}", path); let lib = open_library(path).expect("Could not load compiled Stan model."); diff --git a/rust/examples/example.rs b/rust/examples/example.rs index 6cf936d1..fe710a78 100644 --- a/rust/examples/example.rs +++ b/rust/examples/example.rs @@ -1,4 +1,4 @@ -use bridgestan::{compile_model, open_library, BridgeStanError, Model}; +use bridgestan::{compile_model, download_bridgestan_src, open_library, BridgeStanError, Model}; use std::ffi::CString; use std::path::Path; @@ -16,8 +16,9 @@ fn main() { .join("simple") .join("simple.stan"); + let bs_path = download_bridgestan_src().unwrap(); // The path to the compiled model - let path = compile_model(path, vec![], vec![], None).expect("Could not compile Stan model."); + let path = compile_model(bs_path, path, vec![], vec![]).expect("Could not compile Stan model."); println!("Compiled model: {:?}", path); let lib = open_library(path).expect("Could not load compiled Stan model."); diff --git a/rust/src/download_compile.rs b/rust/src/download_compile.rs index ad892efe..ed04c13f 100644 --- a/rust/src/download_compile.rs +++ b/rust/src/download_compile.rs @@ -12,7 +12,7 @@ use tar::Archive; /// Download and unzip the BridgeStan source distribution for this version /// to ~/.bridgestan/bridgestan-version -pub fn get_bridgestan_src() -> Result { +pub fn download_bridgestan_src() -> Result { let homedir = dirs::home_dir().unwrap_or(temp_dir()); let bs_path_download_temp = homedir.join(".bridgestan_tmp_dir"); @@ -66,22 +66,16 @@ pub fn get_bridgestan_src() -> Result { Ok(bs_path_download_join_version) } -/// Compile a Stan Model given a stan_file and the path to BridgeStan -/// if None, then calls get_bridgestan_src() to download BridgeStan +/// Compile a Stan Model given the path to BridgeStan and to a stan_file pub fn compile_model

( + bs_path: P, stan_file: P, stanc_args: Vec<&str>, make_args: Vec<&str>, - bs_path: Option

, ) -> Result where P: AsRef, { - let bs_path = match bs_path { - Some(path) => path.as_ref().to_owned(), - None => get_bridgestan_src()?, - }; - // using path_absolutize crate for now since // std::fs::canonicalize doesn't behave well on windows // we may switch to std::path::absolute once it stabilizes, see diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 6a08330f..8cd2108c 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -9,6 +9,6 @@ pub(crate) mod ffi; pub use bs_safe::{open_library, BridgeStanError, Model, Rng, StanLibrary}; #[cfg(feature = "compile-stan-model")] -pub use download_compile::{compile_model, get_bridgestan_src}; +pub use download_compile::{compile_model, download_bridgestan_src}; pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/rust/tests/model.rs b/rust/tests/model.rs index 0fca0dc9..41fb68d9 100644 --- a/rust/tests/model.rs +++ b/rust/tests/model.rs @@ -48,7 +48,7 @@ fn logp_gradient() { #[test] #[ignore] fn model_compiling() { - use bridgestan::compile_model; + use bridgestan::{compile_model, download_bridgestan_src}; use common::model_dir; use std::fs::remove_file; @@ -58,7 +58,8 @@ fn model_compiling() { let lib_path = base.join(format!("{}_model.so", name)); let stan_path = base.join(format!("{}.stan", name)); remove_file(lib_path).unwrap(); - compile_model(stan_path, vec![], vec![], None).unwrap(); + let bs_path = download_bridgestan_src().unwrap(); + compile_model(bs_path, stan_path, vec![], vec![]).unwrap(); let (lib, data) = get_model(name); let model = Model::new(&lib, data, 42).unwrap(); From 837fc04a34312a6073427f640889a154d7eb180f Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Sat, 23 Mar 2024 08:46:06 +0000 Subject: [PATCH 25/31] rust: only bridgestan_download_src is to be feature gated --- .github/workflows/main.yaml | 12 +++- rust/.gitignore | 1 + rust/Cargo.toml | 9 +-- rust/README.md | 4 +- rust/src/{download_compile.rs => compile.rs} | 65 +------------------- rust/src/download.rs | 62 +++++++++++++++++++ rust/src/lib.rs | 13 ++-- rust/tests/model.rs | 39 +++++++++++- 8 files changed, 126 insertions(+), 79 deletions(-) create mode 100644 rust/.gitignore rename rust/src/{download_compile.rs => compile.rs} (55%) create mode 100644 rust/src/download.rs diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index f877d770..98d31df2 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -280,7 +280,13 @@ jobs: run: | cargo clippy cargo fmt --check - cargo run --example=example --features compile-stan-model - cargo test --verbose --features compile-stan-model - cargo test --verbose --features compile-stan-model model_compiling -- --ignored + cargo run --example=example --features download-bridgestan-src + + # run all tests except docs and model_downloading_and_compiling cargo test --verbose --all-targets + cargo test --verbose model_compiling -- --ignored + + # run all tests with feature download-bridgestan-src + cargo test --verbose --features download-bridgestan-src + cargo test --verbose --features download-bridgestan-src model_downloading_and_compiling -- --ignored + cargo test --verbose --features download-bridgestan-src model_compiling -- --ignored diff --git a/rust/.gitignore b/rust/.gitignore new file mode 100644 index 00000000..722d5e71 --- /dev/null +++ b/rust/.gitignore @@ -0,0 +1 @@ +.vscode diff --git a/rust/Cargo.toml b/rust/Cargo.toml index c967edc0..0d05e83b 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -15,11 +15,11 @@ ureq = { version = "2.7", optional = true } tar = { version = "0.4", optional = true } flate2 = { version = "1.0", optional = true } dirs = { version = "5.0", optional = true } -path-absolutize = { version = "3.1", optional = true } -log = { version = "0.4", optional = true } +path-absolutize = { version = "3.1" } +log = { version = "0.4" } [features] -compile-stan-model = ["dep:ureq", "dep:tar", "dep:flate2", "dep:dirs", "dep:path-absolutize", "dep:log"] +download-bridgestan-src = ["dep:ureq", "dep:tar", "dep:flate2", "dep:dirs"] [build-dependencies] bindgen = "0.69.1" @@ -28,7 +28,8 @@ bindgen = "0.69.1" approx = "0.5.1" rand = "0.8.5" env_logger = "0.11" +dirs = { version = "5.0" } [[example]] name = "example" -required-features = ["compile-stan-model"] +required-features = ["download-bridgestan-src"] diff --git a/rust/README.md b/rust/README.md index ff210bfe..ead8eb38 100644 --- a/rust/README.md +++ b/rust/README.md @@ -11,7 +11,7 @@ Internally, it relies on [`bindgen`](https://docs.rs/bindgen/) and ## Compiling the model -The Rust wrapper currently have a builtin functionality to compile Stan models (function `compile_model` under the feature flag `compile-stan-model`). For safety reasons all Stan models need to be installed with `STAN_THREADS=true`. However, if you use the Rust wrapper builtin functionality to compile Stan models, this will automatically be set for you. +The Rust wrapper currently have a builtin functionality to compile Stan models (function `compile_model` under the feature flag `download-bridgestan-src`). For safety reasons all Stan models need to be installed with `STAN_THREADS=true`. However, if you use the Rust wrapper builtin functionality to compile Stan models, this will automatically be set for you. When compiling a model using `make`, set the environment variable: @@ -31,7 +31,7 @@ will throw an error when loading the model. ## Usage -Run this example with `RUST_LOG=info cargo run --example=example --features compile-stan-model`. +Run this example with `RUST_LOG=info cargo run --example=example --features download-bridgestan-src`. ```rust use std::ffi::CString; diff --git a/rust/src/download_compile.rs b/rust/src/compile.rs similarity index 55% rename from rust/src/download_compile.rs rename to rust/src/compile.rs index ed04c13f..08ef510c 100644 --- a/rust/src/download_compile.rs +++ b/rust/src/compile.rs @@ -1,70 +1,7 @@ use crate::bs_safe::{BridgeStanError, Result}; -use crate::VERSION; -use flate2::read::GzDecoder; use log::info; use path_absolutize::Absolutize; -use std::{ - env::temp_dir, - fs, - path::{Path, PathBuf}, -}; -use tar::Archive; - -/// Download and unzip the BridgeStan source distribution for this version -/// to ~/.bridgestan/bridgestan-version -pub fn download_bridgestan_src() -> Result { - let homedir = dirs::home_dir().unwrap_or(temp_dir()); - - let bs_path_download_temp = homedir.join(".bridgestan_tmp_dir"); - let bs_path_download = homedir.join(".bridgestan"); - - let bs_path_download_temp_join_version = - bs_path_download_temp.join(format!("bridgestan-{VERSION}")); - let bs_path_download_join_version = bs_path_download.join(format!("bridgestan-{VERSION}")); - - if !bs_path_download_join_version.exists() { - info!("Downloading BridgeStan"); - - fs::remove_dir_all(&bs_path_download_temp).unwrap_or_default(); - fs::create_dir(&bs_path_download_temp).unwrap_or_default(); - fs::create_dir(&bs_path_download).unwrap_or_default(); - - let url = "https://github.com/roualdes/bridgestan/releases/download/".to_owned() - + format!("v{VERSION}/bridgestan-{VERSION}.tar.gz").as_str(); - - let response = ureq::get(url.as_str()) - .call() - .map_err(|e| BridgeStanError::DownloadFailed(e.to_string()))?; - let len = response - .header("Content-Length") - .and_then(|s| s.parse::().ok()) - .unwrap_or(50_000_000); - - let mut bytes: Vec = Vec::with_capacity(len); - response - .into_reader() - .read_to_end(&mut bytes) - .map_err(|e| BridgeStanError::DownloadFailed(e.to_string()))?; - - let tar = GzDecoder::new(bytes.as_slice()); - let mut archive = Archive::new(tar); - archive - .unpack(&bs_path_download_temp) - .map_err(|e| BridgeStanError::DownloadFailed(e.to_string()))?; - - fs::rename( - bs_path_download_temp_join_version, - &bs_path_download_join_version, - ) - .map_err(|e| BridgeStanError::DownloadFailed(e.to_string()))?; - - fs::remove_dir(bs_path_download_temp).unwrap_or_default(); - - info!("Finished downloading BridgeStan"); - } - - Ok(bs_path_download_join_version) -} +use std::path::{Path, PathBuf}; /// Compile a Stan Model given the path to BridgeStan and to a stan_file pub fn compile_model

( diff --git a/rust/src/download.rs b/rust/src/download.rs new file mode 100644 index 00000000..39f1c150 --- /dev/null +++ b/rust/src/download.rs @@ -0,0 +1,62 @@ +use crate::bs_safe::{BridgeStanError, Result}; +use crate::VERSION; +use flate2::read::GzDecoder; +use log::info; +use std::{env::temp_dir, fs, path::PathBuf}; +use tar::Archive; + +/// Download and unzip the BridgeStan source distribution for this version +/// to ~/.bridgestan/bridgestan-version +pub fn download_bridgestan_src() -> Result { + let homedir = dirs::home_dir().unwrap_or(temp_dir()); + + let bs_path_download_temp = homedir.join(".bridgestan_tmp_dir"); + let bs_path_download = homedir.join(".bridgestan"); + + let bs_path_download_temp_join_version = + bs_path_download_temp.join(format!("bridgestan-{VERSION}")); + let bs_path_download_join_version = bs_path_download.join(format!("bridgestan-{VERSION}")); + + if !bs_path_download_join_version.exists() { + info!("Downloading BridgeStan"); + + fs::remove_dir_all(&bs_path_download_temp).unwrap_or_default(); + fs::create_dir(&bs_path_download_temp).unwrap_or_default(); + fs::create_dir(&bs_path_download).unwrap_or_default(); + + let url = "https://github.com/roualdes/bridgestan/releases/download/".to_owned() + + format!("v{VERSION}/bridgestan-{VERSION}.tar.gz").as_str(); + + let response = ureq::get(url.as_str()) + .call() + .map_err(|e| BridgeStanError::DownloadFailed(e.to_string()))?; + let len = response + .header("Content-Length") + .and_then(|s| s.parse::().ok()) + .unwrap_or(50_000_000); + + let mut bytes: Vec = Vec::with_capacity(len); + response + .into_reader() + .read_to_end(&mut bytes) + .map_err(|e| BridgeStanError::DownloadFailed(e.to_string()))?; + + let tar = GzDecoder::new(bytes.as_slice()); + let mut archive = Archive::new(tar); + archive + .unpack(&bs_path_download_temp) + .map_err(|e| BridgeStanError::DownloadFailed(e.to_string()))?; + + fs::rename( + bs_path_download_temp_join_version, + &bs_path_download_join_version, + ) + .map_err(|e| BridgeStanError::DownloadFailed(e.to_string()))?; + + fs::remove_dir(bs_path_download_temp).unwrap_or_default(); + + info!("Finished downloading BridgeStan"); + } + + Ok(bs_path_download_join_version) +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 8cd2108c..2af84cdb 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,14 +1,17 @@ #![doc = include_str!("../README.md")] mod bs_safe; +mod compile; -#[cfg(feature = "compile-stan-model")] -mod download_compile; +#[cfg(feature = "download-bridgestan-src")] +mod download; pub(crate) mod ffi; pub use bs_safe::{open_library, BridgeStanError, Model, Rng, StanLibrary}; -#[cfg(feature = "compile-stan-model")] -pub use download_compile::{compile_model, download_bridgestan_src}; +#[cfg(feature = "download-bridgestan-src")] +pub use download::download_bridgestan_src; -pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION"); +pub use compile::compile_model; + +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/rust/tests/model.rs b/rust/tests/model.rs index 41fb68d9..33b1e412 100644 --- a/rust/tests/model.rs +++ b/rust/tests/model.rs @@ -43,11 +43,48 @@ fn logp_gradient() { assert_ulps_eq!(grad[0], -1f64); } -#[cfg(feature = "compile-stan-model")] +// Note: this test does not require download-bridgestan-src feature +// but it assumes that bridgestan_src is on the home folder +// one way to have that assumption met is by running the example or +// the model_downloading_and_compiling test before running this test #[cfg(target_family = "unix")] #[test] #[ignore] fn model_compiling() { + use bridgestan::compile_model; + use common::model_dir; + use std::fs::remove_file; + + let name = "stdnormal"; + let mut base = model_dir(); + base.push(name); + let lib_path = base.join(format!("{}_model.so", name)); + let stan_path = base.join(format!("{}.stan", name)); + remove_file(lib_path).unwrap(); + + let homedir = dirs::home_dir().unwrap(); + let bs_path_download = homedir.join(".bridgestan"); + let bs_path_download_join_version = + bs_path_download.join(format!("bridgestan-{}", bridgestan::VERSION)); + + compile_model(bs_path_download_join_version, stan_path, vec![], vec![]).unwrap(); + + let (lib, data) = get_model(name); + let model = Model::new(&lib, data, 42).unwrap(); + let theta = [1f64]; + let mut grad = [0f64]; + let logp = model + .log_density_gradient(&theta[..], false, true, &mut grad[..]) + .unwrap(); + assert_ulps_eq!(logp, (2. * PI).sqrt().recip().ln() - 0.5); + assert_ulps_eq!(grad[0], -1f64); +} + +#[cfg(feature = "download-bridgestan-src")] +#[cfg(target_family = "unix")] +#[test] +#[ignore] +fn model_downloading_and_compiling() { use bridgestan::{compile_model, download_bridgestan_src}; use common::model_dir; use std::fs::remove_file; From 1948a89f64095ad2022ea5eba9974e9323002abf Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Sat, 23 Mar 2024 13:27:08 +0000 Subject: [PATCH 26/31] unify .gitignore --- .gitignore | 1 + rust/.gitignore | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 rust/.gitignore diff --git a/.gitignore b/.gitignore index d52c9093..ac21f96d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ c-example/example_static # Rust rust/target/ rust/Cargo.lock +rust/.vscode notes.org diff --git a/rust/.gitignore b/rust/.gitignore deleted file mode 100644 index 722d5e71..00000000 --- a/rust/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.vscode From fb29536814e0045f33a8508a6e051ed32d209822 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Sat, 6 Apr 2024 11:02:45 +0100 Subject: [PATCH 27/31] test improvements --- .github/workflows/main.yaml | 4 ++-- rust/README.md | 8 ++++++-- rust/examples/example.rs | 8 ++++++-- rust/tests/model.rs | 38 +++++++++++++------------------------ 4 files changed, 27 insertions(+), 31 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 98d31df2..04a2d496 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -282,11 +282,11 @@ jobs: cargo fmt --check cargo run --example=example --features download-bridgestan-src - # run all tests except docs and model_downloading_and_compiling + # run all tests except docs and model_downloading cargo test --verbose --all-targets cargo test --verbose model_compiling -- --ignored # run all tests with feature download-bridgestan-src cargo test --verbose --features download-bridgestan-src - cargo test --verbose --features download-bridgestan-src model_downloading_and_compiling -- --ignored + cargo test --verbose --features download-bridgestan-src model_downloading -- --ignored cargo test --verbose --features download-bridgestan-src model_compiling -- --ignored diff --git a/rust/README.md b/rust/README.md index ead8eb38..7204dcb3 100644 --- a/rust/README.md +++ b/rust/README.md @@ -36,7 +36,7 @@ Run this example with `RUST_LOG=info cargo run --example=example --features down ```rust use std::ffi::CString; use std::path::Path; -use bridgestan::{BridgeStanError, Model, open_library, compile_model, download_bridgestan_src}; +use bridgestan::{BridgeStanError, Model, open_library, compile_model}; // The path to the Stan model let path = Path::new(env!["CARGO_MANIFEST_DIR"]) @@ -44,7 +44,11 @@ let path = Path::new(env!["CARGO_MANIFEST_DIR"]) .unwrap() .join("test_models/simple/simple.stan"); -let bs_path = download_bridgestan_src().unwrap(); +// You can manually set the BridgeStan src path or +// automatically download it +let bs_path = "..".into(); +// let bs_path = bridgestan::download_bridgestan_src().unwrap(); + // The path to the compiled model let path = compile_model(bs_path, path, vec![], vec![]).expect("Could not compile Stan model."); println!("Compiled model: {:?}", path); diff --git a/rust/examples/example.rs b/rust/examples/example.rs index fe710a78..4a851c0d 100644 --- a/rust/examples/example.rs +++ b/rust/examples/example.rs @@ -1,4 +1,4 @@ -use bridgestan::{compile_model, download_bridgestan_src, open_library, BridgeStanError, Model}; +use bridgestan::{compile_model, open_library, BridgeStanError, Model}; use std::ffi::CString; use std::path::Path; @@ -16,7 +16,11 @@ fn main() { .join("simple") .join("simple.stan"); - let bs_path = download_bridgestan_src().unwrap(); + // You can manually set the BridgeStan src path or + // automatically download it + let bs_path = "..".into(); + // let bs_path = bridgestan::download_bridgestan_src().unwrap(); + // The path to the compiled model let path = compile_model(bs_path, path, vec![], vec![]).expect("Could not compile Stan model."); println!("Compiled model: {:?}", path); diff --git a/rust/tests/model.rs b/rust/tests/model.rs index 33b1e412..8be6673d 100644 --- a/rust/tests/model.rs +++ b/rust/tests/model.rs @@ -60,12 +60,15 @@ fn model_compiling() { base.push(name); let lib_path = base.join(format!("{}_model.so", name)); let stan_path = base.join(format!("{}.stan", name)); - remove_file(lib_path).unwrap(); + remove_file(lib_path).unwrap_or_default(); let homedir = dirs::home_dir().unwrap(); - let bs_path_download = homedir.join(".bridgestan"); - let bs_path_download_join_version = - bs_path_download.join(format!("bridgestan-{}", bridgestan::VERSION)); + let bs_path_download_join_version = std::env::var("BRIDGESTAN") + .map(|x| x.into()) + .unwrap_or_else(|_| { + let bs_path_download = homedir.join(".bridgestan"); + bs_path_download.join(format!("bridgestan-{}", bridgestan::VERSION)) + }); compile_model(bs_path_download_join_version, stan_path, vec![], vec![]).unwrap(); @@ -84,29 +87,14 @@ fn model_compiling() { #[cfg(target_family = "unix")] #[test] #[ignore] -fn model_downloading_and_compiling() { - use bridgestan::{compile_model, download_bridgestan_src}; - use common::model_dir; - use std::fs::remove_file; +fn model_downloading() { + use bridgestan::download_bridgestan_src; - let name = "stdnormal"; - let mut base = model_dir(); - base.push(name); - let lib_path = base.join(format!("{}_model.so", name)); - let stan_path = base.join(format!("{}.stan", name)); - remove_file(lib_path).unwrap(); let bs_path = download_bridgestan_src().unwrap(); - compile_model(bs_path, stan_path, vec![], vec![]).unwrap(); - - let (lib, data) = get_model(name); - let model = Model::new(&lib, data, 42).unwrap(); - let theta = [1f64]; - let mut grad = [0f64]; - let logp = model - .log_density_gradient(&theta[..], false, true, &mut grad[..]) - .unwrap(); - assert_ulps_eq!(logp, (2. * PI).sqrt().recip().ln() - 0.5); - assert_ulps_eq!(grad[0], -1f64); + let stan_path = bs_path.join("stan"); + assert!(stan_path.is_dir()); + let makefile_path = bs_path.join("Makefile"); + assert!(makefile_path.is_file()); } #[test] From 08a9215732cc5f956a7d52c2412d8d66e98ef028 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Sat, 6 Apr 2024 11:21:00 +0100 Subject: [PATCH 28/31] remove asref generic --- .github/workflows/main.yaml | 4 ++-- rust/README.md | 6 +++--- rust/examples/example.rs | 7 ++++--- rust/src/compile.rs | 12 ++++-------- rust/tests/model.rs | 2 +- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 04a2d496..06613f92 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -282,8 +282,8 @@ jobs: cargo fmt --check cargo run --example=example --features download-bridgestan-src - # run all tests except docs and model_downloading - cargo test --verbose --all-targets + # run all tests except model_downloading + cargo test --verbose cargo test --verbose model_compiling -- --ignored # run all tests with feature download-bridgestan-src diff --git a/rust/README.md b/rust/README.md index 7204dcb3..3d321df7 100644 --- a/rust/README.md +++ b/rust/README.md @@ -35,7 +35,7 @@ Run this example with `RUST_LOG=info cargo run --example=example --features down ```rust use std::ffi::CString; -use std::path::Path; +use std::path::{Path, PathBuf}; use bridgestan::{BridgeStanError, Model, open_library, compile_model}; // The path to the Stan model @@ -46,11 +46,11 @@ let path = Path::new(env!["CARGO_MANIFEST_DIR"]) // You can manually set the BridgeStan src path or // automatically download it -let bs_path = "..".into(); +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, vec![], vec![]).expect("Could not compile Stan model."); +let path = compile_model(&bs_path, &path, vec![], vec![]).expect("Could not compile Stan model."); println!("Compiled model: {:?}", path); let lib = open_library(path).expect("Could not load compiled Stan model."); diff --git a/rust/examples/example.rs b/rust/examples/example.rs index 4a851c0d..b713dbc4 100644 --- a/rust/examples/example.rs +++ b/rust/examples/example.rs @@ -1,6 +1,6 @@ use bridgestan::{compile_model, open_library, BridgeStanError, Model}; use std::ffi::CString; -use std::path::Path; +use std::path::{Path, PathBuf}; fn main() { if std::env::var("RUST_LOG").is_err() { @@ -18,11 +18,12 @@ fn main() { // You can manually set the BridgeStan src path or // automatically download it - let bs_path = "..".into(); + 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, vec![], vec![]).expect("Could not compile Stan model."); + let path = + compile_model(&bs_path, &path, vec![], vec![]).expect("Could not compile Stan model."); println!("Compiled model: {:?}", path); let lib = open_library(path).expect("Could not load compiled Stan model."); diff --git a/rust/src/compile.rs b/rust/src/compile.rs index 08ef510c..6112f345 100644 --- a/rust/src/compile.rs +++ b/rust/src/compile.rs @@ -4,21 +4,17 @@ use path_absolutize::Absolutize; use std::path::{Path, PathBuf}; /// Compile a Stan Model given the path to BridgeStan and to a stan_file -pub fn compile_model

( - bs_path: P, - stan_file: P, +pub fn compile_model( + bs_path: &Path, + stan_file: &Path, stanc_args: Vec<&str>, make_args: Vec<&str>, -) -> Result -where - P: AsRef, -{ +) -> Result { // using path_absolutize crate for now since // std::fs::canonicalize doesn't behave well on windows // we may switch to std::path::absolute once it stabilizes, see // https://github.com/roualdes/bridgestan/pull/212#discussion_r1513375667 let stan_file = stan_file - .as_ref() .absolutize() .map_err(|e| BridgeStanError::ModelCompilingFailed(e.to_string()))?; diff --git a/rust/tests/model.rs b/rust/tests/model.rs index 8be6673d..536269ac 100644 --- a/rust/tests/model.rs +++ b/rust/tests/model.rs @@ -70,7 +70,7 @@ fn model_compiling() { bs_path_download.join(format!("bridgestan-{}", bridgestan::VERSION)) }); - compile_model(bs_path_download_join_version, stan_path, vec![], vec![]).unwrap(); + compile_model(&bs_path_download_join_version, &stan_path, vec![], vec![]).unwrap(); let (lib, data) = get_model(name); let model = Model::new(&lib, data, 42).unwrap(); From 3de69b90fbde234fc5c02fc3a78b8c64caaac1b8 Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Sat, 6 Apr 2024 11:41:00 +0100 Subject: [PATCH 29/31] fix tests --- rust/Cargo.toml | 1 - rust/README.md | 3 ++- rust/examples/example.rs | 3 ++- rust/tests/model.rs | 11 ++++------- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 714e5fc2..b395d6d0 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -32,4 +32,3 @@ dirs = { version = "5.0" } [[example]] name = "example" -required-features = ["download-bridgestan-src"] diff --git a/rust/README.md b/rust/README.md index 3d321df7..aa52cda6 100644 --- a/rust/README.md +++ b/rust/README.md @@ -45,7 +45,8 @@ let path = Path::new(env!["CARGO_MANIFEST_DIR"]) .join("test_models/simple/simple.stan"); // You can manually set the BridgeStan src path or -// automatically download it +// 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(); diff --git a/rust/examples/example.rs b/rust/examples/example.rs index b713dbc4..cf37d735 100644 --- a/rust/examples/example.rs +++ b/rust/examples/example.rs @@ -17,7 +17,8 @@ fn main() { .join("simple.stan"); // You can manually set the BridgeStan src path or - // automatically download it + // 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(); diff --git a/rust/tests/model.rs b/rust/tests/model.rs index 536269ac..0abb407a 100644 --- a/rust/tests/model.rs +++ b/rust/tests/model.rs @@ -54,6 +54,7 @@ fn model_compiling() { use bridgestan::compile_model; use common::model_dir; use std::fs::remove_file; + use std::path::PathBuf; let name = "stdnormal"; let mut base = model_dir(); @@ -62,13 +63,9 @@ fn model_compiling() { let stan_path = base.join(format!("{}.stan", name)); remove_file(lib_path).unwrap_or_default(); - let homedir = dirs::home_dir().unwrap(); - let bs_path_download_join_version = std::env::var("BRIDGESTAN") - .map(|x| x.into()) - .unwrap_or_else(|_| { - let bs_path_download = homedir.join(".bridgestan"); - bs_path_download.join(format!("bridgestan-{}", bridgestan::VERSION)) - }); + let bs_path_download_join_version: PathBuf = std::env::var("BRIDGESTAN") + .unwrap_or("..".to_string()) + .into(); compile_model(&bs_path_download_join_version, &stan_path, vec![], vec![]).unwrap(); From 39f910801a7dd3f2ef741c0bda3ae9e7838d555c Mon Sep 17 00:00:00 2001 From: Marco Inacio Date: Sat, 13 Apr 2024 09:11:11 +0100 Subject: [PATCH 30/31] Update model.rs --- rust/tests/model.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/rust/tests/model.rs b/rust/tests/model.rs index 0abb407a..166596b8 100644 --- a/rust/tests/model.rs +++ b/rust/tests/model.rs @@ -47,7 +47,6 @@ fn logp_gradient() { // but it assumes that bridgestan_src is on the home folder // one way to have that assumption met is by running the example or // the model_downloading_and_compiling test before running this test -#[cfg(target_family = "unix")] #[test] #[ignore] fn model_compiling() { @@ -81,7 +80,6 @@ fn model_compiling() { } #[cfg(feature = "download-bridgestan-src")] -#[cfg(target_family = "unix")] #[test] #[ignore] fn model_downloading() { From f836e895562081d695ce00a928cdaf8385d90bf2 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Wed, 1 May 2024 16:14:21 -0400 Subject: [PATCH 31/31] Clean up Rust doc, tests --- .github/workflows/main.yaml | 11 ++--- rust/Cargo.toml | 5 +- rust/README.md | 32 +++++-------- rust/examples/example.rs | 4 +- rust/src/bs_safe.rs | 4 +- rust/src/compile.rs | 35 +++++++------- rust/src/download.rs | 2 +- rust/src/lib.rs | 6 +-- rust/tests/model.rs | 95 ++++++++++++++++++------------------- 9 files changed, 89 insertions(+), 105 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index a2952ef5..4a51d15e 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -280,13 +280,8 @@ jobs: run: | cargo clippy cargo fmt --check - cargo run --example=example --features download-bridgestan-src - - # run all tests except model_downloading - cargo test --verbose - cargo test --verbose model_compiling -- --ignored + cargo run --example=example # run all tests with feature download-bridgestan-src - cargo test --verbose --features download-bridgestan-src - cargo test --verbose --features download-bridgestan-src model_downloading -- --ignored - cargo test --verbose --features download-bridgestan-src model_compiling -- --ignored + cargo test --verbose --all-features + cargo test --verbose model_compiling -- --ignored diff --git a/rust/Cargo.toml b/rust/Cargo.toml index b395d6d0..36c791a9 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -11,12 +11,12 @@ homepage = "https://roualdes.github.io/bridgestan/latest/" [dependencies] libloading = "0.8.0" thiserror = "1.0.40" +path-absolutize = { version = "3.1" } +log = { version = "0.4" } ureq = { version = "2.7", optional = true } tar = { version = "0.4", optional = true } flate2 = { version = "1.0", optional = true } dirs = { version = "5.0", optional = true } -path-absolutize = { version = "3.1" } -log = { version = "0.4" } [features] download-bridgestan-src = ["dep:ureq", "dep:tar", "dep:flate2", "dep:dirs"] @@ -28,7 +28,6 @@ bindgen = "0.69.1" approx = "0.5.1" rand = "0.8.5" env_logger = "0.11" -dirs = { version = "5.0" } [[example]] name = "example" diff --git a/rust/README.md b/rust/README.md index aa52cda6..567aaea7 100644 --- a/rust/README.md +++ b/rust/README.md @@ -11,27 +11,19 @@ Internally, it relies on [`bindgen`](https://docs.rs/bindgen/) and ## Compiling the model -The Rust wrapper currently have a builtin functionality to compile Stan models (function `compile_model` under the feature flag `download-bridgestan-src`). For safety reasons all Stan models need to be installed with `STAN_THREADS=true`. However, if you use the Rust wrapper builtin functionality to compile Stan models, this will automatically be set for you. +The Rust wrapper has the ability to compile Stan models by invoking the `make` command through the [`compile_model`] function. -When compiling a model using `make`, set the environment variable: +This requires a C++ toolchain and a copy of the BridgeStan source code. The source code can be downloaded automatically by enabling the `download-bridgestan-src` feature and calling [`download_bridgestan_src`]. Alternatively, the path to the BridgeStan source code can be provided manually. -```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"]) -``` +For safety reasons all Stan models need to be built with `STAN_THREADS=true`. This is the default behavior in the `compile_model` function, +but may need to be set manually when compiling the model in other contexts. If `STAN_THREADS` was not specified while building the model, the Rust wrapper will throw an error when loading the model. ## Usage -Run this example with `RUST_LOG=info cargo run --example=example --features download-bridgestan-src`. +Run this example with `cargo run --example=example`. ```rust use std::ffi::CString; @@ -51,7 +43,7 @@ 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, vec![], vec![]).expect("Could not compile Stan 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."); @@ -65,11 +57,13 @@ let data = CString::new(data.to_string().into_bytes()).unwrap(); 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!("Unexpected error") }, + 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(); diff --git a/rust/examples/example.rs b/rust/examples/example.rs index cf37d735..b1cac0b9 100644 --- a/rust/examples/example.rs +++ b/rust/examples/example.rs @@ -3,6 +3,7 @@ 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"); } @@ -23,8 +24,7 @@ fn main() { // let bs_path = bridgestan::download_bridgestan_src().unwrap(); // The path to the compiled model - let path = - compile_model(&bs_path, &path, vec![], vec![]).expect("Could not compile Stan 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."); diff --git a/rust/src/bs_safe.rs b/rust/src/bs_safe.rs index 617cf74f..b71c46c4 100644 --- a/rust/src/bs_safe.rs +++ b/rust/src/bs_safe.rs @@ -102,10 +102,10 @@ pub enum BridgeStanError { /// Setting a print-callback failed. #[error("Failed to set a print-callback: {0}")] SetCallbackFailed(String), - /// Setting a compile Stan model failed. + /// Compilation of the Stan model shared object failed. #[error("Failed to compile Stan model: {0}")] ModelCompilingFailed(String), - /// Setting a download BridgeStan failed. + /// Downloading BridgeStan's C++ source code from GitHub failed. #[error("Failed to download BridgeStan {VERSION} from github.com: {0}")] DownloadFailed(String), } diff --git a/rust/src/compile.rs b/rust/src/compile.rs index 6112f345..7ba3c715 100644 --- a/rust/src/compile.rs +++ b/rust/src/compile.rs @@ -3,12 +3,21 @@ use log::info; use path_absolutize::Absolutize; use std::path::{Path, PathBuf}; -/// Compile a Stan Model given the path to BridgeStan and to a stan_file +const MAKE: &str = if cfg!(target_os = "windows") { + "mingw32-make" +} else { + "make" +}; + +/// Compile a Stan Model. Requires a path to the BridgeStan sources (can be +/// downloaded with [`download_bridgestan_src`](crate::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. pub fn compile_model( bs_path: &Path, stan_file: &Path, - stanc_args: Vec<&str>, - make_args: Vec<&str>, + stanc_args: &[&str], + make_args: &[&str], ) -> Result { // using path_absolutize crate for now since // std::fs::canonicalize doesn't behave well on windows @@ -23,12 +32,9 @@ pub fn compile_model( .parent() .and_then(Path::to_str) .map(|x| format!("--include-paths={x}")) - .map(|x| vec![x]) .unwrap_or_default(); - let includir_stan_file_dir = includir_stan_file_dir - .iter() - .map(String::as_str) - .collect::>(); + + let includir_stan_file_dir = includir_stan_file_dir.as_str(); if stan_file.extension().unwrap_or_default() != "stan" { return Err(BridgeStanError::ModelCompilingFailed( @@ -44,29 +50,24 @@ pub fn compile_model( )); let output = output.with_extension("so"); - let stanc_args = [includir_stan_file_dir.as_slice(), stanc_args.as_slice()].concat(); + let stanc_args = [&[includir_stan_file_dir], stanc_args].concat(); let stanc_args = stanc_args.join(" "); let stanc_args = format!("STANCFLAGS={}", stanc_args); let stanc_args = [stanc_args.as_str()]; let cmd = [ &[output.to_str().unwrap_or_default()], - make_args.as_slice(), + make_args, stanc_args.as_slice(), ] .concat(); - let make = if cfg!(target_os = "windows") { - "mingw32-make" - } else { - "make" - }; info!( "Compiling model with command: {} \"{}\"", - make, + MAKE, cmd.join("\" \"") ); - std::process::Command::new(make) + std::process::Command::new(MAKE) .args(cmd) .current_dir(bs_path) .env("STAN_THREADS", "true") diff --git a/rust/src/download.rs b/rust/src/download.rs index 39f1c150..29b02bf6 100644 --- a/rust/src/download.rs +++ b/rust/src/download.rs @@ -6,7 +6,7 @@ use std::{env::temp_dir, fs, path::PathBuf}; use tar::Archive; /// Download and unzip the BridgeStan source distribution for this version -/// to ~/.bridgestan/bridgestan-version +/// to `~/.bridgestan/bridgestan-$VERSION`. pub fn download_bridgestan_src() -> Result { let homedir = dirs::home_dir().unwrap_or(temp_dir()); diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 2af84cdb..9d545db5 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -2,16 +2,14 @@ mod bs_safe; mod compile; - #[cfg(feature = "download-bridgestan-src")] mod download; - pub(crate) mod ffi; + pub use bs_safe::{open_library, BridgeStanError, Model, Rng, StanLibrary}; +pub use compile::compile_model; #[cfg(feature = "download-bridgestan-src")] pub use download::download_bridgestan_src; -pub use compile::compile_model; - pub const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/rust/tests/model.rs b/rust/tests/model.rs index 166596b8..c859496f 100644 --- a/rust/tests/model.rs +++ b/rust/tests/model.rs @@ -43,55 +43,6 @@ fn logp_gradient() { assert_ulps_eq!(grad[0], -1f64); } -// Note: this test does not require download-bridgestan-src feature -// but it assumes that bridgestan_src is on the home folder -// one way to have that assumption met is by running the example or -// the model_downloading_and_compiling test before running this test -#[test] -#[ignore] -fn model_compiling() { - use bridgestan::compile_model; - use common::model_dir; - use std::fs::remove_file; - use std::path::PathBuf; - - let name = "stdnormal"; - let mut base = model_dir(); - base.push(name); - let lib_path = base.join(format!("{}_model.so", name)); - let stan_path = base.join(format!("{}.stan", name)); - remove_file(lib_path).unwrap_or_default(); - - let bs_path_download_join_version: PathBuf = std::env::var("BRIDGESTAN") - .unwrap_or("..".to_string()) - .into(); - - compile_model(&bs_path_download_join_version, &stan_path, vec![], vec![]).unwrap(); - - let (lib, data) = get_model(name); - let model = Model::new(&lib, data, 42).unwrap(); - let theta = [1f64]; - let mut grad = [0f64]; - let logp = model - .log_density_gradient(&theta[..], false, true, &mut grad[..]) - .unwrap(); - assert_ulps_eq!(logp, (2. * PI).sqrt().recip().ln() - 0.5); - assert_ulps_eq!(grad[0], -1f64); -} - -#[cfg(feature = "download-bridgestan-src")] -#[test] -#[ignore] -fn model_downloading() { - use bridgestan::download_bridgestan_src; - - let bs_path = download_bridgestan_src().unwrap(); - let stan_path = bs_path.join("stan"); - assert!(stan_path.is_dir()); - let makefile_path = bs_path.join("Makefile"); - assert!(makefile_path.is_file()); -} - #[test] fn logp_hessian() { let (lib, data) = get_model("stdnormal"); @@ -219,3 +170,49 @@ fn test_params() { .unwrap(); assert_eq!(theta_unc[0], 0.); } + +#[cfg(feature = "download-bridgestan-src")] +#[test] +fn model_downloading() { + use bridgestan::download_bridgestan_src; + + let bs_path = download_bridgestan_src().unwrap(); + let stan_path = bs_path.join("stan"); + assert!(stan_path.is_dir()); + let makefile_path = bs_path.join("Makefile"); + assert!(makefile_path.is_file()); +} + +// ignore-d to prevent overwriting the model on disk +// while other tests are running +#[test] +#[ignore] +fn model_compiling() { + use bridgestan::compile_model; + use common::model_dir; + use std::fs::remove_file; + use std::path::PathBuf; + + let name = "stdnormal"; + let mut base = model_dir(); + base.push(name); + let lib_path = base.join(format!("{}_model.so", name)); + let stan_path = base.join(format!("{}.stan", name)); + remove_file(lib_path).unwrap_or_default(); + + let bs_path: PathBuf = std::env::var("BRIDGESTAN") + .unwrap_or("..".to_string()) + .into(); + + compile_model(&bs_path, &stan_path, &[], &[]).unwrap(); + + let (lib, data) = get_model(name); + let model = Model::new(&lib, data, 42).unwrap(); + let theta = [1f64]; + let mut grad = [0f64]; + let logp = model + .log_density_gradient(&theta[..], false, true, &mut grad[..]) + .unwrap(); + assert_ulps_eq!(logp, (2. * PI).sqrt().recip().ln() - 0.5); + assert_ulps_eq!(grad[0], -1f64); +}