Source code for mojolearn._x_sequence_autoarima

# SPDX-License-Identifier: Apache-2.0
# Copyright 2026 Andrew Hendel. Part of mojolearn, https://doi.org/10.5281/zenodo.22068632
"""AutoARIMA: batched automatic ARIMA order selection, following cuML's
``AutoARIMA``."""
import itertools

from ._optional_numpy import require_numpy
np = require_numpy('_x_sequence_autoarima')

from . import _portable_math as _pm
from ._arima_impl import ARIMA
from ._tsa_impl import select_d
from ._buffer import _native

_IC = ("aic", "aicc", "bic")


def _rows_where(words, want, count):
    """The ascending positions whose int32 word is `want` (int64, `count`
    of them), by the base binding's select_fold_i64."""
    n = len(words)
    out = np.empty(max(count, 1), dtype=np.int64)
    scratch = np.empty(max(n, 1), dtype=np.int64)
    got = int(_native("select_fold_i64")(words.ctypes.data, n, int(want),
                                         out.ctypes.data if count else scratch.ctypes.data, scratch.ctypes.data))
    if got != count:
        raise RuntimeError("AutoARIMA: select_fold_i64 disagrees with the counts")
    return out[:count]


def _counts(words, k):
    """Rows per int32 word value in [0, k) (bincount)."""
    out = np.zeros(k, dtype=np.int64)
    if len(words):
        _native("bincount_i64")(words.ctypes.data, 2, len(words), k, out.ctypes.data, 0)
    return out


def _take(block, ids):
    """block[ids] for a C-contiguous 2-D (or 1-D) block, a byte gather."""
    out = np.empty((len(ids),) + block.shape[1:], dtype=block.dtype)
    if len(ids):
        ids = np.ascontiguousarray(ids, dtype=np.int64)
        _native("gather_rows_bytes")(block.ctypes.data, out.ctypes.data, ids.ctypes.data, block.shape[0],
                                     len(ids), block.itemsize * (block.size // max(block.shape[0], 1)))
    return out


def _put(block, ids, rows):
    """block[ids] = rows, a byte scatter."""
    rows = np.ascontiguousarray(rows, dtype=block.dtype)
    if len(ids):
        ids = np.ascontiguousarray(ids, dtype=np.int64)
        _native("scatter_rows_bytes")(rows.ctypes.data, ids.ctypes.data, len(ids),
                                      block.itemsize * (block.size // max(block.shape[0], 1)),
                                      block.ctypes.data, block.shape[0])


def _gather_block(src, offset, width, nrows, local, dst_addr):
    """dst rows = src[offset:].reshape(nrows, width)[local], a byte gather
    of float32 / int32 rows (the base binding's gather_rows_bytes)."""
    if len(local):
        ids = np.ascontiguousarray(local, dtype=np.int64)
        _native("gather_rows_bytes")(src.ctypes.data + src.itemsize * offset, dst_addr, ids.ctypes.data,
                                     nrows, len(ids), src.itemsize * width)


def _adopt_search_fit(m, y, search_fit, trial, local):
    """`m` (an unfitted ARIMA of grid row `trial`) fitted from the search's
    fit block for the group positions `local`, through ARIMA._adopt_fit,
    the same state `ARIMA.fit` sets (MOJOLEARN_ARIMA_FAST_SEARCH_REUSE)."""
    from ._arima_impl import _series_major, _no_exog
    from ._buffer import empty, addr
    fit_f32, fit_i32, f_offs, nb, grouped_ll = search_fit
    arr, b, n_obs, copied = _series_major(y, "y")
    N = int(m.complexity_)
    params = empty((b * N,), "<f4")
    x = empty((b * N,), "<f4")
    x0 = empty((b * N,), "<f4")
    stats = empty((2 * b,), "<f4")
    flags = empty((2 * b,), "<i4")
    off = f_offs[trial]
    _gather_block(fit_f32, off, N, nb, local, addr(params, name="params"))
    _gather_block(fit_f32, off + nb * N, N, nb, local, addr(x, name="x"))
    _gather_block(fit_f32, off + 2 * nb * N, N, nb, local, addr(x0, name="x0"))
    sa, fa = addr(stats, name="stats"), addr(flags, name="flags")
    _gather_block(grouped_ll, trial * nb, 1, nb, local, sa)
    _gather_block(fit_f32, off + 3 * nb * N, 1, nb, local, sa + 4 * b)
    _gather_block(fit_i32, 2 * nb * trial, 1, nb, local, fa)
    _gather_block(fit_i32, 2 * nb * trial + nb, 1, nb, local, fa + 4 * b)
    return m._adopt_fit(arr, copied, 2, b, n_obs, _no_exog(), 0, params, x, x0, stats, flags)


def _k_options(fit_intercept, d_, D_, const_both):
    """The constant options of a d group. "auto": the constant when d + D <= 1
    (cuML's rule); with MOJOLEARN_ARIMA_FAST_CONST_BOTH (`const_both`, bit 3
    of `arima_fast_search_mode`) both 0 and 1 when d + D <= 1, statsforecast
    `search_arima`'s `for K in range(max_K + 1)` (allowmean for d + D == 0,
    allowdrift for d + D == 1), k innermost in the grid as there."""
    if fit_intercept == "auto":
        if d_ + D_ > 1:
            return [0]
        return [0, 1] if const_both else [1]
    return _options("k", fit_intercept, 0, 1)


def _options(name, value, lo, hi):
    """The reference's `_parse_sequence`: an int or an iterable, clipped to
    [lo, hi]; empty is refused."""
    vals = [int(value)] if isinstance(value, (int, np.integer)) else [int(v) for v in value]  # glue: user order options, a few ints
    vals = [v for v in vals if lo <= v <= hi]  # glue: user order options, a few ints
    if not vals:
        raise ValueError(f"AutoARIMA: no valid option for {name} in [{lo}, {hi}]")
    return vals


# The order search of cuML `python/cuml/cuml/tsa/auto_arima.pyx`
# (`AutoARIMA.search`, `fit`, `predict`, `forecast`) over mojolearn's batched
# `ARIMA` (`arima/`, the `_mojolearn_arima` binding) and the KPSS choice of `d`
# (`tsa/`, `select_d`). The search, as the reference's:
#   1. `d` per series: the KPSS test at d = 0, 1, ... (`select_d`), unless one
#      `d` is given; series are grouped by their `d`.
#   2. For each group, every (p, q, P, Q, k) of the product of the options, in
#      `itertools.product` order, skipping p + q + P + Q + k == 0, is fitted on
#      the whole group as one batch; each series keeps the order of least
#      information criterion, the FIRST such order on a tie (the reference's
#      `_divide_by_min` is an argmin).
#   3. `fit` refits every chosen order on its sub-batch.
# The information criterion is batched_arima.cu `information_criterion`'s:
# -2 loglike + 2N (aic), + 2N(N+1)/(n - N - 1) (aicc), log(n) N (bic), with
# n = n_obs - d - s D and N the model's parameter count, computed in float64 in
# Mojo (`ic_running_min_f64`) from the fit's float32 log-likelihood. Builds whose
# ARIMA binding reports `arima_order_caps() & 4` take the criterion in float32,
# one rounding of the same value, on the device with the order choice
# (`arima_order_search_device`) or, on the host column and for grids the grouped
# search declines, in Mojo (`ic_running_min_f32`).
# `s` of 0, None or 1 is non-seasonal (the reference's `if s is 1: s = None`).
# seasonal_test="seas" over a D list of two options is statsmodels' STL, not a
# GPU path in the reference either.
[docs] class AutoARIMA: """Automatic ARIMA order selection for a batch of series. Chooses, per series, the differencing order ``d`` by the KPSS test and then the (p, q, P, Q) orders and intercept with the least information criterion, following cuML's ``AutoARIMA.search``; :meth:`fit` then fits each chosen model with :class:`ARIMA`. Every candidate order is fitted on all the series that share a ``d`` as one batch; on a tie the first order tried wins. ``endog`` is laid out one series per row, ``(batch_size, n_obs)``; cuML's AutoARIMA takes series in columns, so transpose to call it the same way. Fitting is maximum likelihood only. Parameters ---------- endog : array-like of shape (batch_size, n_obs) or (n_obs,) The series, float32. float64 raises TypeError. Attributes ---------- d_ : ndarray of shape (batch_size,), int64 Differencing order chosen for each series. Set by :meth:`search`. order_ : ndarray of shape (batch_size, 8), int64 Chosen ``(p, d, q, P, D, Q, s, k)`` per series, ``k`` 1 with an intercept. Set by :meth:`search`. ic_ : ndarray of shape (batch_size,), float64 Information criterion of the chosen order. Set by :meth:`search`. models : list Distinct chosen models as ``((p, d, q), (P, D, Q, s), k)``. Examples -------- >>> import numpy as np, mojolearn >>> y = np.random.default_rng(0).standard_normal((8, 120)).cumsum(axis=1).astype(np.float32) >>> model = mojolearn.AutoARIMA(y).search(d=range(2), p=range(3), q=range(3)).fit() >>> model.forecast(10).shape (8, 10) """ #: The bindings the search runs, through `ARIMA` and `select_d` (named #: here so the lane map derives them and their CPU host bindings). _ARIMA_BINDING = "_mojolearn_arima" _TSA_BINDING = "_mojolearn_tsa"
[docs] def __init__(self, endog): y = np.asarray(endog) if y.dtype == np.float64: raise TypeError("AutoARIMA: float64 is refused; pass float32 (no float64 on the device)") y = np.ascontiguousarray(y, dtype=np.float32) if y.ndim == 1: y = y.reshape(1, -1) if y.ndim != 2 or y.size == 0: raise ValueError("AutoARIMA: endog must be (batch_size, n_obs)") self.endog = y self.batch_size, self.n_obs = int(y.shape[0]), int(y.shape[1]) self.models = []
[docs] def search(self, s=None, d=range(3), D=range(2), p=range(1, 4), q=range(1, 4), P=range(3), Q=range(3), fit_intercept="auto", ic="aicc", test="kpss", seasonal_test="seas", h=1e-8, maxiter=20, method="auto", truncate=0): """Select the orders for every series. Parameters ---------- s : int or None, default None Seasonal period. None, 0 or 1 means non-seasonal (then D, P and Q are 0). d : int or iterable of int, default range(3) Differencing options; a list must be ``0, 1, ..., d_max``. With one value it is used for every series; otherwise the KPSS test picks ``d`` per series. D : int or iterable of int, default range(2) Seasonal differencing; with ``s`` set, give one value (the seasonal test over several values is not supported). p, q : int or iterable of int, default range(1, 4) Non-seasonal AR and MA order options, clipped to [0, 4] (or [0, s - 1] when seasonal). P, Q : int or iterable of int, default range(3) Seasonal AR and MA order options, clipped to [0, 4] (0 when non-seasonal). fit_intercept : bool or "auto", default "auto" Whether candidates include an intercept. ic : {"aic", "aicc", "bic"}, default "aicc" Information criterion. test : {"kpss"}, default "kpss" Stationarity test for ``d``. seasonal_test : str, default "seas" Only used with several ``D`` options, which are not supported. h : float, default 1e-8 Only the default is supported. maxiter : int, default 20 Optimizer iterations per candidate fit. method : {"auto", "ml"}, default "auto" Fitting method; maximum likelihood. ``"css"`` and ``"css-ml"`` raise NotImplementedError. truncate : int, default 0 Only 0 is supported. Returns ------- self Raises ------ ValueError For an unknown ``ic`` or ``test``, or an order option with no valid value. NotImplementedError For css methods, a non-default ``h`` or ``truncate``, several ``D`` options with a season, or a ``d`` list that is not ``0..d_max``. """ if ic not in _IC: raise ValueError(f"AutoARIMA: ic must be one of {_IC}") if test != "kpss": raise ValueError("AutoARIMA: test must be 'kpss', the reference's only stationarity test") if method not in ("auto", "ml"): raise NotImplementedError("AutoARIMA: this ARIMA fits by 'ml' only; css and css-ml are refused") if h != 1e-8 or truncate: raise NotImplementedError("AutoARIMA: h and truncate are not carried by mojolearn.ARIMA") # the reference's `if s is 1: s = None` (R users pass s=1 for a # non-seasonal series): s <= 1 is non-seasonal, so D = P = Q = 0 and # p, q range over 0..4, not 0..s-1 s = int(s) if s and int(s) > 1 else 0 if s: # the reference's D choice: one option is taken as given; only a # list of several runs the seasonal test D_opts = _options("D", D, 0, 1) if len(D_opts) > 1: raise NotImplementedError( f"AutoARIMA: seasonal_test={seasonal_test!r} (statsmodels' STL in the reference) is " "not implemented; pass D as one value") D_ = D_opts[0] else: D_ = 0 d_opts = _options("d", d, 0, 2 - D_) p_opts = _options("p", p, 0, s - 1 if s else 4) q_opts = _options("q", q, 0, s - 1 if s else 4) P_opts = _options("P", P, 0, 4 if s else 0) Q_opts = _options("Q", Q, 0, 4 if s else 0) y = self.endog fast_mode = self._fast_mode() if len(d_opts) == 1: dser = np.full(self.batch_size, d_opts[0], dtype=np.int64) elif fast_mode & 32 and D_ == 0 and d_opts == list(range(len(d_opts))): # MOJOLEARN_ARIMA_FAST_KPSS_D: statsforecast's `ndiffs` (KPSS # level test, lags floor(3 sqrt(n) / 13), alpha 0.05, up to # d_max) for every series in one device launch # (fast_arima_quality.ndiffs_kpss). UNMEASURED as of 2026-10-05. d32 = np.empty(self.batch_size, dtype=np.int32) written = int(ARIMA()._extension().arima_ndiffs_kpss( y.ctypes.data, d32.ctypes.data, [self.batch_size, self.n_obs, d_opts[-1]])) if written != self.batch_size: raise RuntimeError("AutoARIMA: incomplete ndiffs output") dser = np.asarray(d32, dtype=np.int64) # glue: widen the device's choice else: if d_opts != list(range(len(d_opts))): raise NotImplementedError("AutoARIMA: a d option list must be 0, 1, ..., d_max") # the whole batch in one call, (n_obs, n_series) (apple2: one call # per series was 2000 launches and waits, 2.4 s of the search on # an M4 Pro). select_d is a pure function of each series' bits # (tsa/checks/stationarity_check.mojo gates the batch composition # invariant), so every series' d is the one-series call's. dser = np.asarray(select_d(np.ascontiguousarray(y.T), D=D_, s=s, d_max=d_opts[-1]), dtype=np.int64).reshape(-1) self.d_ = dser self.models, self._ids = [], [] # MOJOLEARN_ARIMA_FAST_SEARCH_REUSE: per chosen model, the search's # fit block it can reuse (None when the search wrote none) self._reuse = [] self._search_maxiter = int(maxiter) self._ic = ic self.order_ = np.zeros((self.batch_size, 8), dtype=np.int64) self.ic_ = np.zeros(self.batch_size, dtype=np.float64) # the series of each d by the base binding (bincount, select, gather; # lane cgr4-py-compute), the groups in ascending d dw = np.ascontiguousarray(dser, dtype=np.int32) dcount = _counts(dw, 3) # TOMBSTONE: MOJOLEARN_ARIMA_FAST_CSS_SEARCH (DROPPED-quality) and MOJOLEARN_ARIMA_FAST_STEPWISE (DROPPED-slower) # deleted 2026-10-09 on lane/owed-deletions-D1 (bits 0/1 of arima_fast_search_mode, the CSS / stepwise search # calls and the grouped final fit); code recoverable at b639a2bd2. # Restore: git apply experiments/removed/MOJOLEARN_ARIMA_FAST_CSS_SEARCH.patch; record in docs/TOMBSTONES.md. # lane apple-fast-arima-quality: MOJOLEARN_ARIMA_FAST_CONST_BOTH (bit # 3) every order with and without the constant in the same search # call; MOJOLEARN_ARIMA_FAST_ROOT_CHECK (bit 4) is native (the # searches reject roots of modulus < 1.01) const_both = bool(fast_mode & 8) self._root_check = bool(fast_mode & 16) # TOMBSTONE: MOJOLEARN_ARIMA_FAST_D_CONCURRENT (DROPPED-slower, +5-7%) deleted 2026-10-09 on # lane/owed-deletions-D1 (every d group in one native call); code recoverable at b639a2bd2. # Restore: git apply experiments/removed/MOJOLEARN_ARIMA_FAST_GROUPS_CONCURRENT.patch; record in docs/TOMBSTONES.md. for d_ in range(3): # glue: the three differencing orders d if not dcount[d_]: continue ids = _rows_where(dw, d_, int(dcount[d_])) sub = _take(y, ids) k_opts = _k_options(fit_intercept, d_, D_, const_both) orders, nb = [], len(ids) search_fit = None binding = ARIMA()._extension() caps_fn = getattr(binding, "arima_order_caps", None) caps = int(caps_fn()) if caps_fn is not None else 0 ic_dtype = np.float32 if caps & 4 else np.float64 ic_fold = _native("ic_running_min_f32" if caps & 4 else "ic_running_min_f64") best_ic = np.empty(nb, dtype=ic_dtype) best = np.empty(nb, dtype=np.int64) ic_k = np.empty(nb, dtype=ic_dtype) # Metadata only: retain the exact itertools.product order. The # native experiment groups GPU work by state dimension but writes # likelihood rows back in this order, preserving first-min ties. grid = [o for o in itertools.product(p_opts, q_opts, P_opts, Q_opts, k_opts) # glue: user order metadata if o[0] + o[1] + o[2] + o[3] + o[4]] # glue: user order options, no series data grouped_ll = None chosen_on_device = False plain = not s and D_ == 0 and 4 not in p_opts and 4 not in q_opts enabled = getattr(binding, "arima_order_batch_enabled", None) if grid and self.n_obs > 2 and enabled is not None and enabled(): if caps & 1 and (plain or caps & 2): # lane fam2-timeseries: the grouped search on the device # end to end (seasonal grids with caps & 2); with # caps & 4 the criterion and the first-minimum choice # are device kernels and only the choice crosses back. # 0 written = a grid the build declines (its workspace # bound, or the period rule): the per-order fits below. want_ic = 1 if caps & 4 else 0 packed_grid = [v for o in grid for v in o] # glue: native order metadata arguments pens = [self._penalty_of(sum(o) + 1, ic, d_, D_, s if (o[2] + D_ + o[3]) else 0) for o in grid] # glue: one penalty scalar per order best32 = np.empty(nb, dtype=np.int32) out = np.empty(nb if want_ic else len(grid) * nb, dtype=np.float32) written = int(binding.arima_order_search_device( sub.ctypes.data, out.ctypes.data, best32.ctypes.data, packed_grid, pens, [nb, self.n_obs, d_, D_, s, int(maxiter), want_ic])) if written and written != out.size: raise RuntimeError("AutoARIMA: incomplete grouped search output") if written and want_ic: chosen_on_device = True best = np.asarray(best32, dtype=np.int64) # glue: widen the device's choice best_ic = out elif written: grouped_ll = out.reshape(len(grid), nb) elif plain: grouped_ll = np.empty((len(grid), nb), dtype=np.float32) packed_grid = [v for p_, q_, _, _, k_ in grid for v in (p_, q_, k_)] # glue: native order metadata arguments reuse_on = getattr(binding, "arima_search_reuse_enabled", None) if reuse_on is not None and reuse_on(): # MOJOLEARN_ARIMA_FAST_SEARCH_REUSE: the search also # writes every order's fit block (fast_order_search # order_search_fit): f32 params, x, x0 (nb * N each) # and fx (nb) per order; i32 n_iter, retcode per order f_offs, off = [], 0 for p_, q_, _, _, k_ in grid: # glue: order metadata, block offsets f_offs.append(off) off += nb * (3 * (p_ + q_ + k_ + 1) + 1) fit_f32 = np.empty(max(off, 1), dtype=np.float32) fit_i32 = np.empty(max(2 * nb * len(grid), 1), dtype=np.int32) written = int(binding.arima_order_search_fit( sub.ctypes.data, grouped_ll.ctypes.data, fit_f32.ctypes.data, fit_i32.ctypes.data, packed_grid, [nb, self.n_obs, d_, int(maxiter)])) search_fit = (fit_f32, fit_i32, f_offs, nb, grouped_ll) else: written = int(binding.arima_order_search(sub.ctypes.data, grouped_ll.ctypes.data, packed_grid, [nb, self.n_obs, d_, int(maxiter)])) if written == 0: grouped_ll = None # the build's workspace bound: per-order fits search_fit = None elif written != grouped_ll.size: raise RuntimeError("AutoARIMA: incomplete grouped likelihood output") for trial, (p_, q_, P_, Q_, k_) in enumerate(grid): # glue: user order grid, not series or observations s_ = s if (P_ + D_ + Q_) else 0 orders.append((p_, q_, P_, Q_, s_, k_)) if chosen_on_device: continue m = ARIMA(order=(p_, d_, q_), seasonal_order=(P_, D_, Q_, s_), trend="c" if k_ else "n", maxiter=maxiter) if grouped_ll is None: m.fit(sub) llf = np.ascontiguousarray(m.llf_, dtype=np.float32).reshape(-1) else: llf = np.ascontiguousarray(grouped_ll[trial]) # every series' criterion and the running first-minimum # choice, in Mojo (lane cgr4-py-compute) ic_fold(llf.ctypes.data, nb, self._penalty(m, ic, d_, D_, s_), trial, ic_k.ctypes.data, best_ic.ctypes.data, best.ctypes.data) if not orders: raise ValueError("AutoARIMA: no (p, q, P, Q, k) order to try") table = np.asarray([[p_, d_, q_, P_, D_, Q_, s_, k_] for (p_, q_, P_, Q_, s_, k_) in orders], # glue: one table row per tried order dtype=np.int64) _put(self.order_, ids, _take(table, best)) _put(self.ic_, ids, best_ic) bw = np.ascontiguousarray(best, dtype=np.int32) bcount = _counts(bw, len(orders)) for i, (p_, q_, P_, Q_, s_, k_) in enumerate(orders): # glue: tried orders, not series or observations if not bcount[i]: continue local = _rows_where(bw, i, int(bcount[i])) chosen = _take(ids, local) self.models.append(((p_, d_, q_), (P_, D_, Q_, s_), k_)) self._ids.append(chosen) self._reuse.append(None if search_fit is None else (search_fit, i, local)) self._fitted = [None] * len(self.models) return self
@staticmethod def _fast_mode(): """The ARIMA binding's FAST search switches (`arima_fast_search_mode`: bits 3..5 the quality switches); 0 for a binding without them (the host column, other builds).""" fn = getattr(ARIMA()._extension(), "arima_fast_search_mode", None) return int(fn()) if fn is not None else 0 def _penalty(self, m, ic, d_, D_, s_): """The criterion's parameter penalty (a scalar per order): 2N (aic), + 2N(N+1)/(n - N - 1) (aicc), log(n) N (bic); the criterion is -2 loglike + penalty, per series in `ic_running_min_f64`.""" return self._penalty_of(m.complexity_, ic, d_, D_, s_) def _penalty_of(self, complexity, ic, d_, D_, s_): """`_penalty` from the parameter count alone (no model object).""" N = float(complexity) n = float(self.n_obs - d_ - s_ * D_) if ic == "aic": return 2.0 * N if ic == "aicc": return 2.0 * N + 2.0 * N * (N + 1.0) / (n - N - 1.0) return _pm.log(n) * N # the pinned log, as ARIMA.bic_ (DEVIATION 6900)
[docs] def fit(self, h=1e-8, maxiter=1000, method="ml", truncate=0): """Fit the chosen model of every series. Call :meth:`search` first. Parameters ---------- h : float, default 1e-8 Only the default is supported. maxiter : int, default 1000 Optimizer iterations. method : {"ml"}, default "ml" Maximum likelihood only. truncate : int, default 0 Only 0 is supported. Returns ------- self Raises ------ RuntimeError If :meth:`search` has not been called. """ if not self.models: raise RuntimeError("AutoARIMA: call search() before fit()") if h != 1e-8 or truncate or method != "ml": raise NotImplementedError("AutoARIMA.fit: method 'ml' with the default h only") reuse = getattr(self, "_reuse", None) or [None] * len(self.models) for i, (order, sorder, k) in enumerate(self.models): # glue: chosen order groups, at most the grid m = ARIMA(order=order, seasonal_order=sorder, trend="c" if k else "n", maxiter=maxiter) if reuse[i] is not None and int(maxiter) == self._search_maxiter: # MOJOLEARN_ARIMA_FAST_SEARCH_REUSE: the search ran this order # on these series with the fit's start, optimizer and maxiter, # per series independently; adopt that optimum, no refit self._fitted[i] = _adopt_search_fit(m, _take(self.endog, self._ids[i]), *reuse[i]) else: self._fitted[i] = m.fit(_take(self.endog, self._ids[i])) return self
def _gather(self, fn, width): if any(m is None for m in self._fitted): # glue: chosen order groups, at most the grid raise RuntimeError("AutoARIMA: call fit() first") out = np.zeros((self.batch_size, width), dtype=np.float32) for m, ids in zip(self._fitted, self._ids): # glue: chosen order groups, at most the grid _put(out, ids, np.asarray(fn(m), dtype=np.float32).reshape(len(ids), width)) return out
[docs] def predict(self, start=0, end=None, level=None): """In-sample (and out-of-sample) predictions from ``start`` to ``end``. Parameters ---------- start : int, default 0 First index to predict. end : int or None, default None One past the last index; None means ``n_obs``. level : None Prediction intervals are not supported; anything other than None raises NotImplementedError. Returns ------- ndarray of shape (batch_size, end - start), float32 """ if level is not None: raise NotImplementedError("AutoARIMA.predict: prediction intervals (level) are not implemented") end = self.n_obs if end is None else int(end) return self._gather(lambda m: m.predict(start, end), end - int(start))
[docs] def forecast(self, nsteps, level=None): """Forecast ``nsteps`` past the end of every series. Parameters ---------- nsteps : int Forecast horizon. level : None Prediction intervals are not supported; anything other than None raises NotImplementedError. Returns ------- ndarray of shape (batch_size, nsteps), float32 """ if level is not None: raise NotImplementedError("AutoARIMA.forecast: prediction intervals (level) are not implemented") return self._gather(lambda m: m.forecast(int(nsteps)), int(nsteps))