Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
max-parallel: 8
matrix:
platform: [ "ubuntu-latest", "macos-latest", "windows-latest" ]
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ]
python-version: [ "3.10", "3.11", "3.12", "3.13" ]
poetry-version: [ "1.8.5" ]

steps:
Expand Down Expand Up @@ -58,7 +58,7 @@ jobs:
- name: Install the project dependencies
run: poetry install -E docs

- name: Run static analysis and linters
- name: Run static analysis, linters and mypy
run: poetry run poe check

- name: Run tests
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
python-version: "3.12"

- name: Install Poetry
uses: abatilo/actions-poetry@v4
Expand Down
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@ repos:
language: system
types: [ python ]
require_serial: true
- id: check-types
name: check-types
entry: poetry run poe check-types-pre-commit
language: system
types: [ python ]
require_serial: true
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## v1.3.0 (14.04.2025)

* Bump minimal Python version to 3.10
* Fix type annotations
* Fix checking types by mypy

## v1.2.1 (10.04.2025)

* Update dependencies
Expand Down
4 changes: 2 additions & 2 deletions csaps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from csaps._shortcut import AutoSmoothingResult, csaps
from csaps._sspndg import NdGridCubicSmoothingSpline, NdGridSplinePPForm
from csaps._sspumv import CubicSmoothingSpline, SplinePPForm
from csaps._types import MultivariateDataType, NdGridDataType, UnivariateDataType
from csaps._types import MultivariateDataType, SequenceUnivariateDataType, UnivariateDataType
from csaps._version import __version__

__all__ = [
Expand All @@ -23,6 +23,6 @@
# Type-hints
'UnivariateDataType',
'MultivariateDataType',
'NdGridDataType',
'SequenceUnivariateDataType',
'__version__',
]
15 changes: 10 additions & 5 deletions csaps/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import numpy as np

from ._types import TData, TExtrapolate, TNu, TProps, TSmooth, TSpline, TXi
from ._types import TData, TExtrapolate, TNu, TProps, TSmooth, TSpline, TXi, FloatNDArrayType


class ISplinePPForm(abc.ABC, Generic[TData, TProps]):
Expand All @@ -22,7 +22,7 @@ def breaks(self) -> TData:

Returns
-------
breaks : Union[np.ndarray, ty.Tuple[np.ndarray, ...]]
breaks : Union[np.ndarray, Tuple[np.ndarray, ...]]
Breaks data
"""

Expand All @@ -44,7 +44,7 @@ def order(self) -> TProps:

Returns
-------
order : ty.Union[int, ty.Tuple[int, ...]]
order : Union[int, Tuple[int, ...]]
The spline order
"""

Expand All @@ -55,7 +55,7 @@ def pieces(self) -> TProps:

Returns
-------
pieces : ty.Union[int, ty.Tuple[int, ...]]
pieces : Union[int, Tuple[int, ...]]
The spline pieces data
"""

Expand Down Expand Up @@ -98,5 +98,10 @@ def spline(self) -> TSpline:
"""Returns spline representation in PP-form"""

@abc.abstractmethod
def __call__(self, xi: TXi, nu: Optional[TNu] = None, extrapolate: Optional[TExtrapolate] = None) -> np.ndarray:
def __call__(
self,
xi: TXi,
nu: Optional[TNu] = None,
extrapolate: Optional[TExtrapolate] = None,
) -> FloatNDArrayType:
"""Evaluates spline on the data sites"""
7 changes: 5 additions & 2 deletions csaps/_reshape.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,11 @@ def umv_coeffs_to_canonical(arr: np.ndarray, pieces: int):

"""

ndim = arr.shape[0]
order = arr.shape[1] // pieces
ndim: int = arr.shape[0]
order: int = arr.shape[1] // pieces

shape: tuple[int, ...]
strides: tuple[int, ...]

if ndim == 1:
shape = (order, pieces)
Expand Down
64 changes: 31 additions & 33 deletions csaps/_shortcut.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
from typing import NamedTuple, Optional, Sequence, Union, overload
from collections import abc as c_abc

import numpy as np

from ._base import ISmoothingSpline
from ._sspndg import NdGridCubicSmoothingSpline, ndgrid_prepare_data_vectors
from ._sspndg import NdGridCubicSmoothingSpline
from ._sspumv import CubicSmoothingSpline
from ._types import MultivariateDataType, NdGridDataType, UnivariateDataType
from ._types import MultivariateDataType, SequenceUnivariateDataType, UnivariateDataType


class AutoSmoothingResult(NamedTuple):
Expand All @@ -24,6 +26,8 @@ class AutoSmoothingResult(NamedTuple):
# **************************************
# csaps signatures
#


@overload
def csaps(
xdata: UnivariateDataType,
Expand Down Expand Up @@ -66,10 +70,10 @@ def csaps(

@overload
def csaps(
xdata: NdGridDataType,
xdata: SequenceUnivariateDataType,
ydata: MultivariateDataType,
*,
weights: Optional[NdGridDataType] = None,
weights: Optional[SequenceUnivariateDataType] = None,
smooth: Optional[Sequence[float]] = None,
axis: Optional[int] = None,
normalizedsmooth: bool = False,
Expand All @@ -79,11 +83,11 @@ def csaps(

@overload
def csaps(
xdata: NdGridDataType,
xdata: SequenceUnivariateDataType,
ydata: MultivariateDataType,
xidata: NdGridDataType,
xidata: SequenceUnivariateDataType,
*,
weights: Optional[NdGridDataType] = None,
weights: Optional[SequenceUnivariateDataType] = None,
axis: Optional[int] = None,
normalizedsmooth: bool = False,
) -> AutoSmoothingResult: # pragma: no cover
Expand All @@ -92,33 +96,32 @@ def csaps(

@overload
def csaps(
xdata: NdGridDataType,
xdata: SequenceUnivariateDataType,
ydata: MultivariateDataType,
xidata: NdGridDataType,
xidata: SequenceUnivariateDataType,
*,
smooth: Sequence[float],
weights: Optional[NdGridDataType] = None,
weights: Optional[SequenceUnivariateDataType] = None,
axis: Optional[int] = None,
normalizedsmooth: bool = False,
) -> MultivariateDataType: # pragma: no cover
...


#
# csaps signatures
# **************************************
# csaps implementation


def csaps(
xdata: Union[UnivariateDataType, NdGridDataType],
ydata: MultivariateDataType,
xidata: Optional[Union[UnivariateDataType, NdGridDataType]] = None,
xdata,
ydata,
xidata=None,
*,
weights: Optional[Union[UnivariateDataType, NdGridDataType]] = None,
smooth: Optional[Union[float, Sequence[float]]] = None,
axis: Optional[int] = None,
normalizedsmooth: bool = False,
) -> Union[MultivariateDataType, ISmoothingSpline, AutoSmoothingResult]:
weights=None,
smooth=None,
axis=None,
normalizedsmooth=False,
):
"""Smooths the univariate/multivariate/gridded data or computes the corresponding splines

This function might be used as the main API for smoothing any data.
Expand Down Expand Up @@ -220,32 +223,27 @@ def csaps(

"""

umv = True
if isinstance(xdata, c_abc.Sequence):
try:
ndgrid_prepare_data_vectors(xdata, 'xdata')
except ValueError:
umv = True
else:
if len(xdata) and isinstance(xdata[0], (np.ndarray, c_abc.Sequence)):
umv = False
else:
umv = True

if umv:
axis = -1 if axis is None else axis
sp = CubicSmoothingSpline(
xdata,
ydata,
xdata=xdata,
ydata=ydata,
weights=weights,
smooth=smooth,
axis=axis,
normalizedsmooth=normalizedsmooth,
)
else:
sp = NdGridCubicSmoothingSpline(
xdata,
ydata,
weights,
smooth,
xdata=xdata,
ydata=ydata,
weights=weights,
smooth=smooth,
normalizedsmooth=normalizedsmooth,
)

Expand Down
45 changes: 23 additions & 22 deletions csaps/_sspndg.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
ND-Gridded cubic smoothing spline implementation
"""

from typing import Optional, Sequence, Tuple, Union
from typing import Optional, Sequence, Tuple, Union, cast
import collections.abc as c_abc
from numbers import Number

import numpy as np
from scipy.interpolate import NdPPoly, PPoly
Expand All @@ -18,24 +17,28 @@
umv_coeffs_to_flatten,
)
from ._sspumv import CubicSmoothingSpline
from ._types import NdGridDataType, UnivariateDataType
from ._types import SequenceUnivariateDataType, FloatNDArrayType, Float1DArrayTupe


def ndgrid_prepare_data_vectors(data, name, min_size: int = 2) -> Tuple[np.ndarray, ...]:
def ndgrid_prepare_data_vectors(
data: SequenceUnivariateDataType,
name: str,
min_size: int = 2,
) -> Tuple[Float1DArrayTupe, ...]:
if not isinstance(data, c_abc.Sequence):
raise TypeError(f"'{name}' must be a sequence of 1-d array-like (vectors) or scalars.")

data = list(data)
data_: list[Float1DArrayTupe] = []

for axis, d in enumerate(data):
d = np.asarray(d, dtype=np.float64)
if d.ndim > 1:
raise ValueError(f"All '{name}' elements must be a vector for axis {axis}.")
if d.size < min_size:
raise ValueError(f"'{name}' must contain at least {min_size} data points for axis {axis}.")
data[axis] = d
data_.append(d)

return tuple(data)
return tuple(data_)


class NdGridSplinePPForm(ISplinePPForm[Tuple[np.ndarray, ...], Tuple[int, ...]], NdPPoly):
Expand Down Expand Up @@ -76,9 +79,9 @@ def ndim(self) -> int:
def shape(self) -> Tuple[int, ...]:
return tuple(len(xi) for xi in self.x)

def __call__(
def __call__( # type: ignore[override]
self,
x: Sequence[UnivariateDataType],
x: SequenceUnivariateDataType,
nu: Optional[Tuple[int, ...]] = None,
extrapolate: Optional[bool] = None,
) -> np.ndarray:
Expand All @@ -87,8 +90,8 @@ def __call__(
Parameters
----------

x : tuple of 1-d array-like
The tuple of point values for each dimension to evaluate the spline at.
x : Sequence of 1-d array-like
The sequence of point values for each dimension to evaluate the spline at.

nu : [*Optional*] tuple of int
Orders of derivatives to evaluate. Each must be non-negative.
Expand Down Expand Up @@ -158,7 +161,7 @@ class NdGridCubicSmoothingSpline(
ISmoothingSpline[
NdGridSplinePPForm,
Tuple[float, ...],
NdGridDataType,
SequenceUnivariateDataType,
Tuple[int, ...],
bool,
]
Expand Down Expand Up @@ -204,31 +207,29 @@ class NdGridCubicSmoothingSpline(

def __init__(
self,
xdata: NdGridDataType,
xdata: SequenceUnivariateDataType,
ydata: np.ndarray,
weights: Optional[Union[UnivariateDataType, NdGridDataType]] = None,
weights: Optional[SequenceUnivariateDataType] = None,
smooth: Optional[Union[float, Sequence[Optional[float]]]] = None,
normalizedsmooth: bool = False,
) -> None:
x, y, w, s = self._prepare_data(xdata, ydata, weights, smooth)
coeffs, smooth = self._make_spline(x, y, w, s, normalizedsmooth)

self._spline = NdGridSplinePPForm.construct_fast(coeffs, x)
self._smooth = smooth
coeffs, self._smooth = self._make_spline(x, y, w, s, normalizedsmooth)
self._spline = cast(NdGridSplinePPForm, NdGridSplinePPForm.construct_fast(coeffs, x))

def __call__(
self,
x: Union[NdGridDataType, Sequence[Number]],
x: SequenceUnivariateDataType,
nu: Optional[Tuple[int, ...]] = None,
extrapolate: Optional[bool] = None,
) -> np.ndarray:
) -> FloatNDArrayType:
"""Evaluate the spline for given data

Parameters
----------

x : tuple of 1-d array-like
The tuple of point values for each dimension to evaluate the spline at.
x : Sequence of 1-d array-like
The sequence of point values for each dimension to evaluate the spline at.

nu : [*Optional*] tuple of int
Orders of derivatives to evaluate. Each must be non-negative.
Expand Down
Loading