From 9968f8be41c9c771cf092df45b8d4693f0e411c8 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Mon, 4 Apr 2022 10:06:59 -0400 Subject: [PATCH] Simplify setup.py, make CmdStan the default backend (#2088) --- .github/workflows/build-and-test.yml | 20 +-- .github/workflows/wheel.yml | 39 +---- README.md | 32 ++++- python/MANIFEST.in | 3 +- python/README.md | 19 ++- python/prophet/models.py | 23 ++- python/prophet/tests/test_diagnostics.py | 2 +- python/pyproject.toml | 3 +- python/requirements.txt | 2 +- python/setup.py | 156 +++++--------------- python/stan/{unix => }/prophet.stan | 0 python/stan/win/prophet.stan | 175 ----------------------- 12 files changed, 88 insertions(+), 386 deletions(-) rename python/stan/{unix => }/prophet.stan (100%) delete mode 100644 python/stan/win/prophet.stan diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 0332240..c500546 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -6,9 +6,6 @@ on: pull_request: branches: [ main ] -env: - CMDSTAN_VERSION: "2.26.1" - jobs: build-and-test-python: @@ -38,26 +35,11 @@ jobs: key: ${{ runner.os }}-pip-${{ hashFiles('**/python/requirements.txt') }}-v1 restore-keys: | ${{ runner.os }}-pip- - - name: "Restore cmdstan cache" - id: cache-cmdstan - uses: actions/cache@v2 - with: - path: ${{ env.DEFAULT_HOME }}/.cmdstan - key: ${{ runner.os }}-cmdstan-${{ env.CMDSTAN_VERSION }}-v1 - - name: "Download cmdstan" - if: steps.cache-cmdstan.outputs.cache-hit != 'true' - run: | - wget https://github.com/stan-dev/cmdstan/releases/download/v${{ env.CMDSTAN_VERSION }}/cmdstan-${{ env.CMDSTAN_VERSION }}.tar.gz -O /tmp/cmdstan.tar.gz &> /dev/null - mkdir $HOME/.cmdstan - tar -xf /tmp/cmdstan.tar.gz -C $HOME/.cmdstan &> /dev/null - name: Install and test run: | pip install -U -r python/requirements.txt dask[dataframe] distributed cd python - STAN_BACKEND=PYSTAN python setup.py develop test - python setup.py clean - rm -rf prophet/stan_model - STAN_BACKEND=CMDSTANPY python setup.py develop test + python setup.py develop test build-and-test-r: diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index 5e685bb..7211c32 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -5,9 +5,9 @@ on: types: [ created ] workflow_dispatch: {} + env: - STAN_BACKEND: "PYSTAN,CMDSTANPY" - CMDSTAN_VERSION: "2.26.1" + STAN_BACKEND: "CMDSTANPY" jobs: make-wheel-windows: @@ -55,13 +55,6 @@ jobs: python -m pip install --upgrade pip python -m pip install cibuildwheel build delvewheel - - name: "Restore cmdstan cache" - id: cache-cmdstan - uses: actions/cache@v2 - with: - path: ${{ env.DEFAULT_HOME }}/.cmdstan - key: ${{ runner.os }}-cmdstan-${{ env.CMDSTAN_VERSION }}-v1 - - name: "Restore RTools40" id: cache-rtools uses: actions/cache@v2 @@ -69,21 +62,12 @@ jobs: path: C:/rtools40 key: ${{ runner.os }}-${{ env.OS_VERSION }}-rtools-v1 - - name: "Download cmdstan" - if: steps.cache-cmdstan.outputs.cache-hit != 'true' - run: | - $ProgressPreference = "SilentlyContinue" - Invoke-WebRequest https://github.com/stan-dev/cmdstan/releases/download/v${{ env.CMDSTAN_VERSION }}/cmdstan-${{ env.CMDSTAN_VERSION }}.tar.gz -OutFile "D:/a/_temp/cmdstan.tar.gz" - New-Item -Path "$HOME" -Name ".cmdstan" -ItemType "directory" - tar -xf "D:/a/_temp/cmdstan.tar.gz" -C "$HOME/.cmdstan" - - name: "Build wheel" run: | cd python && python -m cibuildwheel --output-dir wheelhouse env: CIBW_ENVIRONMENT: > STAN_BACKEND="${{ env.STAN_BACKEND }}" - CMDSTAN_VERSION=${{ env.CMDSTAN_VERSION }} PIP_CACHE_DIR="${{ env.PIP_DEFAULT_CACHE }}" CIBW_BUILD: cp36-* cp37-* cp38-* CIBW_ARCHS: native @@ -112,17 +96,16 @@ jobs: - name: "Get OS version (Linux)" if: startsWith(runner.os, 'Linux') run: | - echo "OS_VERSION=`lsb_release -sr`" >> $GITHUB_ENV echo "PIP_DEFAULT_CACHE=$HOME/.cache/pip" >> $GITHUB_ENV echo "DEFAULT_HOME=$HOME" >> $GITHUB_ENV - name: "Get OS version (macOS)" if: startsWith(runner.os, 'macOS') run: | - echo "OS_VERSION=`sw_vers -productVersion`" >> $GITHUB_ENV echo "PIP_DEFAULT_CACHE=$HOME/Library/Caches/pip" >> $GITHUB_ENV echo "DEFAULT_HOME=$HOME" >> $GITHUB_ENV + - name: "Checkout repo" uses: actions/checkout@v2 @@ -146,31 +129,15 @@ jobs: python -m pip install --upgrade pip python -m pip install cibuildwheel build - - name: "Restore cmdstan cache" - id: cache-cmdstan - uses: actions/cache@v2 - with: - path: ${{ env.DEFAULT_HOME }}/.cmdstan - key: ${{ runner.os }}-cmdstan-${{ env.CMDSTAN_VERSION }}-v1 - - - name: "Download cmdstan" - if: steps.cache-cmdstan.outputs.cache-hit != 'true' - run: | - wget https://github.com/stan-dev/cmdstan/releases/download/v${{ env.CMDSTAN_VERSION }}/cmdstan-${{ env.CMDSTAN_VERSION }}.tar.gz -O /tmp/cmdstan.tar.gz &> /dev/null - mkdir $HOME/.cmdstan - tar -xf /tmp/cmdstan.tar.gz -C $HOME/.cmdstan &> /dev/null - - name: "Build wheel" run: | cd python && python -m cibuildwheel --output-dir wheelhouse env: CIBW_ENVIRONMENT: > STAN_BACKEND="${{ env.STAN_BACKEND }}" - CMDSTAN_VERSION=${{ env.CMDSTAN_VERSION }} # Linux builds run in a Docker container, need to point the cache to the host machine. CIBW_ENVIRONMENT_LINUX: > STAN_BACKEND="${{ env.STAN_BACKEND }}" - CMDSTAN_VERSION=${{ env.CMDSTAN_VERSION }} HOME="/host/${{ env.DEFAULT_HOME }}" PIP_CACHE_DIR="/host/${{ env.PIP_DEFAULT_CACHE }}" CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 diff --git a/README.md b/README.md index 7590d43..c527715 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ On Windows, R requires a compiler so you'll need to [follow the instructions](ht If you have custom Stan compiler settings, install from source rather than the CRAN binary. -## Installation in Python +## Installation in Python - PyPI release Prophet is on PyPI, so you can use `pip` to install it. From v0.6 onwards, Python 2 is no longer supported. As of v1.0, the package name on PyPI is "prophet"; prior to v1.0 it was "fbprophet". @@ -64,12 +64,9 @@ Prophet is on PyPI, so you can use `pip` to install it. From v0.6 onwards, Pytho # Install pystan with pip before using pip to install prophet # pystan>=3.0 is currently not supported pip install pystan==2.19.1.1 - pip install prophet ``` -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 prophet. - #### Experimental backend - cmdstanpy You can also choose a (more experimental) alternative stan backend called `cmdstanpy`. It requires the [CmdStan](https://mc-stan.org/users/interfaces/cmdstan) command line interface and you will have to specify the environment variable `STAN_BACKEND` pointing to it, for example: @@ -90,20 +87,41 @@ $ CMDSTAN=/tmp/cmdstan-2.22.1 STAN_BACKEND=PYSTAN,CMDSTANPY pip install prophet After installation, you can [get started!](https://facebook.github.io/prophet/docs/quick_start.html#python-api) -If you upgrade the version of PyStan installed on your system, you may need to reinstall prophet ([see here](https://github.com/facebook/prophet/issues/324)). +If you upgraded the version of PyStan installed on your system, you may need to reinstall prophet ([see here](https://github.com/facebook/prophet/issues/324)). ### Anaconda -Use `conda install gcc` to set up gcc. The easiest way to install Prophet is through conda-forge: `conda install -c conda-forge prophet`. +The easiest way to install Prophet is through conda-forge: `conda install -c conda-forge prophet`. ### Windows -On Windows, PyStan requires a compiler so you'll need to [follow the instructions](https://pystan2.readthedocs.io/en/latest/windows.html). The easiest way to install Prophet in Windows is in Anaconda. +The easiest way to install Prophet in Windows is in Anaconda. ### Linux Make sure compilers (gcc, g++, build-essential) and Python development tools (python-dev, python3-dev) are installed. In Red Hat systems, install the packages gcc64 and gcc64-c++. If you are using a VM, be aware that you will need at least 4GB of memory to install prophet, and at least 2GB of memory to use prophet. +## Installation in Python - Development version + +Since Pystan2 is no longer being maintained, the python package will move to depend solely on `cmdstanpy` (benefits described [here](https://github.com/facebook/prophet/issues/2041)). This has been updated in the development version of the package (1.1), but this version hasn't yet been released to PyPI. If you would like to use `cmdstanpy` only for your workflow, you can clone this repo and build from source manually: + +```bash +git clone https://github.com/facebook/prophet.git +cd prophet/python +python -m install -r requirements.txt +python setup.py develop +``` + +By default, Prophet will use a fixed version of `cmdstan` (downloading and installing it if necessary) to compile the model executables. If this is undesired and you would like to use your own existing `cmdstan` installation, you can set the environment variable `PROPHET_REPACKAGE_CMDSTAN` to `False`: + +```bash +export PROPHET_REPACKAGE_CMDSTAN=False; python setup.py develop +``` + +### Windows + +Using `cmdstanpy` with Windows requires a Unix-compatible C compiler such as mingw-gcc. If cmdstanpy is installed first, one can be installed via the `cmdstanpy.install_cxx_toolchain` command. + ## Changelog ### Version 1.0 (2021.03.28) diff --git a/python/MANIFEST.in b/python/MANIFEST.in index 383a431..450a487 100644 --- a/python/MANIFEST.in +++ b/python/MANIFEST.in @@ -1,5 +1,4 @@ -include stan/unix/*.stan -include stan/win/*.stan +include stan/*.stan include LICENSE include requirements.txt diff --git a/python/README.md b/python/README.md index 61f8ccf..708ba4b 100644 --- a/python/README.md +++ b/python/README.md @@ -18,18 +18,17 @@ Full documentation and examples available at the homepage: https://facebook.gith - Rob Hyndman's [forecast package](http://robjhyndman.com/software/forecast/) - [Statsmodels](http://statsmodels.sourceforge.net/) -## Installation +## Installation - PyPI release -```shell -pip install prophet -``` -Note: Installation requires PyStan, which has its [own installation instructions](http://pystan.readthedocs.io/en/latest/installation_beginner.html). -On Windows, PyStan requires a compiler so you'll need to [follow the instructions](http://pystan.readthedocs.io/en/latest/windows.html). - The key step is installing a recent [C++ compiler](https://visualstudio.microsoft.com/visual-cpp-build-tools/) +See [Installation in Python - PyPI release](https://github.com/facebook/prophet#installation-in-python---pypi-release) -## Installation using Docker and docker-compose (via Makefile) +## Installation - Development version -Simply type `make build` and if everything is fine you should be able to `make shell` or alternative jump directly to `make py-shell`. +See [Installation in Python - Development version](https://github.com/facebook/prophet#installation-in-python---development-version) + +### Installation using Docker and docker-compose (via Makefile) + +Simply type `make build` and if everything is fine you should be able to `make shell` or alternative jump directly to `make py-shell`. To run the tests, inside the container `cd python/prophet` and then `python -m unittest` @@ -41,4 +40,4 @@ To run the tests, inside the container `cd python/prophet` and then `python -m u >>> m.fit(df) # df is a pandas.DataFrame with 'y' and 'ds' columns >>> future = m.make_future_dataframe(periods=365) >>> m.predict(future) - ``` +``` diff --git a/python/prophet/models.py b/python/prophet/models.py index 747b268..08ec2a6 100644 --- a/python/prophet/models.py +++ b/python/prophet/models.py @@ -6,6 +6,7 @@ from __future__ import absolute_import, division, print_function from abc import abstractmethod, ABC +from tempfile import mkdtemp from typing import Tuple from collections import OrderedDict from enum import Enum @@ -61,30 +62,21 @@ class IStanBackend(ABC): class CmdStanPyBackend(IStanBackend): CMDSTAN_VERSION = "2.26.1" def __init__(self): - super().__init__() import cmdstanpy - cmdstanpy.set_cmdstan_path( - pkg_resources.resource_filename("prophet", f"stan_model/cmdstan-{self.CMDSTAN_VERSION}") + # this must be set before super.__init__() for load_model to work on Windows + local_cmdstan = pkg_resources.resource_filename( + "prophet", f"stan_model/cmdstan-{self.CMDSTAN_VERSION}" ) + if Path(local_cmdstan).exists(): + cmdstanpy.set_cmdstan_path(local_cmdstan) + super().__init__() @staticmethod def get_type(): return StanBackendEnum.CMDSTANPY.name - def _add_tbb_to_path(self): - """Add the TBB library to $PATH on Windows only. Required for loading model binaries.""" - if PLATFORM == "win": - tbb_path = pkg_resources.resource_filename( - "prophet", - f"stan_model/cmdstan-{self.CMDSTAN_VERSION}/stan/lib/stan_math/lib/tbb" - ) - os.environ["PATH"] = ";".join( - list(OrderedDict.fromkeys([tbb_path] + os.environ.get("PATH", "").split(";"))) - ) - def load_model(self): import cmdstanpy - self._add_tbb_to_path() model_file = pkg_resources.resource_filename( 'prophet', 'stan_model/prophet_model.bin', @@ -102,6 +94,7 @@ class CmdStanPyBackend(IStanBackend): inits=stan_init, algorithm='Newton' if stan_data['T'] < 100 else 'LBFGS', iter=int(1e4), + output_dir = mkdtemp(), ) args.update(kwargs) diff --git a/python/prophet/tests/test_diagnostics.py b/python/prophet/tests/test_diagnostics.py index 81b54cd..1b8fcd4 100644 --- a/python/prophet/tests/test_diagnostics.py +++ b/python/prophet/tests/test_diagnostics.py @@ -168,7 +168,7 @@ class TestDiagnostics(TestCase): period='10 days', cutoffs=[pd.Timestamp('2012-07-31'), pd.Timestamp('2012-08-31')]) self.assertEqual(len(df_cv1['cutoff'].unique()), 2) - + def test_cross_validation_uncertainty_disabled(self): df = self.__df.copy() for uncertainty in [0, False]: diff --git a/python/pyproject.toml b/python/pyproject.toml index fe3533a..79eaa3a 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -2,7 +2,6 @@ requires = [ "setuptools>=42", "wheel", - "pystan~=2.19.1.1", - "cmdstanpy==0.9.77", + "cmdstanpy>=1.0.0", ] build-backend = "setuptools.build_meta" diff --git a/python/requirements.txt b/python/requirements.txt index 5dddf49..31d0855 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -1,5 +1,5 @@ Cython>=0.22 -cmdstanpy==0.9.77 +cmdstanpy>=1.0.0 pystan~=2.19.1.1 numpy>=1.15.4 pandas>=1.0.4 diff --git a/python/setup.py b/python/setup.py index de91ae2..1f9f4ee 100644 --- a/python/setup.py +++ b/python/setup.py @@ -4,11 +4,8 @@ # LICENSE file in the root directory of this source tree. import os -import pickle -import platform -import subprocess import sys -from collections import OrderedDict +import platform from pathlib import Path from shutil import copy, copytree, rmtree from typing import List @@ -20,18 +17,10 @@ from setuptools.command.build_py import build_py from setuptools.command.develop import develop from setuptools.command.test import test as test_command -PLATFORM = "unix" -if platform.platform().startswith("Win"): - PLATFORM = "win" -MODEL_DIR = os.path.join("stan", PLATFORM) +MODEL_DIR = "stan" MODEL_TARGET_DIR = os.path.join("prophet", "stan_model") -# TODO: Remove when upgrading to cmdstanpy 1.0, use cmdstanpy internals instead -# cmdstan utils -MAKE = os.getenv("MAKE", "make" if PLATFORM != "win" else "mingw32-make") -EXTENSION = ".exe" if PLATFORM == "win" else "" - CMDSTAN_VERSION = "2.26.1" BINARIES_DIR = "bin" BINARIES = ["diagnose", "print", "stanc", "stansummary"] @@ -39,72 +28,6 @@ TBB_PARENT = "stan/lib/stan_math/lib" TBB_DIRS = ["tbb", "tbb_2019_U8"] -# TODO: Remove when upgrading to cmdstanpy 1.0, use cmdstanpy internals instead -def clean_all_cmdstan(verbose: bool = False) -> None: - """Run `make clean-all` in the current directory (must be a cmdstan library). - - Parameters - ---------- - verbose: when ``True``, print build msgs to stdout. - """ - cmd = [MAKE, "clean-all"] - proc = subprocess.Popen( - cmd, - cwd=None, - stdin=subprocess.DEVNULL, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env=os.environ, - ) - while proc.poll() is None: - if proc.stdout: - output = proc.stdout.readline().decode("utf-8").strip() - if verbose and output: - print(output, flush=True) - _, stderr = proc.communicate() - if proc.returncode: - msgs = ['Command "make clean-all" failed'] - if stderr: - msgs.append(stderr.decode("utf-8").strip()) - raise RuntimeError("\n".join(msgs)) - - -# TODO: Remove when upgrading to cmdstanpy 1.0, use cmdstanpy internals instead -def build_cmdstan(verbose: bool = False) -> None: - """Run `make build` in the current directory (must be a cmdstan library). - - Parameters - ---------- - verbose: when ``True``, print build msgs to stdout. - """ - cmd = [MAKE, "build"] - proc = subprocess.Popen( - cmd, - cwd=None, - stdin=subprocess.DEVNULL, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env=os.environ, - ) - while proc.poll() is None: - if proc.stdout: - output = proc.stdout.readline().decode("utf-8").strip() - if verbose and output: - print(output, flush=True) - _, stderr = proc.communicate() - if proc.returncode: - msgs = ['Command "make build" failed'] - if stderr: - msgs.append(stderr.decode("utf-8").strip()) - raise RuntimeError("\n".join(msgs)) - # Add tbb to the $PATH on Windows - if PLATFORM == "win": - libtbb = os.path.join(os.getcwd(), "stan", "lib", "stan_math", "lib", "tbb") - os.environ["PATH"] = ";".join( - list(OrderedDict.fromkeys([libtbb] + os.environ.get("PATH", "").split(";"))) - ) - - def prune_cmdstan(cmdstan_dir: str) -> None: """ Keep only the cmdstan executables and tbb files (minimum required to run a cmdstanpy commands on a pre-compiled model). @@ -116,6 +39,7 @@ def prune_cmdstan(cmdstan_dir: str) -> None: rmtree(temp_dir) temp_dir.mkdir() + print("Copying ", original_dir, " to ", temp_dir, " for pruning") copytree(original_dir / BINARIES_DIR, temp_dir / BINARIES_DIR) for f in (temp_dir / BINARIES_DIR).iterdir(): if f.is_dir(): @@ -128,51 +52,42 @@ def prune_cmdstan(cmdstan_dir: str) -> None: rmtree(original_dir) temp_dir.rename(original_dir) - -def get_cmdstan_cache() -> str: - """Default directory for an existing cmdstan library. Prevents unnecessary re-downloads of cmdstan.""" - return Path.home().resolve() / ".cmdstan" / f"cmdstan-{CMDSTAN_VERSION}" +def repackage_cmdstan(): + return os.environ.get("PROPHET_REPACKAGE_CMDSTAN", "").lower() not in ["false", "0"] -def download_cmdstan(cache_dir: Path) -> None: - """Ensure the cmdstan library exists in the cache directory.""" - import cmdstanpy - from cmdstanpy.install_cmdstan import retrieve_version - - if cache_dir.is_dir(): - print(f"Found existing cmdstan library at {cache_dir}") - else: - print(f"Downloading cmdstan to {cache_dir}") - cache_dir.parent.mkdir(parents=True, exist_ok=True) - with cmdstanpy.utils.pushd(cache_dir.parent): - retrieve_version(version=CMDSTAN_VERSION, progress=False) - - -def install_cmdstan_toolchain(): +def maybe_install_cmdstan_toolchain(): """Install C++ compilers required to build stan models on Windows machines.""" + import cmdstanpy from cmdstanpy.install_cxx_toolchain import main as _install_cxx_toolchain - _install_cxx_toolchain() + + try: + cmdstanpy.utils.cxx_toolchain_path() + except Exception: + _install_cxx_toolchain({"version": None, "dir": None, "verbose": True}) + cmdstanpy.utils.cxx_toolchain_path() def install_cmdstan_deps(cmdstan_dir: Path): import cmdstanpy + from multiprocessing import cpu_count - cmdstan_cache = get_cmdstan_cache() - download_cmdstan(cmdstan_cache) - if cmdstan_dir.is_dir(): - rmtree(cmdstan_dir) - copytree(cmdstan_cache, cmdstan_dir) + if repackage_cmdstan(): + if platform.platform().startswith("Win"): + maybe_install_cmdstan_toolchain() + print("Installing cmdstan to", cmdstan_dir) + if os.path.isdir(cmdstan_dir): + rmtree(cmdstan_dir) - if PLATFORM == "win": - try: - cmdstanpy.utils.cxx_toolchain_path() - except Exception: - install_cmdstan_toolchain() - - with cmdstanpy.utils.pushd(cmdstan_dir): - clean_all_cmdstan() - build_cmdstan() - cmdstanpy.set_cmdstan_path(str(cmdstan_dir)) + if not cmdstanpy.install_cmdstan( + version=CMDSTAN_VERSION, + dir=cmdstan_dir.parent, + overwrite=True, + verbose=True, + cores=cpu_count(), + progress=True, + ): + raise RuntimeError("CmdStan failed to install in repackaged directory") def build_cmdstan_model(target_dir): @@ -186,17 +101,21 @@ def build_cmdstan_model(target_dir): target_dir: Directory to copy the compiled model executable and core cmdstan files to. """ import cmdstanpy + cmdstan_dir = (Path(target_dir) / f"cmdstan-{CMDSTAN_VERSION}").resolve() install_cmdstan_deps(cmdstan_dir) model_name = "prophet.stan" target_name = "prophet_model.bin" sm = cmdstanpy.CmdStanModel(stan_file=os.path.join(MODEL_DIR, model_name)) copy(sm.exe_file, os.path.join(target_dir, target_name)) + # Clean up for f in Path(MODEL_DIR).iterdir(): if f.is_file() and f.name != model_name: os.remove(f) - prune_cmdstan(cmdstan_dir) + + if repackage_cmdstan(): + prune_cmdstan(cmdstan_dir) def build_pystan_model(target_dir): @@ -204,6 +123,7 @@ def build_pystan_model(target_dir): Compile the stan model using pystan and pickle it. The pickle is copied to {target_dir}/prophet_model.pkl. """ import pystan + import pickle model_name = "prophet.stan" target_name = "prophet_model.pkl" @@ -215,7 +135,7 @@ def build_pystan_model(target_dir): def get_backends_from_env() -> List[str]: - return os.environ.get("STAN_BACKEND", "PYSTAN").split(",") + return os.environ.get("STAN_BACKEND", "CMDSTANPY").split(",") def build_models(target_dir): @@ -322,7 +242,7 @@ with open("requirements.txt", "r") as f: setup( name="prophet", - version="1.0.1", + version="1.1", description="Automatic Forecasting Procedure", url="https://facebook.github.io/prophet/", author="Sean J. Taylor , Ben Letham ", @@ -330,7 +250,7 @@ setup( license="MIT", packages=find_packages(), install_requires=install_requires, - python_requires=">=3", + python_requires=">=3.6", zip_safe=False, include_package_data=True, ext_modules=[Extension("prophet.stan_model", [])], diff --git a/python/stan/unix/prophet.stan b/python/stan/prophet.stan similarity index 100% rename from python/stan/unix/prophet.stan rename to python/stan/prophet.stan diff --git a/python/stan/win/prophet.stan b/python/stan/win/prophet.stan deleted file mode 100644 index b32112e..0000000 --- a/python/stan/win/prophet.stan +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. - -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree. - -functions { - real[ , ] get_changepoint_matrix(real[] t, real[] t_change, int T, int S) { - // Assumes t and t_change are sorted. - real A[T, S]; - real a_row[S]; - int cp_idx; - - // Start with an empty matrix. - A = rep_array(0, T, S); - a_row = rep_array(0, S); - cp_idx = 1; - - // Fill in each row of A. - for (i in 1:T) { - while ((cp_idx <= S) && (t[i] >= t_change[cp_idx])) { - a_row[cp_idx] = 1; - cp_idx = cp_idx + 1; - } - A[i] = a_row; - } - return A; - } - - // Logistic trend functions - - real[] logistic_gamma(real k, real m, real[] delta, real[] t_change, int S) { - real gamma[S]; // adjusted offsets, for piecewise continuity - real k_s[S + 1]; // actual rate in each segment - real m_pr; - - // Compute the rate in each segment - k_s[1] = k; - for (i in 1:S) { - k_s[i + 1] = k_s[i] + delta[i]; - } - - // Piecewise offsets - m_pr = m; // The offset in the previous segment - for (i in 1:S) { - gamma[i] = (t_change[i] - m_pr) * (1 - k_s[i] / k_s[i + 1]); - m_pr = m_pr + gamma[i]; // update for the next segment - } - return gamma; - } - - real[] logistic_trend( - real k, - real m, - real[] delta, - real[] t, - real[] cap, - real[ , ] A, - real[] t_change, - int S, - int T - ) { - real gamma[S]; - real Y[T]; - - gamma = logistic_gamma(k, m, delta, t_change, S); - for (i in 1:T) { - Y[i] = cap[i] / (1 + exp(-(k + dot_product(A[i], delta)) - * (t[i] - (m + dot_product(A[i], gamma))))); - } - return Y; - } - - // Linear trend function - - real[] linear_trend( - real k, - real m, - real[] delta, - real[] t, - real[ , ] A, - real[] t_change, - int S, - int T - ) { - real gamma[S]; - real Y[T]; - - for (i in 1:S) { - gamma[i] = -t_change[i] * delta[i]; - } - for (i in 1:T) { - Y[i] = (k + dot_product(A[i], delta)) * t[i] + ( - m + dot_product(A[i], gamma)); - } - return Y; - } - - // Flat trend function - - real[] flat_trend( - real m, - int T - ) { - return rep_array(m, T); - } - - -} - -data { - int T; // Number of time periods - int K; // Number of regressors - real t[T]; // Time - real cap[T]; // Capacities for logistic trend - real y[T]; // Time series - int S; // Number of changepoints - real t_change[S]; // Times of trend changepoints - real X[T,K]; // Regressors - vector[K] sigmas; // Scale on seasonality prior - real tau; // Scale on changepoints prior - int trend_indicator; // 0 for linear, 1 for logistic, 2 for flat - real s_a[K]; // Indicator of additive features - real s_m[K]; // Indicator of multiplicative features -} - -transformed data { - real A[T, S]; - A = get_changepoint_matrix(t, t_change, T, S); -} - -parameters { - real k; // Base trend growth rate - real m; // Trend offset - real delta[S]; // Trend rate adjustments - real sigma_obs; // Observation noise - real beta[K]; // Regressor coefficients -} - -transformed parameters { - real trend[T]; - real Y[T]; - real beta_m[K]; - real beta_a[K]; - - if (trend_indicator == 0) { - trend = linear_trend(k, m, delta, t, A, t_change, S, T); - } else if (trend_indicator == 1) { - trend = logistic_trend(k, m, delta, t, cap, A, t_change, S, T); - } else if (trend_indicator == 2){ - trend = flat_trend(m, T); - } - - for (i in 1:K) { - beta_m[i] = beta[i] * s_m[i]; - beta_a[i] = beta[i] * s_a[i]; - } - - for (i in 1:T) { - Y[i] = ( - trend[i] * (1 + dot_product(X[i], beta_m)) + dot_product(X[i], beta_a) - ); - } -} - -model { - //priors - k ~ normal(0, 5); - m ~ normal(0, 5); - delta ~ double_exponential(0, tau); - sigma_obs ~ normal(0, 0.5); - beta ~ normal(0, sigmas); - - // Likelihood - y ~ normal(Y, sigma_obs); -}