{rstantools}-based compilation

+ cleans up Stan model compilation by switching over to the rstantools-based workflow (see https://mc-stan.org/rstantools/articles/minimal-rstan-package.html for more info)
+ minor documentation change: {devtools} -> {remotes}, which is better for end-users
+ adds RStudio project file which makes it easier for community to get started with contributing to the package
This commit is contained in:
Mikhail Popov 2020-04-28 16:06:17 -04:00
parent 59e374b1ad
commit 25e2b88765
15 changed files with 173 additions and 74 deletions

View file

@ -2,3 +2,5 @@
^libs$
^doc$
^Meta$
^.*\.Rproj$
^\.Rproj\.user$

54
R/.gitignore vendored Normal file
View file

@ -0,0 +1,54 @@
.DS_Store
# History files
.Rhistory
.Rapp.history
# Session Data files
.RData
# User-specific files
.Ruserdata
# Example code in package build process
*-Ex.R
# Output files from R CMD build
/*.tar.gz
# Output files from R CMD check
/*.Rcheck/
# RStudio files
.Rproj.user/
# produced vignettes
vignettes/*.html
vignettes/*.pdf
# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3
.httr-oauth
# knitr and R markdown default cache directories
*_cache/
/cache/
# Temporary files created by R markdown
*.utf8.md
*.knit.md
# R Environment Variables
.Renviron
inst/doc
# Stan
exec/*.rda
src/*.o
src/*.h
src/*.cc
src/*.cpp
src/*.hpp
src/prophet.so
src/prophet.dll
src/init.o

View file

@ -12,8 +12,10 @@ Description: Implements a procedure for forecasting time series data based on
that have strong seasonal effects and several seasons of historical data.
Prophet is robust to missing data and shifts in the trend, and typically
handles outliers well.
URL: https://github.com/facebook/prophet
BugReports: https://github.com/facebook/prophet/issues
Depends:
R (>= 3.2.3),
R (>= 3.4.0),
Rcpp (>= 0.12.0),
rlang (>= 0.3.0.1)
Imports:
@ -22,7 +24,9 @@ Imports:
extraDistr,
ggplot2,
grid,
rstan (>= 2.14.0),
methods,
rstan (>= 2.18.1),
rstantools (>= 2.0.0),
scales,
stats,
tidyr (>= 0.6.1),
@ -32,11 +36,16 @@ Suggests:
testthat,
readr,
rmarkdown
SystemRequirements: GNU make, C++11
Biarch: true
License: MIT + file LICENSE
URL: https://github.com/facebook/prophet
BugReports: https://github.com/facebook/prophet/issues
LinkingTo:
BH (>= 1.66.0),
Rcpp (>= 0.12.0),
RcppEigen (>= 0.3.3.3.0),
rstan (>= 2.18.1),
StanHeaders (>= 2.18.0)
LazyData: true
RoxygenNote: 7.0.2
VignetteBuilder: knitr
SystemRequirements: C++11
Encoding: UTF-8
RoxygenNote: 7.1.0

View file

@ -19,3 +19,4 @@ export(prophet_plot_components)
import(Rcpp)
import(rlang)
importFrom(dplyr,"%>%")
useDynLib(prophet, .registration = TRUE)

View file

@ -80,6 +80,7 @@ globalVariables(c(
#' @importFrom dplyr "%>%"
#' @import Rcpp
#' @import rlang
#' @useDynLib prophet, .registration = TRUE
prophet <- function(df = NULL,
growth = 'linear',
changepoints = NULL,
@ -236,44 +237,6 @@ validate_column_name <- function(
}
}
#' Compile Stan model
#'
#' @return Stan model.
#'
#' @keywords internal
compile_stan_model <- function() {
packageStartupMessage('Compiling model (this will take a few minutes...)')
packageStartupMessage(
'If this is the first time fitting a model since package install, this is normal. ',
'You should not see this message more than once after install.'
)
if (.Platform$OS.type == "windows") {
dest <- file.path(system.file(package="prophet"), 'libs', .Platform$r_arch)
} else {
dest <- file.path(system.file(package="prophet"), 'libs')
}
dir.create(dest, recursive = TRUE, showWarnings = FALSE)
packageStartupMessage(paste('Writing model to:', dest))
packageStartupMessage(paste('Compiling using binary:', R.home('bin')))
fn <- 'stan/prophet.stan'
stan.src <- system.file(fn, package = 'prophet', mustWork = TRUE)
model.binary <- file.path(dest, 'prophet_stan_model.RData')
stanc <- rstan::stanc(stan.src)
model.stanm <- rstan::stan_model(
stanc_ret = stanc,
model_name = 'prophet_model'
)
save('model.stanm', file = model.binary)
packageStartupMessage('------ Model successfully compiled!')
packageStartupMessage('You can ignore any compiler warnings above.')
assign(".prophet.stan.model", model.stanm, envir = prophet_model_env)
return(model.stanm)
}
#' Convert date vector
#'
#' Convert the date to POSIXct object
@ -365,7 +328,7 @@ setup_dataframe <- function(m, df, initialize_scales = FALSE) {
df[[condition.name]] <- as.logical(df[[condition.name]])
}
}
df <- df %>%
dplyr::arrange(ds)
@ -724,7 +687,7 @@ add_regressor <- function(
#' specified, m$seasonality.mode will be used (defaults to 'additive').
#' Additive means the seasonality will be added to the trend, multiplicative
#' means it will multiply the trend.
#'
#'
#' If condition.name is provided, the dataframe passed to `fit` and `predict`
#' should have a column with the specified condition.name containing booleans
#' which decides when to apply seasonality.
@ -741,7 +704,7 @@ add_regressor <- function(
#'
#' @export
add_seasonality <- function(
m, name, period, fourier.order, prior.scale = NULL, mode = NULL,
m, name, period, fourier.order, prior.scale = NULL, mode = NULL,
condition.name = NULL
) {
if (!is.null(m$history)) {
@ -1231,7 +1194,7 @@ fit.prophet <- function(m, df, ...) {
if (exists(".prophet.stan.model", where = prophet_model_env)) {
model <- get('.prophet.stan.model', envir = prophet_model_env)
} else {
model <- compile_stan_model()
model <- stanmodels$prophet
}
stan_init <- function() {
@ -1280,7 +1243,7 @@ fit.prophet <- function(m, df, ...) {
m$params <- m$stan.fit$par
n.iteration <- 1
}
# Cast the parameters to have consistent form, whether full bayes or MAP
for (name in c('delta', 'beta')){
m$params[[name]] <- matrix(m$params[[name]], nrow = n.iteration)
@ -1340,7 +1303,7 @@ predict.prophet <- function(object, df = NULL, ...) {
} else {
intervals <- NULL
}
# Drop columns except ds, cap, floor, and trend
cols <- c('ds', 'trend')
if ('cap' %in% colnames(df)) {

25
R/R/stanmodels.R Normal file
View file

@ -0,0 +1,25 @@
# Generated by rstantools. Do not edit by hand.
# names of stan models
stanmodels <- c("prophet")
# load each stan module
Rcpp::loadModule("stan_fit4prophet_mod", what = TRUE)
# instantiate each stanmodel object
stanmodels <- sapply(stanmodels, function(model_name) {
# create C++ code for stan model
stan_file <- if(dir.exists("stan")) "stan" else file.path("inst", "stan")
stan_file <- file.path(stan_file, paste0(model_name, ".stan"))
stanfit <- rstan::stanc_builder(stan_file,
allow_undefined = TRUE,
obfuscate_model_name = FALSE)
stanfit$model_cpp <- list(model_cppname = stanfit$model_name,
model_cppcode = stanfit$cppcode)
# create stanmodel object
methods::new(Class = "stanmodel",
model_name = stanfit$model_name,
model_code = stanfit$model_code,
model_cpp = stanfit$model_cpp,
mk_cppmodule = function(x) get(paste0("model_", model_name)))
})

4
R/configure vendored Executable file
View file

@ -0,0 +1,4 @@
# Generated by rstantools. Do not edit by hand.
#! /bin/sh
"${R_HOME}/bin/Rscript" -e "rstantools::rstan_config()"

4
R/configure.win Executable file
View file

@ -0,0 +1,4 @@
# Generated by rstantools. Do not edit by hand.
#! /bin/sh
"${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" -e "rstantools::rstan_config()"

View file

@ -0,0 +1 @@
// Insert all #include<foo.hpp> statements here

View file

@ -1,15 +0,0 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/prophet.R
\name{compile_stan_model}
\alias{compile_stan_model}
\title{Compile Stan model}
\usage{
compile_stan_model()
}
\value{
Stan model.
}
\description{
Compile Stan model
}
\keyword{internal}

View file

@ -4,7 +4,9 @@
\name{generated_holidays}
\alias{generated_holidays}
\title{holidays table}
\format{A data frame with five variables: ds, holiday, country, year}
\format{
A data frame with five variables: ds, holiday, country, year
}
\usage{
generated_holidays
}

21
R/prophet.Rproj Normal file
View file

@ -0,0 +1,21 @@
Version: 1.0
RestoreWorkspace: Default
SaveWorkspace: Default
AlwaysSaveHistory: Default
EnableCodeIndexing: Yes
UseSpacesForTab: Yes
NumSpacesForTab: 2
Encoding: UTF-8
RnwWeave: knitr
LaTeX: pdfLaTeX
AutoAppendNewline: Yes
StripTrailingWhitespace: Yes
BuildType: Package
PackageUseDevtools: Yes
PackageInstallArgs: --no-multiarch --with-keep.source
PackageRoxygenize: rd,collate,namespace

15
R/src/Makevars Normal file
View file

@ -0,0 +1,15 @@
# Generated by rstantools. Do not edit by hand.
STANHEADERS_SRC = $(shell "$(R_HOME)/bin$(R_ARCH_BIN)/Rscript" -e "message()" -e "cat(system.file('include', 'src', package = 'StanHeaders', mustWork = TRUE))" -e "message()" | grep "StanHeaders")
PKG_CPPFLAGS = -I"../inst/include" -I"$(STANHEADERS_SRC)" -DBOOST_DISABLE_ASSERTS -DEIGEN_NO_DEBUG -DBOOST_MATH_OVERFLOW_ERROR_POLICY=errno_on_error
CXX_STD = CXX14
all: $(SHLIB)
@if test -e "/usr/bin/install_name_tool" && test -e "/usr/local/clang4/lib/libc++.1.dylib" && test -e "/usr/lib/libc++.1.dylib"; then /usr/bin/install_name_tool -change /usr/local/clang4/lib/libc++.1.dylib /usr/lib/libc++.1.dylib $(SHLIB); fi
clean:
rm -rf *.so *.o
.phony: all clean

14
R/src/Makevars.win Normal file
View file

@ -0,0 +1,14 @@
# Generated by rstantools. Do not edit by hand.
STANHEADERS_SRC = $(shell "$(R_HOME)/bin$(R_ARCH_BIN)/Rscript" -e "message()" -e "cat(system.file('include', 'src', package = 'StanHeaders', mustWork = TRUE))" -e "message()" | grep "StanHeaders")
PKG_CPPFLAGS = -I"../inst/include" -I"$(STANHEADERS_SRC)" -DBOOST_DISABLE_ASSERTS -DEIGEN_NO_DEBUG
CXX_STD = CXX14
all: $(SHLIB)
clean:
RM -rf *.so *.o
.phony: all clean

View file

@ -21,11 +21,11 @@ Prophet is [open source software](https://code.facebook.com/projects/) released
## Installation in R
Prophet is temporarily not on CRAN so you can use the `devtools` package to install it directly from github:
Prophet is temporarily not on CRAN so you can use the `devtools` package to install it directly from GitHub:
```
# R
> devtools::install_github("facebook/prophet", subdir='R', ref="8306ae3")
```R
# install.packages("remotes")
remotes::install_github("facebook/prophet", subdir = "R")
```
After installation, you can [get started!](https://facebook.github.io/prophet/docs/quick_start.html#r-api)
@ -38,11 +38,10 @@ If you have custom Stan compiler settings, install from source rather than the C
## Installation in Python
Prophet is on PyPI, so you can use pip to install it:
Prophet is on PyPI, so you can use `pip` to install it:
```
# bash
$ pip install fbprophet
```bash
pip install fbprophet
```
The default dependency that Prophet has is `pystan`. PyStan has its own [installation instructions](http://pystan.readthedocs.io/en/latest/installation_beginner.html). Install pystan with pip before using pip to install fbprophet.