diff --git a/docs/source/_ext/model_overview.py b/docs/source/_ext/model_overview.py
new file mode 100644
index 000000000..b377e3555
--- /dev/null
+++ b/docs/source/_ext/model_overview.py
@@ -0,0 +1,180 @@
+"""
+Sphinx extension: Auto-generate pytorch_forecasting model overview.
+
+This writes/overwrites docs/source/models.rst during the build,
+listing all registry models with tags and links to API docs.
+"""
+
+from __future__ import annotations
+
+import os
+
+
+def _safe_import_all_objects():
+ try:
+ # prefer public registry interface
+ from pytorch_forecasting._registry import all_objects # type: ignore
+
+ return all_objects, None
+ except Exception as e: # pragma: no cover - defensive
+ return None, e
+
+
+def _render_lines() -> list[str]:
+ all_objects, err = _safe_import_all_objects()
+
+ lines: list[str] = []
+ lines.append("Models")
+ lines.append("======")
+ lines.append("")
+ lines.append("(This page is auto-generated from the registry at build time.)")
+ lines.append("Do not edit manually.")
+ lines.append("")
+
+ if all_objects is None:
+ lines.extend(
+ [
+ ".. note::",
+ " Failed to import registry for model overview.",
+ f" Build-time error: ``{err}``",
+ "",
+ ]
+ )
+ return lines
+
+ try:
+ df = all_objects(
+ object_types=["forecaster_pytorch_v1", "forecaster_pytorch_v2"],
+ as_dataframe=True,
+ return_tags=[
+ "object_type",
+ "info:name",
+ "authors",
+ "python_dependencies",
+ ],
+ return_names=True,
+ )
+ except Exception as e: # pragma: no cover - defensive
+ lines.extend(
+ [
+ ".. note::",
+ f" Registry query failed: ``{e}``",
+ "",
+ ]
+ )
+ return lines
+
+ if df is None or len(df) == 0:
+ lines.extend([".. note::", " No models found in registry.", ""])
+ return lines
+
+ # header
+ lines.append(".. list-table:: Available forecasting models")
+ lines.append(" :header-rows: 1")
+ lines.append(" :widths: 30 15 20 20 15")
+ lines.append("")
+ header_cols = [
+ "Class Name",
+ "Estimator Type",
+ "Authors",
+ "Maintainers",
+ "Dependencies",
+ ]
+ lines.append(" * - " + "\n - ".join(header_cols))
+
+ # rows
+ for _, row in df.sort_values("names").iterrows():
+ pkg_cls = row["objects"]
+ try:
+ model_cls = pkg_cls.get_model_cls()
+ qualname = f"{model_cls.__module__}.{model_cls.__name__}"
+ except Exception:
+ qualname = f"{pkg_cls.__module__}.{pkg_cls.__name__}"
+
+ # Get object type (forecaster_pytorch_v1 or forecaster_pytorch_v2)
+ object_type = row.get("object_type", "")
+ if object_type == "forecaster_pytorch_v1":
+ estimator_type = "forecaster_v1"
+ elif object_type == "forecaster_pytorch_v2":
+ estimator_type = "forecaster_v2"
+ else:
+ estimator_type = object_type
+
+ # Get authors from tags
+ authors = row.get("authors", [])
+ if isinstance(authors, list) and authors:
+ authors_str = ", ".join(authors)
+ else:
+ authors_str = "pytorch-forecasting developers"
+
+ # No maintainers tag exists, so use authors as maintainers
+ maintainers_str = authors_str
+
+ # Get dependencies from tags
+ dependencies = row.get("python_dependencies", [])
+ if isinstance(dependencies, list) and dependencies:
+ dependencies_str = ", ".join(dependencies)
+ else:
+ dependencies_str = "None"
+
+ row_cells = [
+ f":py:class:`~{qualname}`",
+ estimator_type,
+ authors_str,
+ maintainers_str,
+ dependencies_str,
+ ]
+ lines.append(" * - " + "\n - ".join(row_cells))
+
+ lines.append("")
+ return lines
+
+
+def _is_safe_mode() -> bool:
+ """Return True if model overview generation is explicitly disabled.
+
+ By default, generation runs in all environments. Set PF_SKIP_MODEL_OVERVIEW=1 to disable.
+ """
+ if os.environ.get("PF_SKIP_MODEL_OVERVIEW", "").lower() in {"1", "true", "yes"}:
+ return True
+ return False
+
+
+def _write_models_rst(app) -> None:
+ # confdir is docs/source
+ out_file = os.path.join(app.confdir, "models.rst")
+ try:
+ if _is_safe_mode():
+ # minimal page on hosted builders to avoid heavy optional deps
+ lines = [
+ "Models",
+ "======",
+ "",
+ "(Model overview generation is disabled in this build environment.)",
+ "Use a local build to view the full, registry-driven table.",
+ "",
+ ]
+ else:
+ lines = _render_lines()
+ except Exception as exc: # pragma: no cover - defensive
+ lines = [
+ "Models",
+ "======",
+ "",
+ "(Model overview could not be generated due to a build-time error.)",
+ f"Error: ``{exc}``",
+ "",
+ ]
+ os.makedirs(os.path.dirname(out_file), exist_ok=True)
+ with open(out_file, "w", encoding="utf-8") as f:
+ f.write("\n".join(lines))
+
+
+def setup(app):
+ # generate as early as possible so Sphinx sees the written file during source discovery
+ app.connect("config-inited", _write_models_rst)
+ return {
+ "version": "1.0",
+ "parallel_read_safe": True,
+ "parallel_write_safe": True,
+ }
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 0e58692e1..91d839648 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -18,11 +18,18 @@
from sphinx.application import Sphinx
from sphinx.ext.autosummary import Autosummary
from sphinx.pycode import ModuleAnalyzer
+from sphinx.util import logging as sphinx_logging
SOURCE_PATH = Path(os.path.dirname(__file__)) # noqa # docs source
PROJECT_PATH = SOURCE_PATH.joinpath("../..") # noqa # project root
sys.path.insert(0, str(PROJECT_PATH)) # noqa
+sys.path.insert(0, os.path.abspath("../.."))
+
+# make the local _ext folder importable
+_EXT_PATH = SOURCE_PATH.joinpath("_ext")
+if str(_EXT_PATH) not in sys.path:
+ sys.path.insert(0, str(_EXT_PATH))
import pytorch_forecasting # isort:skip
@@ -118,6 +125,9 @@ class ModuleAutoSummary(Autosummary):
def get_items(self, names):
new_names = []
for name in names:
+ # Skip if module doesn't exist in sys.modules
+ if name not in sys.modules:
+ continue
mod = sys.modules[name]
mod_items = getattr(mod, "__all__", mod.__dict__)
for t in mod_items:
@@ -137,6 +147,15 @@ def setup(app: Sphinx):
app.connect("autodoc-skip-member", skip)
app.add_directive("moduleautosummary", ModuleAutoSummary)
app.add_js_file("https://buttons.github.io/buttons.js", **{"async": "async"})
+ # load custom model overview generator if available
+ try:
+ if "model_overview" not in extensions:
+ extensions.append("model_overview")
+ except Exception as exc:
+ # avoid hard-failing docs builds; make the reason visible in Sphinx logs
+ sphinx_logging.getLogger(__name__).warning(
+ "model_overview extension not loaded: %s", exc
+ )
# extension configuration
@@ -190,3 +209,6 @@ def setup(app: Sphinx):
nbsphinx_execute = "never" # always
nbsphinx_allow_errors = False # False
nbsphinx_timeout = 600 # seconds
+
+
+# (model overview generation moved to docs/source/_ext/model_overview.py)
diff --git a/docs/source/models.rst b/docs/source/models.rst
index cd9048b0a..d21db463c 100644
--- a/docs/source/models.rst
+++ b/docs/source/models.rst
@@ -1,166 +1,4 @@
Models
======
-.. _models:
-
-.. currentmodule:: pytorch_forecasting
-
-Model parameters very much depend on the dataset for which they are destined.
-
-PyTorch Forecasting provides a ``.from_dataset()`` method for each model that
-takes a :py:class:`~data.timeseries.TimeSeriesDataSet` and additional parameters
-that cannot directy derived from the dataset such as, e.g. ``learning_rate`` or ``hidden_size``.
-
-To tune models, `optuna `_ can be used. For example, tuning of the
-:py:class:`~models.temporal_fusion_transformer.TemporalFusionTransformer`
-is implemented by :py:func:`~models.temporal_fusion_transformer.tuning.optimize_hyperparameters`
-
-Selecting an architecture
---------------------------
-
-Criteria for selecting an architecture depend heavily on the use-case. There are multiple selection criteria
-and you should take into account. Here is an overview over the pros and cons of the implemented models:
-
-.. csv-table:: Model comparison
- :header: "Name", "Covariates", "Multiple targets", "Regression", "Classification", "Probabilistic", "Uncertainty", "Interactions between series", "Flexible history length", "Cold-start", "Required computational resources (1-5, 5=most)"
-
- :py:class:`~pytorch_forecasting.models.rnn.RecurrentNetwork`, "x", "x", "x", "", "", "", "", "x", "", 2
- :py:class:`~pytorch_forecasting.models.mlp.DecoderMLP`, "x", "x", "x", "x", "", "x", "", "x", "x", 1
- :py:class:`~pytorch_forecasting.models.nbeats.NBeats`, "", "", "x", "", "", "", "", "", "", 1
- :py:class:`~pytorch_forecasting.models.nhits.NHiTS`, "x", "x", "x", "", "", "", "", "", "", 1
- :py:class:`~pytorch_forecasting.models.deepar.DeepAR`, "x", "x", "x", "", "x", "x", "x [#deepvar]_ ", "x", "", 3
- :py:class:`~pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer`, "x", "x", "x", "x", "", "x", "", "x", "x", 4
- :py:class:`~pytorch_forecasting.models.tide.TiDEModel`, "x", "x", "x", "", "", "", "", "x", "", 3
- :py:class:`~pytorch_forecasting.models.xlstm.xLSTMTime`, "x", "x", "x", "", "", "", "", "x", "", 3
-
-.. [#deepvar] Accounting for correlations using a multivariate loss function which converts the network into a DeepVAR model.
-
-Size and type of available data
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-One should particularly consider five criteria.
-
-Availability of covariates
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. _model-covariates:
-
-If you have covariates, that is variables in addition to the target variable itself that hold information
-about the target, then your case will benefit from a model that can accomodate covariates. A model that
-cannot use covariates is :py:class:`~pytorch_forecasting.models.nbeats.NBeats`.
-
-Length of timeseries
-^^^^^^^^^^^^^^^^^^^^^^
-
-The length of time series has a significant impact on which model will work well. Unfortunately,
-most models are created and tested on very long timeseries while in practice short or a mix of short and long
-timeseries are often encountered. A model that can leverage covariates well such as the
-:py:class:`~pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer`
-will typically perform better than other models on short timeseries. It is a significant step
-from short timeseries to making cold-start predictions soley based on static covariates, i.e.
-making predictions without observed history. For example,
-this is only supported by the
-:py:class:`~pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer`
-but does not work tremendously well.
-
-
-Number of timeseries and their relation to each other
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-If your time series are related to each other (e.g. all sales of products of the same company),
-a model that can learn relations between the timeseries can improve accuracy.
-Not that only :ref:`models that can process covariates ` can
-learn relationships between different timeseries.
-If the timeseries denote different entities or exhibit very similar patterns accross the board,
-a model such as :py:class:`~pytorch_forecasting.models.nbeats.NBeats` will not work as well.
-
-If you have only one or very few timeseries,
-they should be very long in order for a deep learning approach to work well. Consider also
-more traditional approaches.
-
-Type of prediction task
-^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Not every can do regression, classification or handle multiple targets. Some are exclusively
-geared towards a single task. For example, :py:class:`~pytorch_forecasting.models.nbeats.NBeats`
-can only be used for regression on a single target without covariates while the
-:py:class:`~pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer` supports
-multiple targets and even hetrogeneous targets where some are continuous variables and others categorical,
-i.e. regression and classification at the same time. :py:class:`~pytorch_forecasting.models.deepar.DeepAR`
-can handle multiple targets but only works for regression tasks.
-
-For long forecast horizon forecasts, :py:class:`~pytorch_forecasting.models.nhits.NHiTS` is an excellent choice
-as it uses interpolation capabilities.
-
-Supporting uncertainty
-~~~~~~~~~~~~~~~~~~~~~~~
-
-Not all models support uncertainty estimation. Those that do, might do so in different fashions.
-Non-parameteric models provide forecasts that are not bound to a given distribution
-while parametric models assume that the data follows a specific distribution.
-
-The parametric models will be a better choice if you
-know how your data (and potentially error) is distributed. However, if you are missing this information or
-cannot make an educated guess that matches reality rather well, the model's uncertainty estimates will
-be adversely impacted. In this case, a non-parameteric model will do much better.
-
-:py:class:`~pytorch_forecasting.models.deepar.DeepAR` is an example for a parameteric model while
-the :py:class:`~pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer`
-can output quantile forecasts that can fit any distribution.
-Models based on normalizing flows marry the two worlds by providing a non-parameteric estimate
-of a full probability distribution. PyTorch Forecasting currently does not provide
-support for these but
-`Pyro, a package for probabilistic programming `_ does
-if you believe that your problem is uniquely suited to this solution.
-
-Computational requirements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Some models have simpler architectures and less parameters than others which can
-lead to significantly different training times. However, this not a general rule as demonstrated
-by Zhuohan et al. in `Train Large, Then Compress: Rethinking Model Size for Efficient Training and Inference of Transformers
-`_. Because the data for a sample for timeseries models is often far samller than it
-is for computer vision or language tasks, GPUs are often underused and increasing the width of models can be an effective way
-to fully use a GPU. This can increase the speed of training while also improving accuracy.
-The other path to pushing utilization of a GPU up is increasing the batch size.
-However, increasing the batch size can adversly affect the generalization abilities of a trained network.
-Also, take into account that often computational resources are mainly necessary for inference/prediction. The upfront task of training
-a models will require developer time (also expensive!) but might be only a small part of the total compuational costs over
-the lifetime of a model.
-
-The :py:class:`~pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer` is
-a rather large model but might benefit from being trained with.
-For example, :py:class:`~pytorch_forecasting.models.nbeats.NBeats` or :py:class:`~pytorch_forecasting.models.nhits.NHiTS` are
-efficient models.
-Autoregressive models such as :py:class:`~pytorch_forecasting.models.deepar.DeepAR` might be quick to train
-but might be slow at inference time (in case of :py:class:`~pytorch_forecasting.models.deepar.DeepAR` this is
-driven by sampling results probabilistically multiple times, effectively increasing the computational burden linearly with the
-number of samples.
-
-
-Implementing new architectures
--------------------------------
-
-Please see the :ref:`Using custom data and implementing custom models ` tutorial on how implement basic and more advanced models.
-
-Every model should inherit from a base model in :py:mod:`~pytorch_forecasting.models.base_model`.
-
-.. autoclass:: pytorch_forecasting.models.base_model.BaseModel
- :noindex:
- :members: __init__
-
-
-
-Details and available models
--------------------------------
-
-See the API documentation for further details on available models:
-
-.. currentmodule:: pytorch_forecasting
-
-.. moduleautosummary::
- :toctree: api/
- :template: custom-module-template.rst
- :recursive:
-
- pytorch_forecasting.models
+*(This file is overwritten during the docs build by the `model_overview` extension. Do not edit manually.)*