BridgeStan 1.0.0 released

We’re very happy to announce the release of version 1.0.0 of BridgeStan.

What is BridgeStan?

To quote from the documentation,

BridgeStan provides efficient in-memory access through Python, Julia, and R to the methods of a Stan model, including log densities, gradients, Hessians, and constraining and unconstraining transforms.

In other words, BridgeStan lets you define a model in Stan, compile it to C++, then access it efficiently through Python, Julia, or R. BridgeStan does not perform any kind of inference. It only provides a hook into log density, gradient and Hessian evaluation as well as the constraining and unconstraining transforms.

BridgeStan provides the model accessibility features of PyStan and RStan to users of our lightweight CmdStan interfaces, cmdstanr and cmdstanpy in a up-to-date (with Stan), performant, easy-to-install, and lightweight package.

BridgeStan also provides a simple C client, which should make it easier to interface with other client languages beyond R, Python, and Julia.

What’s it good for?

The primary goal for BridgeStan is to support both algorithm research and application development.

For example, @YifanZhou built an implementation of zero-variance control variates in Python on top of BridgeStan, which is available through GitHub at zhouyifan233/ControlVariates.

Documentation

The user and developer facing documentation, is here:

The documentation explains how to install BridgeStan on Windows, Linux, or Mac OS X, and how to use it through Python, R, Julia, or C. It also provides technical details and additional links for testing and documentation generation for developers who would like to contribute to BridgeStan.

Source code repository

The source code is all here:

The code is pretty simple—the harder part was getting all the builds, dependencies, and config right.

How does it work?

Like RStan and the original PyStan, BridgeStan uses a client-specific foreign function interface to communicate between the client language (R, Python, or Julia) and C++ (or C). That means there’s no additional processes to manage and no communication overhead as all of the data is shared with the client language in a single process. It also imports the makefile magic from CmdStan to manage Stan model transpiration to C++ and then the compilation of that C++.

It works through a combination of makefile magic and connections to Python, Julia, and R at the lowest integration level. That is, rather than using a high-level interface like Rcpp (R) or Cython (Python), we use the .C interface to R and the ctypes interface to Python. The advantage of this is fewer dependencies and easier installation.

There are some more details in the foreign function interface overview.

There are dependencies on stanc3 (the transpiler that converts Stan to C++), stan (which implements the basic model interface), and the Stan math library which implements autodiff, constrained variable transforms, and all the special functions.

Hessians are implemented following RStan using central finite differences over gradients. Clients can also configure BridgeStan to use the faster and more accurate autodiff-based Hessian calculations in cases where all of the functions are supported for higher-order differentiation (our implicit functions like ODE integrators do not support autodiff for second derivatives yet).

History

The BridgeStan project was started by @roualdes as a Julia interface, and then @WardBrian and I got involved. BridgeStan descends directly from @syclik’s and @dmuck’s ReddingStan, which is an out-of-process server-based approach to the same problem.

ReddingStan was in turn motivated by httpstan, which is the foundation of PyStan3; both httpstan and PyStan3 are primarily maintained by @ariddell with help from @ahartikainen and @mjcarter.

The major difference between BridgeStan and both ReddingStan and httpstan is that BridgeStan runs in memory through a foreign function interface rather than as a server. In this way, BridgeStan is closer in spirit to RStan and the original pre-httpstan design of PyStan, both of which accessed models in memory through a (higher-level) foreign function interface.

Not an official Stan project

We intentionally decided to not try to make this an official Stan project in order to maintain control through the design phase. This was mainly at my urging, because I’ve been very frustrated in the past being overruled on all of our interface designs.

We might be amenable to making it an official Stan project at a later date.

18 Likes

Sounds neat!

If I recall, the issue with using bridge sampling with cmdstanr was that cmdstanr didn’t provide access to the log posterior density calculation. This would seem to resolve that issue.

Will there eventually be support to expose user-defined Stan functions?

1 Like

The github version of cmdstanr currently has support for accessing the log-posterior density calculation and exposing user-defined functions, if that’s any help

1 Like

No, there’s no good/general mechanism to do this in a language independent way. I have a separate project which does this for Python (currently on Mac/Linux only) GitHub - WardBrian/pybind_expose_stan_fns: Exposing Stan functions in Python

CmdStanR will support exposing Stan functions in R in a future release (currently on GitHub, as @andrjohns mentioned)

1 Like

Thanks for exploring some of the possibilities between languages! I wonder if the spirit of C-API could be explored in the other direction? For instance, I have some model implementations that don’t fit well with the Stan language, but I’d like to make use of Stan’s HMC implementation: in principle, I should be able to define a function like

int target(double *lp, double *lpgrad, double *theta);

and pass it to an API like

go_nuts(&nuts_config, &target)

and get some samples. This would make it enormously easier for model writers in Python & Julia who can’t/won’t shoehorn their model defs into Stan language. And it kinda fits with the BridgeStan theme, at least that was my goal with pystanpy which relies on CmdStan, though ideally, one could drive the sampler from the foreign language instead.

Doing exactly what you describe is tricky, since the Stan C++ implementation of the algorithms is very tightly coupled with the autodiff specifics of the math library.

That said, @Bob_Carpenter is currently working on something with the same goal, which is a library of sampler implementations in Python. The theory is that if the target/gradient calculations are done in compiled code, then the fact that HMC itself is implemented in Python (possibly using numba or cython) should be “fast enough” while making it easy to prototype on top of, use code from different sources, etc.

3 Likes

We currently use Rstan to calculate gradients for an already fitted model. That is, we fit a model, getting samples for all parameters. Then we “turn the model inside out” and build models where the parameters are data, and some other quantity, x, is parameter. Using the Rstan interface, we can then calculate gradient (df/dx) which we use in an optimisation algorithm.
Bridgestan looks very suitable for us. However, our main concern is being able to switch out the data of the Stan object very fast, because we want to evaluate gradients with different posterior samples as input (data).
Would Bridgestan make that faster?

2 Likes

I believe RStan will still be faster for this purpose. RStan is able to pass data in memory, while BridgeStan only has in-memory passage of parameters. If data/model initialization is truly your bottleneck it is most likely you will still want to use RStan.

I suppose the exception to this is if you already have the data saved in a format like JSON, it will be probably just as quick, but if the data is being created on the fly in R I think the overhead of serializing it might be noticeable

1 Like