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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## v0.11.0

* Internal re-design `SplinePPForm` and `NdGridSplinePPForm` classes [#17](https://github.com/espdev/csaps/issues/17):
- Remove `shape` and `axis` properties and reshaping data in these classes
- `NdGridSplinePPForm` coefficients array for 1D grid now is 1-d instead of 2-d
* Refactoring the code and decrease memory consumption
* Add `overload` type-hints for `csaps` function signatures

## v0.10.1

* Fix call of `numpy.pad` function for numpy <1.17 [#15](https://github.com/espdev/csaps/issues/15)
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
**csaps** is a Python package for univariate, multivariate and n-dimensional grid data approximation using cubic smoothing splines.
The package can be useful in practical engineering tasks for data approximation and smoothing.

## Installation
## Installing

Use pip for installing:

Expand Down Expand Up @@ -89,20 +89,22 @@ plt.show()

## Documentation

More examples of usage and the full documentation can be found at ReadTheDocs.

https://csaps.readthedocs.io
More examples of usage and the full documentation can be found at https://csaps.readthedocs.io.

## Testing

pytest, tox and Travis CI are used for testing. Please see [tests](tests).

## Algorithms and implementations
## Algorithm and Implementation

**csaps** package is a Python modified port of MATLAB [CSAPS](https://www.mathworks.com/help/curvefit/csaps.html) function that is an implementation of
**csaps** Python package is inspired by MATLAB [CSAPS](https://www.mathworks.com/help/curvefit/csaps.html) function that is an implementation of
Fortran routine SMOOTH from [PGS](http://pages.cs.wisc.edu/~deboor/pgs/) (originally written by Carl de Boor).

[csaps-cpp](https://github.com/espdev/csaps-cpp) C++11 Eigen based implementation of the algorithm.
Also the algothithm implementation in other languages:

* [csaps-rs](https://github.com/espdev/csaps-rs) Rust ndarray/sprs based implementation
* [csaps-cpp](https://github.com/espdev/csaps-cpp) C++11 Eigen based implementation (incomplete)


## References

Expand Down
2 changes: 0 additions & 2 deletions csaps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
)
from csaps._types import (
UnivariateDataType,
UnivariateVectorizedDataType,
MultivariateDataType,
NdGridDataType,
)
Expand All @@ -46,7 +45,6 @@

# Type-hints
'UnivariateDataType',
'UnivariateVectorizedDataType',
'MultivariateDataType',
'NdGridDataType',
]
97 changes: 70 additions & 27 deletions csaps/_shortcut.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,50 +6,93 @@
"""

from collections import abc as c_abc
from typing import Optional, Union, Sequence, NamedTuple

import numpy as np
from typing import Optional, Union, Sequence, NamedTuple, overload

from ._base import ISmoothingSpline
from ._sspumv import CubicSmoothingSpline
from ._sspndg import ndgrid_prepare_data_sites, NdGridCubicSmoothingSpline
from ._types import (
UnivariateDataType,
UnivariateVectorizedDataType,
NdGridDataType,
)

_XDataType = Union[UnivariateDataType, NdGridDataType]
_YDataType = Union[UnivariateVectorizedDataType, np.ndarray]
_XiDataType = Optional[Union[UnivariateDataType, NdGridDataType]]
_WeightsDataType = Optional[Union[UnivariateDataType, NdGridDataType]]
_SmoothDataType = Optional[Union[float, Sequence[Optional[float]]]]
from ._types import UnivariateDataType, MultivariateDataType, NdGridDataType


class AutoSmoothingResult(NamedTuple):
"""The result for auto smoothing for `csaps` function"""

values: _YDataType
values: MultivariateDataType
"""Smoothed data values"""

smooth: _SmoothDataType
smooth: Union[float, Sequence[Optional[float]]]
"""The calculated smoothing parameter"""


_ReturnType = Union[
_YDataType,
AutoSmoothingResult,
ISmoothingSpline,
]
# **************************************
# csaps signatures
#
@overload
def csaps(xdata: UnivariateDataType,
ydata: MultivariateDataType,
*,
weights: Optional[UnivariateDataType] = None,
smooth: Optional[float] = None,
axis: Optional[int] = None) -> ISmoothingSpline: ...


@overload
def csaps(xdata: UnivariateDataType,
ydata: MultivariateDataType,
xidata: UnivariateDataType,
*,
weights: Optional[UnivariateDataType] = None,
axis: Optional[int] = None) -> AutoSmoothingResult: ...


@overload
def csaps(xdata: UnivariateDataType,
ydata: MultivariateDataType,
xidata: UnivariateDataType,
*,
smooth: float,
weights: Optional[UnivariateDataType] = None,
axis: Optional[int] = None) -> MultivariateDataType: ...


@overload
def csaps(xdata: NdGridDataType,
ydata: MultivariateDataType,
*,
weights: Optional[NdGridDataType] = None,
smooth: Optional[Sequence[float]] = None,
axis: Optional[int] = None) -> ISmoothingSpline: ...


@overload
def csaps(xdata: NdGridDataType,
ydata: MultivariateDataType,
xidata: NdGridDataType,
*,
weights: Optional[NdGridDataType] = None,
axis: Optional[int] = None) -> AutoSmoothingResult: ...


@overload
def csaps(xdata: NdGridDataType,
ydata: MultivariateDataType,
xidata: NdGridDataType,
*,
smooth: Sequence[float],
weights: Optional[NdGridDataType] = None,
axis: Optional[int] = None) -> MultivariateDataType: ...
#
# csaps signatures
# **************************************


def csaps(xdata: _XDataType,
ydata: _YDataType,
xidata: _XiDataType = None,
def csaps(xdata: Union[UnivariateDataType, NdGridDataType],
ydata: MultivariateDataType,
xidata: Optional[Union[UnivariateDataType, NdGridDataType]] = None,
*,
weights: _WeightsDataType = None,
smooth: _SmoothDataType = None,
axis: Optional[int] = None) -> _ReturnType:
weights: Optional[Union[UnivariateDataType, NdGridDataType]] = None,
smooth: Optional[Union[float, Sequence[float]]] = None,
axis: Optional[int] = None) -> Union[MultivariateDataType, ISmoothingSpline, AutoSmoothingResult]:
"""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
60 changes: 22 additions & 38 deletions csaps/_sspndg.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,82 +37,64 @@ class NdGridSplinePPForm(SplinePPFormBase[ty.Sequence[np.ndarray], ty.Tuple[int,

Parameters
----------
breaks : np.ndarray
Breaks values 1-D array
breaks : Sequence[np.ndarray]
The sequence of the breaks 1-D arrays for each dimension

coeffs : np.ndarray
Spline coefficients N-D array
Tensor-product spline coefficients N-D array

"""

def __init__(self, breaks: ty.Sequence[np.ndarray], coeffs: np.ndarray) -> None:
self._breaks = breaks
self._coeffs = coeffs
self._pieces = tuple(x.size - 1 for x in breaks)
self._order = tuple(s // p for s, p in zip(coeffs.shape, self._pieces))
self._ndim = len(breaks)

if self._ndim > 1:
self._order = tuple(s // p for s, p in zip(coeffs.shape, self._pieces))
else:
# the corner case for univariate spline that is represented as 1d-grid
self._order = (coeffs.shape[1] // self._pieces[0], )

@property
def breaks(self) -> ty.Sequence[np.ndarray]:
"""Returns the sequence of the data breaks for each dimension"""
return self._breaks

@property
def coeffs(self) -> np.ndarray:
"""Returns n-d array of tensor-product n-d grid spline coefficients"""
return self._coeffs

@property
def order(self) -> ty.Tuple[int, ...]:
"""Returns the tuple of the spline orders for each dimension"""
return self._order

@property
def pieces(self) -> ty.Tuple[int, ...]:
"""Returns the tuple of the spline pieces for each dimension"""
return self._pieces

@property
def ndim(self) -> int:
"""Returns the dimensionality of n-d grid data"""
return self._ndim

@property
def shape(self) -> ty.Tuple[int, ...]:
"""Returns the original data shape
"""
return tuple(x.size for x in self.breaks)

def evaluate(self, xi: ty.Sequence[np.ndarray]) -> np.ndarray:
"""Evaluates the spline for given data point(s) on the n-d grid"""
shape = tuple(x.size for x in xi)

coeffs = self.coeffs
coeffs_shape = list(coeffs.shape)
coeffs_shape = coeffs.shape

d = self.ndim - 1
permuted_axes = (d, *range(d))
ndim_m1 = self.ndim - 1
permuted_axes = (ndim_m1, *range(ndim_m1))

for i in reversed(range(self.ndim)):
xii = xi[i]
ndim = int(np.prod(coeffs_shape[:d]))

if self.ndim > 2:
coeffs = coeffs.reshape((ndim, self.pieces[i] * self.order[i]))

spp = SplinePPForm(
breaks=self.breaks[i],
coeffs=coeffs,
pieces=self.pieces[i],
order=self.order[i],
shape=(ndim, xii.size)
)
umv_ndim = int(np.prod(coeffs_shape[:ndim_m1]))
coeffs = coeffs.reshape((umv_ndim, self.pieces[i] * self.order[i]))

coeffs = spp.evaluate(xii)
coeffs = SplinePPForm(breaks=self.breaks[i], coeffs=coeffs).evaluate(xi[i])

if self.ndim > 2:
coeffs = coeffs.reshape((*coeffs_shape[:d], shape[i]))

if self.ndim > 1:
coeffs = coeffs.transpose(permuted_axes)
coeffs_shape = list(coeffs.shape)
coeffs = coeffs.reshape((*coeffs_shape[:ndim_m1], shape[i])).transpose(permuted_axes)
coeffs_shape = coeffs.shape

return coeffs.reshape(shape)

Expand Down Expand Up @@ -261,4 +243,6 @@ def _make_spline(self, smooth: ty.List[ty.Optional[float]]) -> ty.Tuple[NdGridSp
coeffs = coeffs.transpose(permute_axes)
coeffs_shape = list(coeffs.shape)

coeffs = coeffs.squeeze()

return NdGridSplinePPForm(breaks=self._xdata, coeffs=coeffs), tuple(reversed(smooths))
Loading