diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index ec7db9a5..3f5a5e09 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -14,6 +14,7 @@ jobs: build: runs-on: ${{matrix.os}} strategy: + fail-fast: false matrix: os: [windows-latest, ubuntu-latest, macos-latest] env: @@ -103,6 +104,10 @@ jobs: pip install pytest pip install . + - name: Test import + run: | + python -c "import bridgestan" + - name: Run tests run: | cd python/ @@ -155,6 +160,12 @@ jobs: path: ./test_models/ key: ${{ hashFiles('**/*.stan', 'src/*', 'stan/src/stan/version.hpp', 'Makefile') }}-${{ matrix.os }}-v${{ env.CACHE_VERSION }} + - name: Check import + run: | + cd julia/ + julia --project=. -e "using Pkg; Pkg.instantiate()" + julia --project=. -e "using BridgeStan" + - name: Run tests run: | cd julia/ @@ -272,5 +283,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 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index bef1b8b4..91b4ebb2 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 @@ -80,10 +87,11 @@ jobs: with: name: release-artifacts path: | - python/dist/ + python/dist/*.whl bridgestan-*.tar.gz - name: Create release + if: ${{ !inputs.is_rerun }} uses: ncipollo/release-action@v1 with: artifacts: "bridgestan-*.tar.gz,python/dist/*" @@ -97,12 +105,23 @@ jobs: - name: Upload PyPI wheels if: ${{ !inputs.dry_run }} - uses: pypa/gh-action-pypi-publish@v1.8.6 + uses: pypa/gh-action-pypi-publish@v1.8.7 with: password: ${{ secrets.PYPI_TOKEN }} packages_dir: python/dist/ skip_existing: true + - name: Publish Rust crate + if: ${{ !inputs.dry_run }} + 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 }} + - name: Create JuliaRegistration comment if: ${{ !inputs.dry_run }} uses: peter-evans/commit-comment@v2 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 31f06a83..e2223622 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,8 +71,18 @@ We use [Gnu make](https://www.gnu.org/software/make/) for builds. If you have p * [Test](https://docs.julialang.org/en/v1/stdlib/Test/) * [LinearAlgebra](https://docs.julialang.org/en/v1/stdlib/LinearAlgebra/) -* Julia code is formatted using [JuliaFormatter](https://github.com/domluna/JuliaFormatter.jl). +* Julia code is formatted using[JuliaFormatter](https://github.com/domluna/JuliaFormatter.jl). +* Julia's multi-threading capabilities allow different processors/threads to +make simultaneous calls to the BridgeStan API. Such capabilities require the +target Stan program to be compiled with `STAN_THREADS=true`, see the function +[compile_model](https://roualdes.github.io/bridgestan/latest/languages/julia.html#BridgeStan.compile_model) +for more details. The Julia interface tests this feature and thus requires +`STAN_THREADS=true` for the tests to run successfully. + +### Rust development + +* Rust development is based on `cargo`, which should handle dependencies, testing, and formatting. ## Proposing a new interface language diff --git a/Makefile b/Makefile index 43aed233..358be220 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,7 @@ INC_FIRST ?= -I $(STAN)src -I $(RAPIDJSON) ## Set -fPIC globally since we're always building a shared library CXXFLAGS += -fPIC +CXXFLAGS_SUNDIALS += -fPIC ## set flags for stanc compiler (math calls MIGHT? set STAN_OPENCL) ifdef STAN_OPENCL @@ -56,12 +57,12 @@ $(BRIDGE_O) : $(BRIDGE_DEPS) .PRECIOUS: %.hpp ## builds executable (suffix depends on platform) -%_model.so : %.hpp $(BRIDGE_O) $(LIBSUNDIALS) $(MPI_TARGETS) $(TBB_TARGETS) +%_model.so : %.hpp $(BRIDGE_O) $(SUNDIALS_TARGETS) $(MPI_TARGETS) $(TBB_TARGETS) @echo '' @echo '--- Compiling C++ code ---' $(COMPILE.cpp) -x c++ -o $(subst \,/,$*).o $(subst \,/,$<) @echo '--- Linking C++ code ---' - $(LINK.cpp) -shared -lm -o $(patsubst %.hpp, %_model.so, $(subst \,/,$<)) $(subst \,/,$*.o) $(BRIDGE_O) $(LDLIBS) $(LIBSUNDIALS) $(MPI_TARGETS) $(TBB_TARGETS) + $(LINK.cpp) -shared -lm -o $(patsubst %.hpp, %_model.so, $(subst \,/,$<)) $(subst \,/,$*.o) $(BRIDGE_O) $(LDLIBS) $(SUNDIALS_TARGETS) $(MPI_TARGETS) $(TBB_TARGETS) $(RM) $(subst \,/,$*).o .PHONY: docs @@ -94,7 +95,7 @@ stan-update-remote: # print compilation command line config .PHONY: compile_info compile_info: - @echo '$(LINK.cpp) $(BRIDGE_O) $(LDLIBS) $(LIBSUNDIALS) $(MPI_TARGETS) $(TBB_TARGETS)' + @echo '$(LINK.cpp) $(BRIDGE_O) $(LDLIBS) $(SUNDIALS_TARGETS) $(MPI_TARGETS) $(TBB_TARGETS)' ## print value of makefile variable (e.g., make print-TBB_TARGETS) .PHONY: print-% diff --git a/R/DESCRIPTION b/R/DESCRIPTION index d7637547..7830ad1f 100644 --- a/R/DESCRIPTION +++ b/R/DESCRIPTION @@ -1,6 +1,6 @@ Package: bridgestan Title: BridgeStan, Accessing Stan Model Functions in R -Version: 2.0.0 +Version: 2.1.1 Authors@R: person(given="Brian", family="Ward", , "bward@flatironinstitute.org", role = c("aut", "cre")) License: BSD_3_clause diff --git a/README.md b/README.md index c5bf4d57..e7546bac 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![DOCS](https://img.shields.io/badge/docs-latest-blue)](https://roualdes.github.io/bridgestan/) [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.7760173.svg)](https://doi.org/10.5281/zenodo.7760173) [![CI](https://github.com/roualdes/bridgestan/actions/workflows/main.yaml/badge.svg)](https://github.com/roualdes/bridgestan/actions/workflows/main.yaml) BridgeStan provides efficient in-memory access through Python, Julia, -and R to the methods of a [Stan](https://mc-stan.org) model, including +Rust, and R to the methods of a [Stan](https://mc-stan.org) model, including log densities, gradients, Hessians, and constraining and unconstraining transforms. The motivation was developing inference algorithms in higher-level languages for arbitrary Stan models. @@ -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,6 +68,8 @@ in Python, Julia, R, and C. * From R: [`example.r`](R/example.R) +* From Rust: [`example.rs`](rust/examples/example.rs) + * From C: [`example.c`](c-example/example.c) Examples of other functionality can be found in the `test` folder for each interface. @@ -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/docs/_static/css/custom.css b/docs/_static/css/custom.css new file mode 100644 index 00000000..d1db67cd --- /dev/null +++ b/docs/_static/css/custom.css @@ -0,0 +1,7 @@ +html[data-theme="light"] { + --pst-color-inline-code: #008000; +} + +html[data-theme="dark"] { + --pst-color-inline-code: #659157; +} diff --git a/docs/_static/switcher.json b/docs/_static/switcher.json index 39c8fdef..31244b17 100644 --- a/docs/_static/switcher.json +++ b/docs/_static/switcher.json @@ -4,6 +4,16 @@ "version": "latest", "url": "https://roualdes.github.io/bridgestan/latest/" }, + { + "name": "v2.1.1", + "version": "v2.1.1", + "url": "https://roualdes.github.io/bridgestan/v2.1.1/" + }, + { + "name": "v2.1.0", + "version": "v2.1.0", + "url": "https://roualdes.github.io/bridgestan/v2.1.0/" + }, { "name": "v2.0.0", "version": "v2.0.0", diff --git a/docs/conf.py b/docs/conf.py index 7a481721..96574357 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -53,6 +53,7 @@ html_static_path = ["_static"] html_css_files = [ "css/Documenter.css", + "css/custom.css", ] html_show_sphinx = False diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 7d1cf6d8..d1efd2d3 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -8,7 +8,7 @@ Requirement: C++ toolchain Stan requires a C++ tool chain consisting of * A C++14 compiler. On Windows, MSCV is *not* supported, so something like MinGW GCC is required. -* The Gnu ``make`` utility for \*nix *or* ``mingw32-make`` for Windows +* The Gnu :command:`make` utility for \*nix *or* :command:`mingw32-make` for Windows Here are complete instructions by platform for installing both, from the CmdStan installation instructions. @@ -24,7 +24,8 @@ and no additional dependencies are required to be installed separately for the C As of version 1.0.2, the :doc:`Julia ` and :doc:`Python ` interfaces will download -the source for you, so this section is optional. +the source for you the first time you compile a model, so +this section is optional. Downloading a released archive ______________________________ @@ -36,10 +37,10 @@ To use these, simply download the file associated with the version you wish to u and unzip its contents into the folder you would like BridgeStan to be in. -Installing the latest version with ``git`` -__________________________________________ +Installing the latest version with :command:`git` +_________________________________________________ -If you have ``git`` installed, you may download BridgeStan by navigating to the folder you'd like +If you have :command:`git` installed, you may download BridgeStan by navigating to the folder you'd like BridgeStan to be in and running .. code-block:: shell @@ -64,10 +65,10 @@ a terminal in your BridgeStan folder and running # Windows mingw32-make.exe test_models/multi/multi_model.so -This will compile the file ``test_models/multi/multi.stan`` into a shared library object for use with BridgeStan. +This will compile the file :file:`test_models/multi/multi.stan` into a shared library object for use with BridgeStan. This will require internet access the first time you run it in order to download the appropriate Stan compiler for your platform into -``/bin/stanc[.exe]`` +:file:`{}/bin/stanc{[.exe]}` Installing an Interface ----------------------- @@ -81,10 +82,10 @@ Optional: Customizing BridgeStan BridgeStan has many compiler flags and options set by default. Many of these defaults are the same as those used by the CmdStan interface to Stan. You can override the defaults or add new flags -on the command line when invoking ``make``, or make them persistent by -creating or editing the file ``/make/local``. +on the command line when invoking :command:`make`, or make them persistent by +creating or editing the file :file:`{}/make/local`. -For example, setting the contents of ``make/local`` to the following +For example, setting the contents of :file:`make/local` to the following includes compiler flags for optimization level and architecture. .. code-block:: Makefile @@ -94,7 +95,7 @@ includes compiler flags for optimization level and architecture. # Adding other arbitrary C++ compiler flags CXXFLAGS+= -march=native -Flags for ``stanc3`` can also be set here +Flags for :command:`stanc3` can also be set here .. code-block:: Makefile @@ -106,7 +107,7 @@ ________________________________________ In order for Python or Julia to be able to call a single Stan model concurrently from multiple threads or for a Stan model to execute its -own code in parallel, the following flag must be set in ``make/local`` +own code in parallel, the following flag must be set in :file:`make/local` or on the command line. .. code-block:: Makefile @@ -116,7 +117,7 @@ or on the command line. Note that this flag changes a lot of the internals of the Stan library and as such, **all models used in the same process should have the same -setting**. Mixing models which have ``STAN_THREADS`` enabled with those that do not +setting**. Mixing models which have :makevar:`STAN_THREADS` enabled with those that do not will most likely lead to segmentation faults or other crashes. Additional flags, such as those for MPI and OpenCL, are covered in the @@ -131,12 +132,12 @@ to be computed directly, particularly models which use implicit functions like t or ODE integrators. If your Stan model does not use these features, you can enable autodiff Hessians by -setting the compile-time flag ``BRIDGESTAN_AD_HESSIAN=true`` in the invocation to ``make``. -This can be set in ``make/local`` if you wish to use it by default. +setting the compile-time flag ``BRIDGESTAN_AD_HESSIAN=true`` in the invocation to :command:`make`. +This can be set in :file:`make/local` if you wish to use it by default. This value is reported by the ``model_info`` function if you would like to check at run time whether Hessians are computed with nested autodiff or with finite differences. Similar to -``STAN_THREADS``, it is not advised to mix models which use autodiff Hessians with those that +:makevar:`STAN_THREADS`, it is not advised to mix models which use autodiff Hessians with those that do not in the same program. Autodiff Hessians may be faster than finite differences depending on your model, and will @@ -147,21 +148,21 @@ __________________________ If you wish to use BridgeStan for an older released version, all you need to do is -1. Set ``STANC3_VERSION`` in ``make/local`` to your desired version, e.g. ``v2.26.0`` +1. Set :makevar:`STANC3_VERSION` in :file:`make/local` to your desired version, e.g. ``v2.26.0`` 2. Go into the ``stan`` submodule and run ``git checkout release/VERSION``, e.g. ``release/v2.26.0`` 3. Also in the ``stan`` submodule, run ``make math-update`` 4. In the top level BridgeStan directory, run ``make clean`` To return to the version of Stan currently used by BridgeStan, you can run ``make stan-update`` from the top level directory -and remove ``STANC3_VERSION`` from your ``make/local`` file, before running ``make clean`` again. +and remove :makevar:`STANC3_VERSION` from your ``make/local`` file, before running ``make clean`` again. Using Pre-Existing Stan Installations _____________________________________ If you wish to use BridgeStan with a pre-existing download of the Stan repository, or with -a custom fork or branch, you can set the ``STAN`` (and, optionally, ``MATH``) variables to the -path to your existing copy in calls to ``make``, or more permanently by setting them in a -``make/local`` file as described above. +a custom fork or branch, you can set the :makevar:`STAN` (and, optionally, :makevar:`MATH`) variables to the +path to your existing copy in calls to :command:`make`, or more permanently by setting them in a +:file:`make/local` file as described above. The easiest way to use a custom stanc3 is to place the built executable at -``bin/stanc[.exe]``. +:file:`bin/stanc{[.exe]}`. diff --git a/docs/index.rst b/docs/index.rst index f18654d0..ae1722af 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,13 +1,9 @@ -.. BridgeStan documentation master file, created by - sphinx-quickstart on Fri Sep 2 11:44:11 2022. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. Welcome to BridgeStan's documentation! ====================================== BridgeStan provides efficient in-memory access through Python, Julia, -and R to the methods of a `Stan `__ model, including +R, and Rust to the methods of a `Stan `__ model, including log densities, gradients, Hessians, and constraining and unconstraining transforms. diff --git a/docs/internals/documentation.rst b/docs/internals/documentation.rst index 826a6bbf..52997dd5 100644 --- a/docs/internals/documentation.rst +++ b/docs/internals/documentation.rst @@ -11,7 +11,7 @@ generation (powered by `Sphinx `__) autom combines these together into the documentation you are reading now. To build the documentation, you can run ``make docs`` in the top-level directory. -This places the files in ``docs/_build/html``. At a minimum, the following must be installed: +This places the files in :file:`docs/_build/html`. At a minimum, the following must be installed: * The Python interface to BridgeStan * `Sphinx 5.0 or above `__ @@ -28,6 +28,6 @@ If you wish to build the C++ portions of the documentation, you should also have * `Breathe `__ Similarly, the Julia documentation will only update if Julia is installed. Julia -documentation is written in ``julia/docs/src/julia.md``. We then build +documentation is written in :file:`julia/docs/src/julia.md`. We then build this with `DocumenterMarkdown.jl `__, -and the output is placed in ``docs/languages/julia.md``. +and the output is placed in :file:`docs/languages/julia.md`. diff --git a/docs/internals/ffi.rst b/docs/internals/ffi.rst index 9b244c42..8feb6436 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,13 +87,16 @@ 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 ______________ -Printed output from the C++ code cannot easily be captured in the higher-level language. -This is particularly relevant for error messaging, which is printed to the standard -error output ``stderr`` from C++. This does *not*, for example, correspond to the -``sys.stderr`` stream available from Python. +Printed output from the C++ code cannot always be easily captured in the higher-level language. +This is particularly relevant for print statements in Stan, which are printed to the standard +output ``stdout`` from C++. This does *not*, for example, correspond to the +:py:data:`sys.stdout` stream available from Python by default. +We tackle this problem through the use of an interface-provided callback function +when necessary. See :cpp:func:`bs_set_print_callback()` for more details. diff --git a/docs/internals/testing.rst b/docs/internals/testing.rst index 0697019a..22ee1a41 100644 --- a/docs/internals/testing.rst +++ b/docs/internals/testing.rst @@ -3,7 +3,7 @@ Testing Testing for BridgeStan is primarily done through the higher-level :doc:`interfaces <../languages>`. -All tests are based around the same set of test models (in the ``test_models/`` folder). +All tests are based around the same set of test models (in the :file:`test_models/` folder). You can build all of the test models at once with @@ -12,7 +12,7 @@ You can build all of the test models at once with make STAN_THREADS=true test_models -j Note: The additional functionality provided by -``STAN_THREADS`` is only tested by the Julia tests, +:makevar:`STAN_THREADS` is only required by the Julia and Rust tests, but in order to facilitate the same built models being used in all tests we use it regardless of interface. @@ -26,7 +26,7 @@ In Python we use `pytest `__ to run tests. Te are written using basic ``assert`` statements and helper code from :py:mod:`numpy.testing`. The Python test suite has the ability to run mutually exclusive groups of code. This is to allow -testing of features such as the ``BRIDGESTAN_AD_HESSIAN`` flag which change underlying code and +testing of features such as the :makevar:`BRIDGESTAN_AD_HESSIAN` flag which change underlying code and therefore cannot be loaded at the same time as models compiled without it. Running @@ -43,7 +43,7 @@ Will run the "default" grouping. To run the other group(s), run cd python/ pytest --run-type=ad_hessian -v -The set up for this can be seen in ``tests/conftest.py`` and is based on the +The set up for this can be seen in :file:`tests/conftest.py` and is based on the `Pytest documentation examples `__. Julia @@ -68,3 +68,13 @@ R tests are written using `testthat `__. Rscript -e "devtools::test()" The R unit tests are much more basic than the Python or Julia tests. + +Rust +_____ + +The Rust tests can be run with :command:`cargo` + +.. code-block:: shell + + cd rust/ + cargo test diff --git a/docs/languages.rst b/docs/languages.rst index bbdecfff..f21bcc1f 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. @@ -10,7 +10,7 @@ All language interfaces expose the same core functionality from the :doc:`C API <./languages/c-api>`. Additional "quality of life" features such as the ability to download BridgeStan's source code automatically or compile models from inside the language -(rather than manually calling ``make``) are available on a best-effort basis. +(rather than manually calling :command:`make`) are available on a best-effort basis. If you are missing these features in your favorite language, we would welcome a `contribution `__! @@ -20,6 +20,7 @@ If you are missing these features in your favorite language, we would welcome a languages/python languages/julia languages/r + languages/rust languages/c-api diff --git a/docs/languages/c-api.rst b/docs/languages/c-api.rst index 68779fc1..e1f450f5 100644 --- a/docs/languages/c-api.rst +++ b/docs/languages/c-api.rst @@ -13,8 +13,8 @@ BridgeStan's pre-requisites and downloaded a copy of the BridgeStan source code. Example Program --------------- -An example program is provided alongside the BridgeStan source in ``c-example/``. -Details for building the example can be found in ``c-example/Makefile``. +An example program is provided alongside the BridgeStan source in :file:`c-example/`. +Details for building the example can be found in :file:`c-example/Makefile`. .. raw:: html @@ -33,7 +33,7 @@ Details for building the example can be found in ``c-example/Makefile``. API Reference ------------- -The following are the C functions exposed by the BridgeStan library in ``bridgestan.h``. +The following are the C functions exposed by the BridgeStan library in :file:`bridgestan.h`. These are wrapped in the various high-level interfaces. These functions are implemented in C++, see :doc:`../internals` for more details. @@ -46,7 +46,7 @@ R-compatible functions ---------------------- To support calling these functions from R without including R-specific headers -into the project, the following functions are exposed in ``bridgestanR.h``. +into the project, the following functions are exposed in :file:`bridgestanR.h`. These are small shims which call the above functions. All arguments and return values must be handeled via pointers. diff --git a/docs/languages/julia.md b/docs/languages/julia.md index 119cf6e6..64b2b369 100644 --- a/docs/languages/julia.md +++ b/docs/languages/julia.md @@ -37,7 +37,7 @@ BridgeStan is registered on JuliaRegistries each release. ``` -The first time you need it, the BridgeStan source code will be downloaded as an [Artifact](https://pkgdocs.julialang.org/v1/artifacts/). If you prefer to use a source distribution of BridgeStan, consult the following section. +The first time you compile a model, the BridgeStan source code will be downloaded as an [Artifact](https://pkgdocs.julialang.org/v1/artifacts/). If you prefer to use a source distribution of BridgeStan, consult the following section. 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. @@ -152,7 +152,7 @@ Return the log density of the specified unconstrained parameters. This calculation drops constant terms that do not depend on the parameters if `propto` is `true` and includes change of variables terms for constrained parameters if `jacobian` is `true`. -source
+source
# **`BridgeStan.log_density_gradient`** — *Function*. @@ -170,7 +170,7 @@ This calculation drops constant terms that do not depend on the parameters if `p This allocates new memory for the gradient output each call. See `log_density_gradient!` for a version which allows re-using existing memory. -source
+source
# **`BridgeStan.log_density_hessian`** — *Function*. @@ -188,7 +188,7 @@ This calculation drops constant terms that do not depend on the parameters if `p This allocates new memory for the gradient and Hessian output each call. See `log_density_gradient!` for a version which allows re-using existing memory. -source
+source
# **`BridgeStan.param_constrain`** — *Function*. @@ -208,7 +208,7 @@ This allocates new memory for the output each call. See `param_constrain!` for a This is the inverse of `param_unconstrain`. -source
+source
# **`BridgeStan.param_unconstrain`** — *Function*. @@ -228,7 +228,7 @@ This allocates new memory for the output each call. See `param_unconstrain!` for This is the inverse of `param_constrain`. -source
+source
# **`BridgeStan.param_unconstrain_json`** — *Function*. @@ -246,7 +246,7 @@ The JSON is expected to be in the [JSON Format for CmdStan](https://mc-stan.org/ This allocates new memory for the output each call. See `param_unconstrain_json!` for a version which allows re-using existing memory. -source
+source
# **`BridgeStan.name`** — *Function*. @@ -260,7 +260,7 @@ name(sm) Return the name of the model `sm` -source
+source
# **`BridgeStan.model_info`** — *Function*. @@ -276,7 +276,7 @@ Return information about the model `sm`. This includes the Stan version and important compiler flags. -source
+source
# **`BridgeStan.param_num`** — *Function*. @@ -292,7 +292,7 @@ Return the number of (constrained) parameters in the model. This is the total of all the sizes of items declared in the `parameters` block of the model. If `include_tp` or `include_gq` are true, items declared in the `transformed parameters` and `generate quantities` blocks are included, respectively. -source
+source
# **`BridgeStan.param_unc_num`** — *Function*. @@ -308,7 +308,7 @@ Return the number of unconstrained parameters in the model. This function is mainly different from `param_num` when variables are declared with constraints. For example, `simplex[5]` has a constrained size of 5, but an unconstrained size of 4. -source
+source
# **`BridgeStan.param_names`** — *Function*. @@ -326,7 +326,7 @@ For containers, indexes are separated by periods (.). For example, the scalar `a` has indexed name `"a"`, the vector entry `a[1]` has indexed name `"a.1"` and the matrix entry `a[2, 3]` has indexed names `"a.2.3"`. Parameter order of the output is column major and more generally last-index major for containers. -source
+source
# **`BridgeStan.param_unc_names`** — *Function*. @@ -342,7 +342,7 @@ Return the indexed names of the unconstrained parameters. For example, a scalar unconstrained parameter `b` has indexed name `b` and a vector entry `b[3]` has indexed name `b.3`. -source
+source
# **`BridgeStan.log_density_gradient!`** — *Function*. @@ -360,7 +360,7 @@ This calculation drops constant terms that do not depend on the parameters if `p The gradient is stored in the vector `out`, and a reference is returned. See `log_density_gradient` for a version which allocates fresh memory. -source
+source
# **`BridgeStan.log_density_hessian!`** — *Function*. @@ -378,7 +378,7 @@ This calculation drops constant terms that do not depend on the parameters if `p The gradient is stored in the vector `out_grad` and the Hessian is stored in `out_hess` and references are returned. See `log_density_hessian` for a version which allocates fresh memory. -source
+source
# **`BridgeStan.param_constrain!`** — *Function*. @@ -398,7 +398,7 @@ The result is stored in the vector `out`, and a reference is returned. See `para This is the inverse of `param_unconstrain!`. -source
+source
# **`BridgeStan.param_unconstrain!`** — *Function*. @@ -418,7 +418,7 @@ The result is stored in the vector `out`, and a reference is returned. See `para This is the inverse of `param_constrain!`. -source
+source
# **`BridgeStan.param_unconstrain_json!`** — *Function*. @@ -436,7 +436,7 @@ The JSON is expected to be in the [JSON Format for CmdStan](https://mc-stan.org/ The result is stored in the vector `out`, and a reference is returned. See `param_unconstrain_json` for a version which allocates fresh memory. -source
+source
# **`BridgeStan.StanRNG`** — *Type*. @@ -456,6 +456,24 @@ This object is not thread-safe, one should be created per thread. source
+# +**`BridgeStan.new_rng`** — *Function*. + + + +```julia +new_rng(sm::StanModel, seed) +``` + +Construct a StanRNG instance from a `StanModel` instance and a seed. This function is a wrapper around the constructor `StanRNG`. + +This can be used in the `param_constrain` and `param_constrain!` methods when using the generated quantities block. + +The StanRNG object created is not thread-safe, one should be created per thread. + + +source
+ diff --git a/docs/languages/python.rst b/docs/languages/python.rst index 8f7d5c9a..746254a6 100644 --- a/docs/languages/python.rst +++ b/docs/languages/python.rst @@ -18,8 +18,8 @@ For convience, BridgeStan is uploaded to the Python Package Index each release. pip install bridgestan -The first time you need it, the BridgeStan source code will be downloaded -and placed in ``~/.bridgestan/``. If you prefer to use a source distribution of BridgeStan, +The first time you compile a model, the BridgeStan source code will be downloaded +and placed in :file:`~/.bridgestan/`. If you prefer to use a source distribution of BridgeStan, consult the following section. @@ -53,7 +53,7 @@ NumPy if it is not already installed. Example Program --------------- -An example program is provided alongside the Python interface code in ``example.py``: +An example program is provided alongside the Python interface code in :file:`example.py`: .. raw:: html diff --git a/docs/languages/rust.rst b/docs/languages/rust.rst new file mode 100644 index 00000000..8ccbe0ef --- /dev/null +++ b/docs/languages/rust.rst @@ -0,0 +1,47 @@ +Rust Interface +============== + +`See the BridgeStan Crate documentation on docs.rs `__ + +---- + +Installation +------------ + +The BridgeStan Rust client is available on `crates.io `__ and via :command:`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. + +``STAN_THREADS=true`` needs to be specified when compiling a model, for more +details see the `API reference `__. + +Example Program +--------------- + +An example program is provided alongside the Rust crate in :file:`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/julia/Artifacts.toml b/julia/Artifacts.toml index fcdd03f5..3f0bb45f 100644 --- a/julia/Artifacts.toml +++ b/julia/Artifacts.toml @@ -1,7 +1,7 @@ [bridgestan] -git-tree-sha1 = "247b321809bb1a1ddd011f69648ae729ba8282c4" +git-tree-sha1 = "ac3a3214a3759f4eb652ce30f2e1b2feb6df1f00" lazy = true [[bridgestan.download]] - sha256 = "8bd57926e2afb694d2d246a08efb1ab6b56027adcdbeb6ab0407ed58aba88778" - url = "https://github.com/roualdes/bridgestan/releases/download/v2.0.0/bridgestan-2.0.0.tar.gz" + sha256 = "6eb3526b831fc81bd4283169e38ad54bf0cf91ebc5a527862212fbd6b8fe603e" + url = "https://github.com/roualdes/bridgestan/releases/download/v2.1.1/bridgestan-2.1.1.tar.gz" diff --git a/julia/Project.toml b/julia/Project.toml index 5116c630..4584149c 100644 --- a/julia/Project.toml +++ b/julia/Project.toml @@ -1,7 +1,7 @@ name = "BridgeStan" uuid = "c88b6f0a-829e-4b0b-94b7-f06ab5908f5a" authors = ["Brian Ward ", "Bob Carpenter "] -version = "2.0.0" +version = "2.1.1" [deps] LazyArtifacts = "4af54fe1-eca0-43a8-85a7-787d91b784e3" diff --git a/julia/docs/src/julia.md b/julia/docs/src/julia.md index 5f99b949..0bbbb1f0 100644 --- a/julia/docs/src/julia.md +++ b/julia/docs/src/julia.md @@ -20,7 +20,7 @@ BridgeStan is registered on JuliaRegistries each release. ] add BridgeStan ``` -The first time you need it, the BridgeStan source code will be downloaded +The first time you compile a model, the BridgeStan source code will be downloaded as an [Artifact](https://pkgdocs.julialang.org/v1/artifacts/). If you prefer to use a source distribution of BridgeStan, consult the following section. @@ -90,6 +90,7 @@ param_constrain! param_unconstrain! param_unconstrain_json! StanRNG +new_rng ``` ### Compilation utilities diff --git a/julia/example.jl b/julia/example.jl index 4c0752ac..779d605d 100644 --- a/julia/example.jl +++ b/julia/example.jl @@ -2,6 +2,10 @@ using BridgeStan const BS = BridgeStan +# These paths are what they are because this example lives in a subfolder +# of the BridgeStan repository. If you're running this on your own, you +# will most likely want to delete the next line (to have BridgeStan +# download its sources for you) and change the paths on the following two BS.set_bridgestan_path!("../") bernoulli_stan = joinpath(@__DIR__, "../test_models/bernoulli/bernoulli.stan") diff --git a/julia/src/BridgeStan.jl b/julia/src/BridgeStan.jl index 26f05cb7..5c65e8ff 100644 --- a/julia/src/BridgeStan.jl +++ b/julia/src/BridgeStan.jl @@ -23,7 +23,8 @@ export StanModel, get_bridgestan_path, set_bridgestan_path!, compile_model, - StanRNG + StanRNG, + new_rng include("model.jl") include("compile.jl") diff --git a/julia/src/model.jl b/julia/src/model.jl index 2f426afd..3356e56e 100644 --- a/julia/src/model.jl +++ b/julia/src/model.jl @@ -139,6 +139,19 @@ mutable struct StanRNG end end +""" + new_rng(sm::StanModel, seed) + +Construct a StanRNG instance from a `StanModel` instance and a seed. This +function is a wrapper around the constructor `StanRNG`. + +This can be used in the `param_constrain` and `param_constrain!` methods +when using the generated quantities block. + +The StanRNG object created is not thread-safe, one should be created per thread. +""" +new_rng(sm::StanModel, seed) = StanRNG(sm, seed) + """ name(sm) diff --git a/python/bridgestan/__init__.py b/python/bridgestan/__init__.py index 283047c5..24f4a9f5 100644 --- a/python/bridgestan/__init__.py +++ b/python/bridgestan/__init__.py @@ -3,50 +3,3 @@ from .model import StanModel __all__ = ["StanModel", "set_bridgestan_path", "compile_model"] - -import platform as _plt - -if _plt.system() == "Windows": - - def _windows_path_setup(): - """Add tbb.dll to %PATH% on Windows.""" - import os - import subprocess - import warnings - - from .compile import get_bridgestan_path - - try: - out = subprocess.run( - ["where.exe", "tbb.dll"], check=True, capture_output=True - ) - tbb_path = os.path.dirname(out.stdout.decode().splitlines()[0]) - os.add_dll_directory(tbb_path) - except: - tbb_path = os.path.join( - get_bridgestan_path(), "stan", "lib", "stan_math", "lib", "tbb" - ) - os.environ["PATH"] = tbb_path + ";" + os.environ["PATH"] - os.add_dll_directory(tbb_path) - - try: - out = subprocess.run( - [ - "where.exe", - "libwinpthread-1.dll", - "libgcc_s_seh-1.dll", - "libstdc++-6.dll", - ], - check=True, - capture_output=True, - ) - mingw_dir = os.path.dirname(out.stdout.decode().splitlines()[0]) - os.add_dll_directory(mingw_dir) - except: - # no default location - warnings.warn( - "Unable to find MinGW's DLL location. Loading BridgeStan models may fail.", - RuntimeWarning, - ) - - _windows_path_setup() diff --git a/python/bridgestan/__version.py b/python/bridgestan/__version.py index 3394157e..44e1ec5f 100644 --- a/python/bridgestan/__version.py +++ b/python/bridgestan/__version.py @@ -1,2 +1,2 @@ -__version__ = "2.0.0" +__version__ = "2.1.1" __version_info__ = tuple(map(int, __version__.split("."))) diff --git a/python/bridgestan/compile.py b/python/bridgestan/compile.py index 5dddc049..c91465e3 100644 --- a/python/bridgestan/compile.py +++ b/python/bridgestan/compile.py @@ -1,9 +1,9 @@ import os import platform import subprocess +import warnings from pathlib import Path from typing import List -import warnings from .__version import __version__ from .download import CURRENT_BRIDGESTAN, HOME_BRIDGESTAN, get_bridgestan_src @@ -26,11 +26,14 @@ def verify_bridgestan_path(path: str) -> None: ) +IS_WINDOWS = platform.system() == "Windows" +WINDOWS_PATH_SET = False + PYTHON_FOLDER = Path(__file__).parent.parent MAKE = os.getenv( "MAKE", - "make" if platform.system() != "Windows" else "mingw32-make", + "make" if not IS_WINDOWS else "mingw32-make", ) @@ -44,6 +47,7 @@ def set_bridgestan_path(path: str) -> None: of this package (which, assuming a source installation, corresponds to the repository root). """ + path = os.path.abspath(path) verify_bridgestan_path(path) os.environ["BRIDGESTAN"] = path @@ -99,7 +103,6 @@ def compile_model( :raises RuntimeError: If compilation fails. """ verify_bridgestan_path(get_bridgestan_path()) - file_path = Path(stan_file).resolve() validate_readable(str(file_path)) if file_path.suffix != ".stan": @@ -125,3 +128,55 @@ def compile_model( raise RuntimeError(error) return output + + +def windows_dll_path_setup(): + """Add tbb.dll to %PATH% on Windows.""" + global WINDOWS_PATH_SET + if IS_WINDOWS and not WINDOWS_PATH_SET: + try: + out = subprocess.run( + ["where.exe", "tbb.dll"], check=True, capture_output=True + ) + tbb_path = os.path.dirname(out.stdout.decode().splitlines()[0]) + os.add_dll_directory(tbb_path) + except: + try: + tbb_path = os.path.abspath( + os.path.join( + get_bridgestan_path(), "stan", "lib", "stan_math", "lib", "tbb" + ) + ) + os.environ["PATH"] = tbb_path + ";" + os.environ["PATH"] + os.add_dll_directory(tbb_path) + WINDOWS_PATH_SET = True + except: + warnings.warn( + "Unable to set path to TBB's DLL. Loading BridgeStan models may fail. " + f"Tried path '{tbb_path}'", + RuntimeWarning, + ) + WINDOWS_PATH_SET = False + try: + out = subprocess.run( + [ + "where.exe", + "libwinpthread-1.dll", + "libgcc_s_seh-1.dll", + "libstdc++-6.dll", + ], + check=True, + capture_output=True, + ) + mingw_dir = os.path.abspath( + os.path.dirname(out.stdout.decode().splitlines()[0]) + ) + os.add_dll_directory(mingw_dir) + WINDOWS_PATH_SET &= True + except: + # no default location + warnings.warn( + "Unable to find MinGW's DLL location. Loading BridgeStan models may fail.", + RuntimeWarning, + ) + WINDOWS_PATH_SET = False diff --git a/python/bridgestan/model.py b/python/bridgestan/model.py index c5d34c5a..77aa8e2f 100644 --- a/python/bridgestan/model.py +++ b/python/bridgestan/model.py @@ -7,7 +7,7 @@ from numpy.ctypeslib import ndpointer from .__version import __version_info__ -from .compile import compile_model +from .compile import windows_dll_path_setup, compile_model from .util import validate_readable FloatArray = npt.NDArray[np.float64] @@ -67,6 +67,7 @@ def __init__( with open(model_data, "r") as f: model_data = f.read() + windows_dll_path_setup() self.lib_path = model_lib self.stanlib = ctypes.CDLL(self.lib_path) diff --git a/python/example.py b/python/example.py index d74839cc..470e99c1 100644 --- a/python/example.py +++ b/python/example.py @@ -2,7 +2,11 @@ import bridgestan as bs -bs.set_bridgestan_path("../") +# These paths are what they are because this example lives in a subfolder +# of the BridgeStan repository. If you're running this on your own, you +# will most likely want to delete the next line (to have BridgeStan +# download its sources for you) and change the paths on the following two +bs.set_bridgestan_path("..") stan = "../test_models/bernoulli/bernoulli.stan" data = "../test_models/bernoulli/bernoulli.data.json" diff --git a/python/test/test_stanmodel.py b/python/test/test_stanmodel.py index cfa8861b..edee9361 100644 --- a/python/test/test_stanmodel.py +++ b/python/test/test_stanmodel.py @@ -42,6 +42,12 @@ def test_constructor(): with pytest.raises(RuntimeError, match="find this text: datafails"): b4 = bs.StanModel(throw_data_so) + load_sundials = str(STAN_FOLDER / "ode_sundials" / "ode_sundials_model.so") + ode_sundials_data = ( + STAN_FOLDER / "ode_sundials" / "ode_sundials.data.json" + ).read_text() + bs.StanModel(load_sundials, ode_sundials_data) + def test_name(): std_so = str(STAN_FOLDER / "stdnormal" / "stdnormal_model.so") diff --git a/rust/Cargo.toml b/rust/Cargo.toml index ca63bfa5..219e3859 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "bridgestan" -version = "2.0.0" +version = "2.1.1" edition = "2021" rust-version = "1.69" description = "Rust interface for BridgeStan" repository = "https://github.com/roualdes/bridgestan" -license = "BSD-3-clause" +license = "BSD-3-Clause" homepage = "https://roualdes.github.io/bridgestan/latest/" [dependencies] diff --git a/rust/README.md b/rust/README.md index afdebc2d..c5a9fede 100644 --- a/rust/README.md +++ b/rust/README.md @@ -1,17 +1,43 @@ # 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). -It relies on [`bindgen`](https://docs.rs/bindgen/) and [`libloading`](https://docs.rs/libloading/). +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. -The Rust wrapper does not have any functionality to compile Stan models. +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: +Run this example with `cargo run --example=example`. + ```rust -use std::ffi::{OsStr, CString}; +use std::ffi::CString; use std::path::Path; use bridgestan::{BridgeStanError, Model, open_library}; @@ -22,7 +48,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}"#; @@ -35,7 +61,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/make_model.rs b/rust/examples/example.rs similarity index 97% rename from rust/examples/make_model.rs rename to rust/examples/example.rs index f45cfa69..0e32d213 100644 --- a/rust/examples/make_model.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 1823165a..74d24ee7 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,17 +35,17 @@ 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 /// /// 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); @@ -58,7 +58,7 @@ impl StanLibrary { } } - /// Unload the stan library. + /// Unload the Stan library. /// /// # Safety /// @@ -80,28 +80,35 @@ pub struct LoadingError(#[from] libloading::Error); #[derive(Error, Debug)] #[non_exhaustive] pub enum BridgeStanError { + /// 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. #[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), } 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. +/// 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> = @@ -117,7 +124,7 @@ pub fn open_library>(path: P) -> Result { let self_minor: c_int = env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(); let self_patch: c_int = env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(); - if (self_major != major) | (self_minor != minor) { + if !((self_major == major) & (self_minor <= minor)) { return Err(BridgeStanError::BadLibraryVersion( format!("{}.{}.{}", major, minor, patch), format!("{}.{}.{}", self_major, self_minor, self_patch), @@ -143,12 +150,17 @@ 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, } +// 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 {} @@ -204,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| { @@ -244,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") { @@ -259,11 +271,19 @@ 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() } + /// 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) } @@ -285,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( @@ -311,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 { @@ -322,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() @@ -333,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()) } @@ -342,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!( @@ -374,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], @@ -418,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], @@ -472,20 +507,17 @@ 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 unconstrained 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 - /// - /// `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`. + /// *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], @@ -517,7 +549,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" ); } @@ -541,16 +573,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(), @@ -611,7 +634,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(); diff --git a/src/bridgestan.h b/src/bridgestan.h index 4aa9db9a..ab3ca91a 100644 --- a/src/bridgestan.h +++ b/src/bridgestan.h @@ -40,7 +40,7 @@ extern int bs_patch_version; * This PRNG is used for RNG functions in the `transformed data` * block of the model, and then discarded. * @param[out] error_msg a pointer to a string that will be allocated if there - * is an error. This must later be freed by calling `bs_free_error_msg`. + * is an error. This must later be freed by calling bs_free_error_msg(). * @return pointer to constructed model or `nullptr` if construction * fails */ @@ -161,10 +161,10 @@ int bs_param_unc_num(const bs_model* m); * @param[in] theta_unc sequence of unconstrained parameters * @param[out] theta sequence of constrained parameters * @param[in] rng pointer to pseudorandom number generator, should be created - * by `bs_rng_construct`. This is only required when `include_gq` is `true`, + * by bs_rng_construct(). This is only required when `include_gq` is `true`, * otherwise it can be null. * @param[out] error_msg a pointer to a string that will be allocated if there - * is an error. This must later be freed by calling `bs_free_error_msg`. + * is an error. This must later be freed by calling bs_free_error_msg(). * @return code 0 if successful and code -1 if there is an exception * in the underlying Stan code */ @@ -183,7 +183,7 @@ int bs_param_constrain(const bs_model* m, bool include_tp, bool include_gq, * @param[in] theta sequence of constrained parameters * @param[out] theta_unc sequence of unconstrained parameters * @param[out] error_msg a pointer to a string that will be allocated if there - * is an error. This must later be freed by calling `bs_free_error_msg`. + * is an error. This must later be freed by calling bs_free_error_msg(). * @return code 0 if successful and code -1 if there is an exception * in the underlying Stan code */ @@ -200,7 +200,7 @@ int bs_param_unconstrain(const bs_model* m, const double* theta, * @param[in] json JSON-encoded constrained parameters * @param[out] theta_unc sequence of unconstrained parameters * @param[out] error_msg a pointer to a string that will be allocated if there - * is an error. This must later be freed by calling `bs_free_error_msg`. + * is an error. This must later be freed by calling bs_free_error_msg(). * @return code 0 if successful and code -1 if there is an exception * in the underlying Stan code */ @@ -220,7 +220,7 @@ int bs_param_unconstrain_json(const bs_model* m, const char* json, * @param[in] theta_unc unconstrained parameters * @param[out] lp log density to be set * @param[out] error_msg a pointer to a string that will be allocated if there - * is an error. This must later be freed by calling `bs_free_error_msg`. + * is an error. This must later be freed by calling bs_free_error_msg(). * @return code 0 if successful and code -1 if there is an exception * in the underlying Stan code */ @@ -244,7 +244,7 @@ int bs_log_density(const bs_model* m, bool propto, bool jacobian, * @param[out] val log density to be set * @param[out] grad gradient to set * @param[out] error_msg a pointer to a string that will be allocated if there - * is an error. This must later be freed by calling `bs_free_error_msg`. + * is an error. This must later be freed by calling bs_free_error_msg(). * @return code 0 if successful and code -1 if there is an exception * in the underlying Stan code */ @@ -259,7 +259,7 @@ int bs_log_density_gradient(const bs_model* m, bool propto, bool jacobian, * `jacobian` is `true`, and return a return code of 0 for success * and -1 if there is an exception executing the Stan program. The * pointer `grad` must have enough space to hold the gradient. The - * pointer `Hessian` must have enough space to hold the Hessian. + * pointer `hessian` must have enough space to hold the Hessian. * * The gradients are computed using automatic differentiation. the * Hessians are @@ -272,7 +272,7 @@ int bs_log_density_gradient(const bs_model* m, bool propto, bool jacobian, * @param[out] grad gradient to set * @param[out] hessian hessian to set * @param[out] error_msg a pointer to a string that will be allocated if there - * is an error. This must later be freed by calling `bs_free_error_msg`. + * is an error. This must later be freed by calling bs_free_error_msg(). * @return code 0 if successful and code -1 if there is an exception * in the underlying Stan code */ @@ -281,13 +281,13 @@ int bs_log_density_hessian(const bs_model* m, bool propto, bool jacobian, double* hessian, char** error_msg); /** - * Construct an PRNG object to be used in `bs_param_constrain`. + * Construct an PRNG object to be used in bs_param_constrain(). * This object is not thread safe and should be constructed and * destructed for each thread. * * @param[in] seed seed for the RNG * @param[out] error_msg a pointer to a string that will be allocated if there - * is an error. This must later be freed by calling `bs_free_error_msg`. + * is an error. This must later be freed by calling bs_free_error_msg(). */ bs_rng* bs_rng_construct(unsigned int seed, char** error_msg); @@ -307,7 +307,7 @@ void bs_rng_destruct(bs_rng* rng); * never propagate an exception. Passing NULL will redirect printing back to * stdout. * @param[out] error_msg a pointer to a string that will be allocated if there - * is an error. This must later be freed by calling `bs_free_error_msg`. + * is an error. This must later be freed by calling bs_free_error_msg(). * @return code 0 if successful and code -1 if there is an exception */ int bs_set_print_callback(STREAM_CALLBACK callback, char** error_msg); diff --git a/src/version.hpp b/src/version.hpp index ae5c0fab..8a536c1c 100644 --- a/src/version.hpp +++ b/src/version.hpp @@ -12,8 +12,8 @@ #endif #define BRIDGESTAN_MAJOR 2 -#define BRIDGESTAN_MINOR 0 -#define BRIDGESTAN_PATCH 0 +#define BRIDGESTAN_MINOR 1 +#define BRIDGESTAN_PATCH 1 namespace bridgestan { diff --git a/test_models/ode_sundials/ode_sundials.data.json b/test_models/ode_sundials/ode_sundials.data.json new file mode 100644 index 00000000..9e080d3f --- /dev/null +++ b/test_models/ode_sundials/ode_sundials.data.json @@ -0,0 +1,7 @@ +{ + "T": 5, + "y0":[5.0, 6.0], + "t0": 0.0, + "ts": [1.0, 2.0, 3.0, 4.0, 5.0], + "theta": 0.1 +} diff --git a/test_models/ode/ode.stan b/test_models/ode_sundials/ode_sundials.stan similarity index 60% rename from test_models/ode/ode.stan rename to test_models/ode_sundials/ode_sundials.stan index d088e22c..6af2a3dc 100644 --- a/test_models/ode/ode.stan +++ b/test_models/ode_sundials/ode_sundials.stan @@ -1,6 +1,7 @@ -// example model from https://mc-stan.org/docs/stan-users-guide/measurement-error-models.html functions { - vector sho(real t, vector y, real theta) { + vector sho(real t, + vector y, + real theta) { vector[2] dydt; dydt[1] = y[2]; dydt[2] = -y[1] - theta * y[2]; @@ -15,12 +16,11 @@ data { real theta; } model { - } generated quantities { - array[T] vector[2] y_sim = ode_rk45(sho, y0, t0, ts, theta); + array[T] vector[2] y_sim = ode_bdf(sho, y0, t0, ts, theta); // add measurement error - for (t in 1 : T) { + for (t in 1:T) { y_sim[t, 1] += normal_rng(0, 0.1); y_sim[t, 2] += normal_rng(0, 0.1); }