Source code for sparsehydro.models.torch_model
"""PyTorch-compatible model interface for gradient-based parameter estimation.
:class:`ITorchModel` combines the sparsehydro :class:`~sparsehydro.models.IModel`
lifecycle with ``torch.nn.Module``, enabling automatic differentiation through the
model computation so that parameters can be optimised with gradient-based methods
(e.g. Adam, L-BFGS).
PyTorch is an *optional* dependency. If it is not installed, importing this
module defines a placeholder class that raises :class:`ImportError` on
instantiation, preserving the package's ability to run without torch.
Install with::
pip install sparsehydro[torch]
"""
from __future__ import annotations
from abc import abstractmethod
from typing import Any
from ..enums import ModelState
from .base import IModel
try:
import torch
import torch.nn as nn
class ITorchModel(IModel, nn.Module):
"""Abstract interface for differentiable parsimonious models.
Inherits from both :class:`~sparsehydro.models.IModel` and
``torch.nn.Module``. Subclasses must implement all five
:class:`~sparsehydro.models.IModel` lifecycle methods and
:meth:`forward` — the differentiable computation graph.
The default :meth:`predict` implementation calls :meth:`forward` and
advances model state to ``PREDICTED``. Override :meth:`predict` if
additional pre/post-processing around :meth:`forward` is required.
Gradient-tracked parameters should be declared as ``torch.nn.Parameter``
attributes so that PyTorch optimisers can discover them via
``Module.parameters()``. The sparsehydro
:meth:`~sparsehydro.models.IModel.register_scalar_parameter` /
:meth:`~sparsehydro.models.IModel.register_vector_parameter`
registry holds *metadata* (bounds, units, description) for the same
parameters to allow calibration algorithms to apply constraints.
Example::
import torch
import torch.nn as nn
from sparsehydro import ModelState, ScalarParameter
from sparsehydro.models import ITorchModel
class LinearReservoir(ITorchModel):
def initialize(self) -> None:
self.k = nn.Parameter(torch.tensor(0.5))
self.register_scalar_parameter(
ScalarParameter("k", value=0.5,
lower_bound=0.0, upper_bound=1.0)
)
self._state = ModelState.INITIALIZED
def validate(self) -> bool:
ok = self.parameters_valid()
if ok:
self._state = ModelState.VALIDATED
return ok
def prepare(self, forcing: torch.Tensor) -> None:
self._forcing = forcing
self._state = ModelState.PREPARED
def forward(self, forcing: torch.Tensor) -> torch.Tensor:
return self.k * forcing
def finalize(self) -> None:
self._state = ModelState.FINALIZED
"""
def __init__(self) -> None:
IModel.__init__(self)
nn.Module.__init__(self)
[docs]
@abstractmethod
def forward(self, *args: Any, **kwargs: Any) -> "torch.Tensor":
"""Differentiable forward pass producing the model output.
Subclasses implement the model computation using torch operations
so that gradients flow back to the registered ``torch.nn.Parameter``
attributes.
:param args: Positional forcing inputs (e.g. a rainfall tensor).
:type args: Any
:param kwargs: Keyword forcing inputs.
:type kwargs: Any
:returns: The model output tensor with gradient tracking enabled.
:rtype: torch.Tensor
"""
[docs]
def predict(self, *args: Any, **kwargs: Any) -> "torch.Tensor":
"""Run the differentiable forward pass and advance model state.
Delegates to :meth:`forward` and sets the model state to
:attr:`~sparsehydro.enums.ModelState.PREDICTED`.
:param args: Positional forcing inputs forwarded to :meth:`forward`.
:type args: Any
:param kwargs: Keyword forcing inputs forwarded to :meth:`forward`.
:type kwargs: Any
:returns: The output tensor returned by :meth:`forward`.
:rtype: torch.Tensor
"""
result = self.forward(*args, **kwargs)
self._state = ModelState.PREDICTED
return result
except ImportError: # pragma: no cover
[docs]
class ITorchModel: # type: ignore[no-redef]
"""Placeholder raised when PyTorch is not installed.
Install PyTorch to use this class::
pip install sparsehydro[torch]
:raises ImportError: Always raised on instantiation.
"""
def __init__(self, *args: Any, **kwargs: Any) -> None:
raise ImportError(
"PyTorch is required to use ITorchModel. "
"Install it with: pip install sparsehydro[torch]"
)