Skip to content

Commit

Permalink
Merge pull request #18 from espdev/refactoring-internals
Browse files Browse the repository at this point in the history
Refactoring internals
  • Loading branch information
espdev authored Mar 27, 2020
2 parents 6a301dc + 47c2fc4 commit a05249b
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 198 deletions.
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

0 comments on commit a05249b

Please sign in to comment.