augurs-prophet
contains an implementation of the Prophet
time series forecasting library.
This crate aims to be low-dependency to enable it to run in as many places as possible. With that said, we need to talk about optimizers…
The original Prophet library uses Stan to handle optimization and MCMC sampling. Stan is a platform for statistical modeling which can perform Bayesian statistical inference as well as maximum likelihood estimation using optimizers such as L-BFGS. However, it is written in C++ and has non-trivial dependencies, which makes it difficult to interface with from Rust (or, indeed, Python).
augurs-prophet
(similar to the Python library) abstracts optimization
and sampling implementations using the Optimizer
and Sampler
traits.
These are yet to be implemented, but I have a few ideas:
This is the approach now taken by the Python implementation, which uses
the cmdstanpy
package and compiles the Stan program into a standalone
binary on installation. It then executes that binary during the fitting
stage to perform optimization or sampling, passing the data and
parameters between Stan and Python using files on the filesystem.
This works fine if you're operating in a desktop or server environment, but poses issues when running in more esoteric environments such as WebAssembly.
We could choose to write a libstan
crate which uses cxx
to
interface directly with the C++ library generated by Stan. Since the
model code is constant (unless we upgrade the version of stanc
used to
generate it), we could also write a small amount of C++ to make it
possible for us to pass data directly to it from Rust.
In theory this should work OK for any target which Stan can compile to.
The problem I've noticed is that Stan isn't particularly careful about
which headers it imports, so even just compiling the model.hpp
library,
you end up with a bunch of I/O and filesystem related headers imported,
which aren't available when using standard WASM.
Perhaps we could clean Stan up so it didn't import those things? We should be able to target most environments in that case.
For WASM, we could abstract the C++ side of things behind a
WASM component which exposes an optimize
interface,
and create a second Prophet component which imports that
interface to implement the Optimizer
trait of this crate.
We could re-implement Stan in a new Rust crate and use that here. This is likely to be by far the largest amount of work!
This implementation is based heavily on the original Prophet Python package. Some changes have been made to make the APIs more idiomatic Rust or to take advantage of the type system.