csaps-1.3.3/CHANGELOG.md 0000644 0000000 0000000 00000010230 00000000000 011323 0 ustar 00 # Changelog
## v1.3.3 (07.09.2025)
* Fix spsolve warning (set diag sparse matrix type to CSR to fix the warning)
* Add extrapolation section to tutorial documentation
* Formatting code, type-hints (internal)
## v1.3.2 (15.04.2025)
* Remove `docs` extra dependencies from the package
* Refresh the documentation with Furo theme
## v1.3.1 (14.04.2025)
* Update readme and docs
## 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
* Update the package classifiers
## v1.2.0 (30.06.2024)
* Bump minimal Python version to 3.9
* Use ruff as the code linter and formatter
* Update dependencies
## v1.1.0 (05.10.2021)
* Introduced optional `normalizedsmooth` argument to reduce dependence on xdata and weights [#47](https://github.com/espdev/csaps/pull/47)
* Update numpy and scipy dependency ranges
## v1.0.4 (04.05.2021)
* Bump numpy dependency version
## v1.0.3 (01.01.2021)
* Bump scipy dependency version
* Bump sphinx dependency version and use m2r2 sphinx extension instead of m2r
* Add Python 3.9 to classifiers list and to Travis CI
* Set development status classifier to "5 - Production/Stable"
* Happy New Year!
## v1.0.2 (19.07.2020)
* Fix using 'nu' argument when n-d grid spline evaluating [#32](https://github.com/espdev/csaps/pull/32)
## v1.0.1 (19.07.2020)
* Fix n-d grid spline evaluating performance regression [#31](https://github.com/espdev/csaps/pull/31)
## v1.0.0 (11.07.2020)
* Use `PPoly` and `NdPPoly` base classes from SciPy interpolate module for `SplinePPForm` and `NdGridSplinePPForm` respectively.
* Remove deprecated classes `UnivariateCubicSmoothingSpline` and `MultivariateCubicSmoothingSpline`
* Update the documentation
**Notes**
In this release the spline representation (the array of spline coefficients) has been changed
according to `PPoly`/`NdPPoly`.
See SciPy [PPoly](https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.PPoly.html)
and [NdPPoly](https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.NdPPoly.html) documentation for details.
## v0.11.0 (28.03.2020)
* 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 (19.03.2020)
* Fix call of `numpy.pad` function for numpy <1.17 [#15](https://github.com/espdev/csaps/issues/15)
## v0.10.0 (18.02.2020)
* Significant performance improvements for make/evaluate splines and memory consumption optimization
* Change format for storing spline coefficients (reshape coeffs array) to improve performance
* Add shape property to `SplinePPForm`/`NdGridSplinePPForm` and axis property to `SplinePPForm`
* Fix issues with the smoothing factor in nd-grid case: inverted ordering and unnable to use 0.0 value
* Update documentation
## v0.9.0 (21.01.2020)
* Drop support of Python 3.5
* `weights`, `smooth` and `axis` arguments in `csaps` function are keyword-only now
* `UnivariateCubicSmoothingSpline` and `MultivariateCubicSmoothingSpline` classes are deprecated
and will be removed in 1.0.0 version. Use `CubicSmoothingSpline` instead.
## v0.8.0 (13.01.2020)
* Add `csaps` function that can be used as the main API
* Refactor the internal structure of the package
* Add the [documentation](https://csaps.readthedocs.io)
**Attention**
This is the last version that supports Python 3.5.
The next versions will support Python 3.6 or above.
## v0.7.0 (19.09.2019)
* Add Generic-based type-hints and mypy-compatibility
## v0.6.1 (13.09.2019)
* A slight refactoring and extra data copies removing
## v0.6.0 (12.09.2019)
* Add "axis" parameter for univariate/multivariate cases
## v0.5.0 (10.06.2019)
* Reorganize the project to package-based structure
* Add the interface class for all smoothing spline classes
## v0.4.2 (07.09.2019)
* FIX: "smooth" value is 0.0 was not used
## v0.4.1 (30.05.2019)
* First PyPI release
csaps-1.3.3/CONTRIBUTORS.txt 0000644 0000000 0000000 00000000037 00000000000 012214 0 ustar 00 Eugene Prilepin
Shamus Husheer
csaps-1.3.3/LICENSE 0000644 0000000 0000000 00000002060 00000000000 010521 0 ustar 00 MIT License
Copyright (c) 2017 Eugene Prilepin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
csaps-1.3.3/README.md 0000644 0000000 0000000 00000010456 00000000000 011003 0 ustar 00

**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.
## Installing
Use pip for installing:
```
pip install -U csaps
```
or Poetry:
```
poetry add csaps
```
The module depends only on NumPy and SciPy. Python 3.10 or above is supported.
## Simple Examples
Here is a couple of examples of smoothing data.
An univariate data smoothing:
```python
import numpy as np
import matplotlib.pyplot as plt
from csaps import csaps
np.random.seed(1234)
x = np.linspace(-5., 5., 25)
y = np.exp(-(x/2.5)**2) + (np.random.rand(25) - 0.2) * 0.3
xs = np.linspace(x[0], x[-1], 150)
ys = csaps(x, y, xs, smooth=0.85)
plt.plot(x, y, 'o', xs, ys, '-')
plt.show()
```
A surface data smoothing:
```python
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from csaps import csaps
np.random.seed(1234)
xdata = [np.linspace(-3, 3, 41), np.linspace(-3.5, 3.5, 31)]
i, j = np.meshgrid(*xdata, indexing='ij')
ydata = (3 * (1 - j)**2. * np.exp(-(j**2) - (i + 1)**2)
- 10 * (j / 5 - j**3 - i**5) * np.exp(-j**2 - i**2)
- 1 / 3 * np.exp(-(j + 1)**2 - i**2))
ydata = ydata + (np.random.randn(*ydata.shape) * 0.75)
ydata_s = csaps(xdata, ydata, xdata, smooth=0.988)
fig = plt.figure(figsize=(7, 4.5))
ax = fig.add_subplot(111, projection='3d')
ax.set_facecolor('none')
c = [s['color'] for s in plt.rcParams['axes.prop_cycle']]
ax.plot_wireframe(j, i, ydata, linewidths=0.5, color=c[0], alpha=0.5)
ax.scatter(j, i, ydata, s=10, c=c[0], alpha=0.5)
ax.plot_surface(j, i, ydata_s, color=c[1], linewidth=0, alpha=1.0)
ax.view_init(elev=9., azim=290)
plt.show()
```
## Documentation
More examples of usage and the full documentation can be found at https://csaps.readthedocs.io.
## Development
We use Poetry to manage the project:
```
git clone https://github.com/espdev/csaps.git
cd csaps
poetry install -E docs
```
Also, install pre-commit hooks:
```
poetry run pre-commit install
```
## Testing and Linting
We use pytest for testing and ruff/mypy for linting.
Use `poethepoet` to run tests and linters:
```
poetry run poe test
poetry run poe check
```
## Algorithm and Implementation
**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).
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
C. de Boor, A Practical Guide to Splines, Springer-Verlag, 1978.
## License
[MIT](https://choosealicense.com/licenses/mit/)
csaps-1.3.3/csaps/__init__.py 0000644 0000000 0000000 00000001423 00000000000 012740 0 ustar 00 """
Cubic spline approximation (smoothing)
"""
from csaps._base import ISmoothingSpline, ISplinePPForm
from csaps._shortcut import AutoSmoothingResult, csaps
from csaps._sspndg import NdGridCubicSmoothingSpline, NdGridSplinePPForm
from csaps._sspumv import CubicSmoothingSpline, SplinePPForm
from csaps._types import MultivariateDataType, SequenceUnivariateDataType, UnivariateDataType
from csaps._version import __version__
__all__ = [
# Shortcut
'csaps',
'AutoSmoothingResult',
# Classes
'ISplinePPForm',
'ISmoothingSpline',
'SplinePPForm',
'NdGridSplinePPForm',
'CubicSmoothingSpline',
'NdGridCubicSmoothingSpline',
# Type-hints
'UnivariateDataType',
'MultivariateDataType',
'SequenceUnivariateDataType',
'__version__',
]
csaps-1.3.3/csaps/_base.py 0000644 0000000 0000000 00000004457 00000000000 012264 0 ustar 00 """
The base classes and interfaces
"""
from typing import Generic
import abc
import numpy as np
from ._types import FloatNDArrayType, TData, TExtrapolate, TNu, TProps, TSmooth, TSpline, TXi
class ISplinePPForm(abc.ABC, Generic[TData, TProps]):
"""The interface class for spline representation in PP-form"""
__module__ = 'csaps'
@property
@abc.abstractmethod
def breaks(self) -> TData:
"""Returns the breaks for the spline
Returns
-------
breaks : Union[np.ndarray, Tuple[np.ndarray, ...]]
Breaks data
"""
@property
@abc.abstractmethod
def coeffs(self) -> np.ndarray:
"""Returns the spline coefficients
Returns
-------
coeffs : np.ndarray
Coefficients n-d array
"""
@property
@abc.abstractmethod
def order(self) -> TProps:
"""Returns the spline order
Returns
-------
order : Union[int, Tuple[int, ...]]
The spline order
"""
@property
@abc.abstractmethod
def pieces(self) -> TProps:
"""Returns the spline pieces data
Returns
-------
pieces : Union[int, Tuple[int, ...]]
The spline pieces data
"""
@property
@abc.abstractmethod
def ndim(self) -> int:
"""Returns the spline dimension count
Returns
-------
ndim : int
The spline dimension count
"""
@property
@abc.abstractmethod
def shape(self) -> tuple[int, ...]:
"""Returns the source data shape
Returns
-------
shape : tuple of int
The source data shape
"""
class ISmoothingSpline(abc.ABC, Generic[TSpline, TSmooth, TXi, TNu, TExtrapolate]):
"""The interface class for smooting splines"""
__module__ = 'csaps'
@property
@abc.abstractmethod
def smooth(self) -> TSmooth:
"""Returns smoothing factor(s)"""
@property
@abc.abstractmethod
def spline(self) -> TSpline:
"""Returns spline representation in PP-form"""
@abc.abstractmethod
def __call__(
self,
xi: TXi,
nu: TNu | None = None,
extrapolate: TExtrapolate | None = None,
) -> FloatNDArrayType:
"""Evaluates spline on the data sites"""
csaps-1.3.3/csaps/_reshape.py 0000644 0000000 0000000 00000012410 00000000000 012765 0 ustar 00 import functools
from itertools import chain
import operator
import numpy as np
from numpy.lib.stride_tricks import as_strided
def prod(x):
"""Product of a list/tuple of numbers; ~40x faster vs np.prod for Python tuples"""
if len(x) == 0:
return 1
return functools.reduce(operator.mul, x)
def to_2d(arr: np.ndarray, axis: int) -> np.ndarray:
"""Transforms the shape of N-D array to 2-D NxM array
The function transforms N-D array to 2-D NxM array along given axis,
where N is dimension and M is the nember of elements.
The function does not create a copy.
Parameters
----------
arr : np.array
N-D array
axis : int
Axis that will be used for transform array shape
Returns
-------
arr2d : np.ndarray
2-D NxM array view
Raises
------
ValueError : axis is out of array axes
See Also
--------
from_2d
Examples
--------
.. code-block:: python
>>> shape = (2, 3, 4)
>>> arr = np.arange(1, np.prod(shape)+1).reshape(shape)
>>> arr_2d = to_2d(arr, axis=1)
>>> print(arr)
[[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
[[13 14 15 16]
[17 18 19 20]
[21 22 23 24]]]
>>> print(arr_2d)
[[ 1 5 9]
[ 2 6 10]
[ 3 7 11]
[ 4 8 12]
[13 17 21]
[14 18 22]
[15 19 23]
[16 20 24]]
"""
arr = np.asarray(arr)
axis = arr.ndim + axis if axis < 0 else axis
if axis >= arr.ndim: # pragma: no cover
raise ValueError(f'axis {axis} is out of array axes {arr.ndim}')
tr_axes = list(range(arr.ndim))
tr_axes.pop(axis)
tr_axes.append(axis)
new_shape = (np.prod(arr.shape) // arr.shape[axis], arr.shape[axis])
return arr.transpose(tr_axes).reshape(new_shape)
def umv_coeffs_to_canonical(arr: np.ndarray, pieces: int):
"""
Parameters
----------
arr : array
The 2-d array with shape (n, m) where:
n -- the number of spline dimensions (1 for univariate)
m -- order * pieces
pieces : int
The number of pieces
Returns
-------
arr_view : array view
The 2-d or 3-d array view with shape (k, p) or (k, p, n) where:
k -- spline order
p -- the number of spline pieces
n -- the number of spline dimensions (multivariate case)
"""
ndim: int = arr.shape[0]
order: int = arr.shape[1] // pieces
shape: tuple[int, ...]
strides: tuple[int, ...]
if ndim == 1:
shape = (order, pieces)
strides = (arr.strides[1] * pieces, arr.strides[1])
else:
shape = (order, pieces, ndim)
strides = (arr.strides[1] * pieces, arr.strides[1], arr.strides[0])
return as_strided(arr, shape=shape, strides=strides)
def umv_coeffs_to_flatten(arr: np.ndarray):
"""
Parameters
----------
arr : array
The 2-d or 3-d array with shape (k, m) or (k, m, n) where:
k -- the spline order
m -- the number of spline pieces
n -- the number of spline dimensions (multivariate case)
Returns
-------
arr_view : array view
The array 2-d view with shape (1, k * m) or (n, k * m)
"""
if arr.ndim == 2:
arr_view = arr.ravel()[np.newaxis]
elif arr.ndim == 3:
shape = (arr.shape[2], prod(arr.shape[:2]))
strides = arr.strides[:-3:-1]
arr_view = as_strided(arr, shape=shape, strides=strides)
else: # pragma: no cover
raise ValueError(f'The array ndim must be 2 or 3, but given array has ndim={arr.ndim}.')
return arr_view
def ndg_coeffs_to_canonical(arr: np.ndarray, pieces: tuple[int, ...]) -> np.ndarray:
"""Returns array canonical view for given n-d grid coeffs flatten array
Creates n-d array canonical view with shape (k0, ..., kn, p0, ..., pn) for given
array with shape (m0, ..., mn) and pieces (p0, ..., pn).
Parameters
----------
arr : array
The input array with shape (m0, ..., mn)
pieces : tuple
The number of pieces (p0, ..., pn)
Returns
-------
arr_view : array view
The canonical view for given array with shape (k0, ..., kn, p0, ..., pn)
"""
if arr.ndim > len(pieces):
return arr
shape = tuple(sz // p for sz, p in zip(arr.shape, pieces)) + pieces
strides = tuple(st * p for st, p in zip(arr.strides, pieces)) + arr.strides
return as_strided(arr, shape=shape, strides=strides)
def ndg_coeffs_to_flatten(arr: np.ndarray):
"""Creates flatten array view for n-d grid coeffs canonical array
For example for input array (4, 4, 20, 30) will be created the flatten view (80, 120)
Parameters
----------
arr : array
The input array with shape (k0, ..., kn, p0, ..., pn) where:
``k0, ..., kn`` -- spline orders
``p0, ..., pn`` -- spline pieces
Returns
-------
arr_view : array view
Flatten view of array with shape (m0, ..., mn)
"""
if arr.ndim == 2:
return arr
ndim = arr.ndim // 2
axes = tuple(chain.from_iterable(zip(range(ndim), range(ndim, arr.ndim))))
shape = tuple(prod(arr.shape[i::ndim]) for i in range(ndim))
return arr.transpose(axes).reshape(shape)
csaps-1.3.3/csaps/_shortcut.py 0000644 0000000 0000000 00000016475 00000000000 013230 0 ustar 00 """
The module provided `csaps` shortcut function for smoothing data
"""
from typing import NamedTuple, Sequence, overload
import numpy as np
from ._base import ISmoothingSpline
from ._sspndg import NdGridCubicSmoothingSpline
from ._sspumv import CubicSmoothingSpline
from ._types import MultivariateDataType, SequenceUnivariateDataType, UnivariateDataType
class AutoSmoothingResult(NamedTuple):
"""The result for auto smoothing for `csaps` function"""
values: MultivariateDataType
"""Smoothed data values"""
smooth: float | Sequence[float | None]
"""The calculated smoothing parameter"""
# **************************************
# csaps signatures
#
@overload
def csaps(
xdata: UnivariateDataType,
ydata: MultivariateDataType,
*,
weights: UnivariateDataType | None = None,
smooth: float | None = None,
axis: int | None = None,
normalizedsmooth: bool = False,
) -> ISmoothingSpline: # pragma: no cover
...
@overload
def csaps(
xdata: UnivariateDataType,
ydata: MultivariateDataType,
xidata: UnivariateDataType,
*,
weights: UnivariateDataType | None = None,
axis: int | None = None,
normalizedsmooth: bool = False,
) -> AutoSmoothingResult: # pragma: no cover
...
@overload
def csaps(
xdata: UnivariateDataType,
ydata: MultivariateDataType,
xidata: UnivariateDataType,
*,
smooth: float,
weights: UnivariateDataType | None = None,
axis: int | None = None,
normalizedsmooth: bool = False,
) -> MultivariateDataType: # pragma: no cover
...
@overload
def csaps(
xdata: SequenceUnivariateDataType,
ydata: MultivariateDataType,
*,
weights: SequenceUnivariateDataType | None = None,
smooth: Sequence[float | None] | None = None,
axis: int | None = None,
normalizedsmooth: bool = False,
) -> ISmoothingSpline: # pragma: no cover
...
@overload
def csaps(
xdata: SequenceUnivariateDataType,
ydata: MultivariateDataType,
xidata: SequenceUnivariateDataType,
*,
weights: SequenceUnivariateDataType | None = None,
axis: int | None = None,
normalizedsmooth: bool = False,
) -> AutoSmoothingResult: # pragma: no cover
...
@overload
def csaps(
xdata: SequenceUnivariateDataType,
ydata: MultivariateDataType,
xidata: SequenceUnivariateDataType,
*,
smooth: Sequence[float | None],
weights: SequenceUnivariateDataType | None = None,
axis: int | None = None,
normalizedsmooth: bool = False,
) -> MultivariateDataType: # pragma: no cover
...
# **************************************
# csaps implementation
def csaps(
xdata,
ydata,
xidata=None,
*,
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.
Parameters
----------
xdata : np.ndarray, array-like
The data sites ``x1 < x2 < ... < xN``:
- 1-D data vector/sequence (array-like) for univariate/multivariate ``ydata`` case
- The sequence of 1-D data vectors for nd-gridded ``ydata`` case
ydata : np.ndarray, array-like
The data values:
- 1-D data vector/sequence (array-like) for univariate data case
- N-D array/array-like for multivariate data case
- N-D array for nd-gridded data case
xidata : [*Optional*] np.ndarray, array-like, Sequence[array-like]
The data sites for output smoothed data:
- 1-D data vector/sequence (array-like) for univariate/multivariate ``ydata`` case
- The sequence of 1-D data vectors for nd-gridded ``ydata`` case
If this argument was not set, the function will return computed spline
for given data in :class:`ISmoothingSpline` object.
weights : [*Optional*] np.ndarray, array-like, Sequence[array-like]
The weights data vectors:
- 1-D data vector/sequence (array-like) for univariate/multivariate ``ydata`` case
- The sequence of 1-D data vectors for nd-gridded ``ydata`` case
smooth : [*Optional*] float, Sequence[float]
The smoothing factor value(s):
- float value in the range ``[0, 1]`` for univariate/multivariate ``ydata`` case
- the sequence of float in the range ``[0, 1]`` or None for nd-gridded ``ydata`` case
If this argument was not set or None or sequence with None-items, the function will return
named tuple :class:`AutoSmoothingResult` with computed smoothed data values and smoothing factor value(s).
axis : [*Optional*] int
The ``ydata`` axis. Axis along which ``ydata`` is assumed to be varying.
If this argument was not set the last axis will be used (``axis == -1``).
.. note::
Currently, `axis` will be ignored for nd-gridded ``ydata`` case.
normalizedsmooth : [*Optional*] bool
If True, the smooth parameter is normalized such that results are invariant to xdata range
and less sensitive to nonuniformity of weights and xdata clumping
.. versionadded:: 1.1.0
Returns
-------
yidata : np.ndarray
Smoothed data values if ``xidata`` and ``smooth`` were set.
autosmoothing_result : AutoSmoothingResult
The named tuple object with two fileds:
- 'values' -- smoothed data values
- 'smooth' -- computed smoothing factor
This result will be returned if ``xidata`` was set and ``smooth`` was not set.
ssp_obj : ISmoothingSpline
Smoothing spline object if ``xidata`` was not set:
- :class:`CubicSmoothingSpline` instance for univariate/multivariate data
- :class:`NdGridCubicSmoothingSpline` instance for nd-gridded data
Examples
--------
Univariate data smoothing
.. code-block:: python
import numpy as np
from csaps import csaps
x = np.linspace(-5., 5., 25)
y = np.exp(-(x/2.5)**2) + (np.random.rand(25) - 0.2) * 0.3
xi = np.linspace(-5., 5., 150)
# Smooth data with smoothing factor 0.85
yi = csaps(x, y, xi, smooth=0.85)
# Smooth data and compute smoothing factor automatically
yi, smooth = csaps(x, y, xi)
# Do not evaluate the spline, only compute it
sp = csaps(x, y, smooth=0.98)
See Also
--------
CubicSmoothingSpline
NdGridCubicSmoothingSpline
"""
umv = True
if isinstance(xdata, Sequence):
if len(xdata) and isinstance(xdata[0], (np.ndarray, Sequence)):
umv = False
if umv:
axis = -1 if axis is None else axis
sp = CubicSmoothingSpline(
xdata=xdata,
ydata=ydata,
weights=weights,
smooth=smooth,
axis=axis,
normalizedsmooth=normalizedsmooth,
)
else:
sp = NdGridCubicSmoothingSpline(
xdata=xdata,
ydata=ydata,
weights=weights,
smooth=smooth,
normalizedsmooth=normalizedsmooth,
)
if xidata is None:
return sp
yidata = sp(xidata)
auto_smooth = smooth is None
if isinstance(smooth, Sequence):
auto_smooth = any(sm is None for sm in smooth)
if auto_smooth:
return AutoSmoothingResult(yidata, sp.smooth)
else:
return yidata
csaps-1.3.3/csaps/_sspndg.py 0000644 0000000 0000000 00000025601 00000000000 012642 0 ustar 00 """
ND-Gridded cubic smoothing spline implementation
"""
from typing import Optional, Sequence, cast
import numpy as np
from scipy.interpolate import NdPPoly, PPoly
from ._base import ISmoothingSpline, ISplinePPForm
from ._reshape import (
ndg_coeffs_to_canonical,
ndg_coeffs_to_flatten,
prod,
umv_coeffs_to_canonical,
umv_coeffs_to_flatten,
)
from ._sspumv import CubicSmoothingSpline
from ._types import Float1DArrayTupe, FloatNDArrayType, SequenceUnivariateDataType
def ndgrid_prepare_data_vectors(
data: SequenceUnivariateDataType,
name: str,
min_size: int = 2,
) -> tuple[Float1DArrayTupe, ...]:
if not isinstance(data, Sequence):
raise TypeError(f"'{name}' must be a sequence of 1-d array-like (vectors) or scalars.")
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_.append(d)
return tuple(data_)
class NdGridSplinePPForm(ISplinePPForm[tuple[np.ndarray, ...], tuple[int, ...]], NdPPoly):
"""N-D grid spline representation in PP-form
N-D grid spline is represented in piecewise tensor product polynomial form.
Notes
-----
Inherited from :py:class:`scipy.interpolate.NdPPoly`
"""
__module__ = 'csaps'
@property
def breaks(self) -> tuple[np.ndarray, ...]:
return self.x
@property
def coeffs(self) -> np.ndarray:
return self.c
@property
def order(self) -> tuple[int, ...]:
return self.c.shape[: self.c.ndim // 2]
@property
def pieces(self) -> tuple[int, ...]:
return self.c.shape[self.c.ndim // 2 :]
@property
def ndim(self) -> int:
return len(self.x)
@property
def shape(self) -> tuple[int, ...]:
return tuple(len(xi) for xi in self.x)
def __call__( # type: ignore[override]
self,
x: SequenceUnivariateDataType,
nu: Optional[tuple[int, ...]] = None,
extrapolate: Optional[bool] = None,
) -> np.ndarray:
"""Evaluate the spline for given data
Parameters
----------
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.
extrapolate : [*Optional*] bool
Whether to extrapolate to out-of-bounds points based on first and last
intervals, or to return NaNs.
Returns
-------
y : array-like
Interpolated values. Shape is determined by replacing the
interpolation axis in the original array with the shape of x.
"""
x = ndgrid_prepare_data_vectors(x, 'x', min_size=1)
if len(x) != self.ndim:
raise ValueError(f"'x' sequence must have length {self.ndim} according to 'breaks'")
if nu is None:
nu = (0,) * len(x)
if extrapolate is None:
extrapolate = True
shape = tuple(x.size for x in x)
coeffs = ndg_coeffs_to_flatten(self.coeffs)
coeffs_shape = coeffs.shape
ndim_m1 = self.ndim - 1
permuted_axes = (ndim_m1, *range(ndim_m1))
for i in reversed(range(self.ndim)):
umv_ndim = prod(coeffs_shape[:ndim_m1])
c_shape = (umv_ndim, self.pieces[i] * self.order[i])
if c_shape != coeffs_shape:
coeffs = coeffs.reshape(c_shape)
coeffs_cnl = umv_coeffs_to_canonical(coeffs, self.pieces[i])
spline = PPoly.construct_fast(coeffs_cnl, self.breaks[i], axis=1)
coeffs = spline(x[i], nu=nu[i], extrapolate=extrapolate)
shape_r = (*coeffs_shape[:ndim_m1], shape[i])
coeffs = coeffs.reshape(shape_r).transpose(permuted_axes)
coeffs_shape = coeffs.shape
return coeffs.reshape(shape)
def __repr__(self): # pragma: no cover
return (
f'{type(self).__name__}\n'
f' breaks: {self.breaks}\n'
f' coeffs shape: {self.coeffs.shape}\n'
f' data shape: {self.shape}\n'
f' pieces: {self.pieces}\n'
f' order: {self.order}\n'
f' ndim: {self.ndim}\n'
)
class NdGridCubicSmoothingSpline(
ISmoothingSpline[
NdGridSplinePPForm,
tuple[float, ...],
SequenceUnivariateDataType,
tuple[int, ...],
bool,
]
):
"""N-D grid cubic smoothing spline
Class implements N-D grid data smoothing (piecewise tensor product polynomial).
Parameters
----------
xdata : list, tuple, Sequence[vector-like]
X data site vectors for each dimensions. These vectors determine ND-grid.
For example::
# 2D grid
x = [
np.linspace(0, 5, 21),
np.linspace(0, 6, 25),
]
ydata : np.ndarray
Y data ND-array with shape equal ``xdata`` vector sizes
weights : [*Optional*] list, tuple, Sequence[vector-like]
Weights data vector(s) for all dimensions or each dimension with
size(s) equal to ``xdata`` sizes
smooth : [*Optional*] float, Sequence[float]
The smoothing parameter (or a sequence of parameters for each dimension) in range ``[0, 1]`` where:
- 0: The smoothing spline is the least-squares straight line fit
- 1: The cubic spline interpolant with natural condition
normalizedsmooth : [*Optional*] bool
If True, the smooth parameter is normalized such that results are invariant to xdata range
and less sensitive to nonuniformity of weights and xdata clumping
.. versionadded:: 1.1.0
"""
__module__ = 'csaps'
def __init__(
self,
xdata: SequenceUnivariateDataType,
ydata: np.ndarray,
weights: SequenceUnivariateDataType | None = None,
smooth: float | Sequence[float | None] | None = None,
normalizedsmooth: bool = False,
) -> None:
x, y, w, s = self._prepare_data(xdata, ydata, weights, 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: SequenceUnivariateDataType,
nu: tuple[int, ...] | None = None,
extrapolate: bool | None = None,
) -> FloatNDArrayType:
"""Evaluate the spline for given data
Parameters
----------
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.
extrapolate : [*Optional*] bool
Whether to extrapolate to out-of-bounds points based on first and last
intervals, or to return NaNs.
Returns
-------
y : array-like
Interpolated values. Shape is determined by replacing the
interpolation axis in the original array with the shape of x.
"""
return self._spline(x, nu=nu, extrapolate=extrapolate)
@property
def smooth(self) -> tuple[float, ...]:
"""Returns a tuple of smoothing parameters for each axis
Returns
-------
smooth : tuple[float, ...]
The smoothing parameter in the range ``[0, 1]`` for each axis
"""
return self._smooth
@property
def spline(self) -> NdGridSplinePPForm:
"""Returns the spline description in 'NdGridSplinePPForm' instance
Returns
-------
spline : NdGridSplinePPForm
The spline description in :class:`NdGridSplinePPForm` instance
"""
return self._spline
@classmethod
def _prepare_data(cls, xdata, ydata, weights, smooth):
xdata = ndgrid_prepare_data_vectors(xdata, 'xdata')
ydata = np.asarray(ydata)
data_ndim = len(xdata)
if ydata.ndim != data_ndim:
raise ValueError(f"'ydata' must have dimension {data_ndim} according to 'xdata'")
for axis, (yd, xs) in enumerate(zip(ydata.shape, map(len, xdata))):
if yd != xs:
raise ValueError(f"'ydata' ({yd}) and xdata ({xs}) sizes mismatch for axis {axis}")
if not weights:
weights = [None] * data_ndim
else:
weights = ndgrid_prepare_data_vectors(weights, 'weights')
if len(weights) != data_ndim:
raise ValueError(f"'weights' ({len(weights)}) and 'xdata' ({data_ndim}) dimensions mismatch")
for axis, (w, x) in enumerate(zip(weights, xdata)):
if w is not None:
if w.size != x.size:
raise ValueError(f"'weights' ({w.size}) and 'xdata' ({x.size}) sizes mismatch for axis {axis}")
if smooth is None:
smooth = [None] * data_ndim
if not isinstance(smooth, Sequence):
smooth = [float(smooth)] * data_ndim
else:
smooth = list(smooth)
if len(smooth) != data_ndim:
raise ValueError(f'Number of smoothing parameter values must be equal number of dimensions ({data_ndim})')
return xdata, ydata, weights, smooth
@staticmethod
def _make_spline(xdata, ydata, weights, smooth, normalizedsmooth):
ndim = len(xdata)
if ndim == 1:
s = CubicSmoothingSpline(
xdata[0],
ydata,
weights=weights[0],
smooth=smooth[0],
normalizedsmooth=normalizedsmooth,
)
return s.spline.coeffs, (s.smooth,)
shape = ydata.shape
coeffs = ydata
coeffs_shape = list(shape)
smooths = []
permute_axes = (ndim - 1, *range(ndim - 1))
# computing coordinatewise smoothing spline
for i in reversed(range(ndim)):
if ndim > 2:
coeffs = coeffs.reshape(prod(coeffs.shape[:-1]), coeffs.shape[-1])
s = CubicSmoothingSpline(
xdata[i],
coeffs,
weights=weights[i],
smooth=smooth[i],
normalizedsmooth=normalizedsmooth,
)
smooths.append(s.smooth)
coeffs = umv_coeffs_to_flatten(s.spline.coeffs)
if ndim > 2:
coeffs_shape[-1] = s.spline.pieces * s.spline.order
coeffs = coeffs.reshape(coeffs_shape)
coeffs = coeffs.transpose(permute_axes)
coeffs_shape = list(coeffs.shape)
pieces = tuple(int(size - 1) for size in shape)
coeffs = ndg_coeffs_to_canonical(coeffs.squeeze(), pieces)
return coeffs, tuple(reversed(smooths))
csaps-1.3.3/csaps/_sspumv.py 0000644 0000000 0000000 00000024000 00000000000 012671 0 ustar 00 """
Univariate/multivariate cubic smoothing spline implementation
"""
from typing import Literal, cast
from functools import partial
import numpy as np
from scipy.interpolate import PPoly
import scipy.sparse as sp
import scipy.sparse.linalg as la
from ._base import ISmoothingSpline, ISplinePPForm
from ._reshape import prod, to_2d
from ._types import FloatNDArrayType, MultivariateDataType, UnivariateDataType
diags_csr = partial(sp.diags, format='csr')
vpad = partial(np.pad, pad_width=[(1, 1), (0, 0)], mode='constant')
class SplinePPForm(ISplinePPForm[np.ndarray, int], PPoly):
"""The base class for univariate/multivariate spline in piecewise polynomial form
Piecewise polynomial in terms of coefficients and breakpoints.
Notes
-----
Inherited from :py:class:`scipy.interpolate.PPoly`
"""
__module__ = 'csaps'
@property
def breaks(self) -> np.ndarray:
return self.x
@property
def coeffs(self) -> np.ndarray:
return self.c
@property
def order(self) -> int:
return self.c.shape[0]
@property
def pieces(self) -> int:
return self.c.shape[1]
@property
def ndim(self) -> int:
"""Returns the number of spline dimensions
The number of dimensions is product of shape without ``shape[self.axis]``.
"""
shape = list(self.shape)
shape.pop(self.axis)
return prod(shape)
@property
def shape(self) -> tuple[int, ...]:
"""Returns the source data shape"""
shape: list[int] = list(self.c.shape[2:])
shape.insert(self.axis, self.c.shape[1] + 1)
return tuple(shape)
def __repr__(self) -> str: # pragma: no cover
return (
f'{type(self).__name__}\n'
f' breaks: {self.breaks}\n'
f' coeffs shape: {self.coeffs.shape}\n'
f' data shape: {self.shape}\n'
f' axis: {self.axis}\n'
f' pieces: {self.pieces}\n'
f' order: {self.order}\n'
f' ndim: {self.ndim}\n'
)
class CubicSmoothingSpline(
ISmoothingSpline[
SplinePPForm,
float,
UnivariateDataType,
int,
bool | Literal['periodic'],
]
):
"""Cubic smoothing spline
The cubic spline implementation for univariate/multivariate data.
Parameters
----------
xdata : np.ndarray, sequence, vector-like
X input 1-D data vector (data sites: ``x1 < x2 < ... < xN``)
ydata : np.ndarray, vector-like, sequence[vector-like]
Y input 1-D data vector or ND-array with shape[axis] equal of `xdata` size)
weights : [*Optional*] np.ndarray, list
Weights 1-D vector with size equal of ``xdata`` size
smooth : [*Optional*] float
Smoothing parameter in range [0, 1] where:
- 0: The smoothing spline is the least-squares straight line fit
- 1: The cubic spline interpolant with natural condition
axis : [*Optional*] int
Axis along which ``ydata`` is assumed to be varying.
Meaning that for x[i] the corresponding values are np.take(ydata, i, axis=axis).
By default, it is -1 (the last axis).
normalizedsmooth : [*Optional*] bool
If True, the smooth parameter is normalized such that results are invariant to xdata range
and less sensitive to nonuniformity of weights and xdata clumping
.. versionadded:: 1.1.0
"""
__module__ = 'csaps'
def __init__(
self,
xdata: UnivariateDataType,
ydata: MultivariateDataType,
weights: UnivariateDataType | None = None,
smooth: float | None = None,
axis: int = -1,
normalizedsmooth: bool = False,
) -> None:
x, y, w, shape, axis = self._prepare_data(xdata, ydata, weights, axis)
coeffs, self._smooth = self._make_spline(x, y, w, smooth, shape, normalizedsmooth)
self._spline = cast(SplinePPForm, SplinePPForm.construct_fast(coeffs, x, axis=axis))
def __call__(
self,
x: UnivariateDataType,
nu: int | None = None,
extrapolate: bool | Literal['periodic'] | None = None,
) -> FloatNDArrayType:
"""Evaluate the spline for given data
Parameters
----------
x : 1-d array-like
Points to evaluate the spline at.
nu : [*Optional*] int
Order of derivative to evaluate. Must be non-negative.
extrapolate : [*Optional*] bool or 'periodic'
If bool, determines whether to extrapolate to out-of-bounds points
based on first and last intervals, or to return NaNs. If 'periodic',
periodic extrapolation is used. Default is True.
Notes
-----
Derivatives are evaluated piecewise for each polynomial
segment, even if the polynomial is not differentiable at the
breakpoints. The polynomial intervals are considered half-open,
``[a, b)``, except for the last interval which is closed
``[a, b]``.
"""
if nu is None:
nu = 0
return self._spline(x, nu=nu, extrapolate=extrapolate)
@property
def smooth(self) -> float:
"""Returns the smoothing factor
Returns
-------
smooth : float
Smoothing factor in the range [0, 1]
"""
return self._smooth
@property
def spline(self) -> SplinePPForm:
"""Returns the spline description in `SplinePPForm` instance
Returns
-------
spline : SplinePPForm
The spline representation in :class:`SplinePPForm` instance
"""
return self._spline
@staticmethod
def _prepare_data(xdata, ydata, weights, axis):
xdata = np.asarray(xdata, dtype=np.float64)
ydata = np.asarray(ydata, dtype=np.float64)
if xdata.ndim > 1:
raise ValueError("'xdata' must be a vector")
if xdata.size < 2:
raise ValueError("'xdata' must contain at least 2 data points.")
axis = ydata.ndim + axis if axis < 0 else axis
if ydata.shape[axis] != xdata.size:
raise ValueError(
f"'ydata' data must be a 1-D or N-D array with shape[{axis}] "
f"that is equal to 'xdata' size ({xdata.size})"
)
# Rolling axis for using its shape while constructing coeffs array
shape = np.rollaxis(ydata, axis).shape
# Reshape ydata N-D array to 2-D NxM array where N is the data
# dimension and M is the number of data points.
ydata = to_2d(ydata, axis)
if weights is None:
weights = np.ones_like(xdata)
else:
weights = np.asarray(weights, dtype=np.float64)
if weights.size != xdata.size:
raise ValueError('Weights vector size must be equal of xdata size')
return xdata, ydata, weights, shape, axis
@staticmethod
def _compute_smooth(a, b):
"""
The calculation of the smoothing spline requires the solution of a
linear system whose coefficient matrix has the form p*A + (1-p)*B, with
the matrices A and B depending on the data sites x. The default value
of p makes p*trace(A) equal (1 - p)*trace(B).
"""
def trace(m: sp.dia_matrix):
return m.diagonal().sum()
return 1.0 / (1.0 + trace(a) / (6.0 * trace(b)))
@staticmethod
def _normalize_smooth(x: np.ndarray, w: np.ndarray, smooth: float | None) -> float:
"""
See the explanation here: https://github.com/espdev/csaps/pull/47
"""
span = np.ptp(x)
eff_x = 1 + (span**2) / np.sum(np.diff(x) ** 2)
eff_w = np.sum(w) ** 2 / np.sum(w**2)
k = 80 * (span**3) * (x.size**-2) * (eff_x**-0.5) * (eff_w**-0.5)
s = 0.5 if smooth is None else smooth
p = s / (s + (1 - s) * k)
return p
@staticmethod
def _make_spline(x, y, w, smooth, shape, normalizedsmooth):
pcount = x.size
dx = np.diff(x)
if not all(dx > 0): # pragma: no cover
raise ValueError("Items of 'xdata' vector must satisfy the condition: x1 < x2 < ... < xN")
dy = np.diff(y, axis=1)
dy_dx = dy / dx
if pcount == 2:
# The corner case for the data with 2 points (1 breaks interval)
# In this case we have 2-ordered spline and linear interpolation in fact
yi = y[:, 0][:, np.newaxis]
c_shape = (2, pcount - 1) + shape[1:]
c = np.vstack((dy_dx, yi)).reshape(c_shape)
p = 1.0
return c, p
# Create diagonal sparse matrices
diags_r = np.vstack((dx[1:], 2 * (dx[1:] + dx[:-1]), dx[:-1]))
r = sp.spdiags(diags_r, [-1, 0, 1], pcount - 2, pcount - 2, format='csr')
dx_recip = 1.0 / dx
diags_qtw = np.vstack((dx_recip[:-1], -(dx_recip[1:] + dx_recip[:-1]), dx_recip[1:]))
diags_sqrw_recip = 1.0 / np.sqrt(w)
qtw = diags_csr(diags_qtw, [0, 1, 2], (pcount - 2, pcount)) @ diags_csr(diags_sqrw_recip, 0, (pcount, pcount))
qtw = qtw @ qtw.T
p = smooth
if normalizedsmooth:
p = CubicSmoothingSpline._normalize_smooth(x, w, smooth)
elif smooth is None:
p = CubicSmoothingSpline._compute_smooth(r, qtw)
pp = 6.0 * (1.0 - p)
# Solve linear system for the 2nd derivatives
a = pp * qtw + p * r
b = np.diff(dy_dx, axis=1).T
u = la.spsolve(a, b)
if u.ndim < 2:
u = u[np.newaxis]
if y.shape[0] == 1:
u = u.T
dx = dx[:, np.newaxis]
d1 = np.diff(vpad(u), axis=0) / dx
d2 = np.diff(vpad(d1), axis=0)
diags_w_recip = 1.0 / w
w = diags_csr(diags_w_recip, 0, (pcount, pcount))
yi = y.T - (pp * w) @ d2
pu = vpad(p * u)
c1 = np.diff(pu, axis=0) / dx
c2 = 3.0 * pu[:-1, :]
c3 = np.diff(yi, axis=0) / dx - dx * (2.0 * pu[:-1, :] + pu[1:, :])
c4 = yi[:-1, :]
c_shape = (4, pcount - 1) + shape[1:]
c = np.vstack((c1, c2, c3, c4)).reshape(c_shape)
return c, p
csaps-1.3.3/csaps/_types.py 0000644 0000000 0000000 00000001445 00000000000 012510 0 ustar 00 """
Type-hints and type vars
"""
from typing import Annotated, Literal, Sequence, TypeVar, Union
from typing_extensions import TypeAlias
import numpy as np
from numpy.typing import NDArray
FloatDType: TypeAlias = Union[np.float32, np.float64]
FloatNDArrayType: TypeAlias = NDArray[FloatDType]
Float1DArrayTupe: TypeAlias = Annotated[FloatNDArrayType, Literal['N']]
UnivariateDataType: TypeAlias = Union[Float1DArrayTupe, Sequence[float]]
MultivariateDataType: TypeAlias = Union[FloatNDArrayType, Sequence[Union[float, UnivariateDataType]]]
SequenceUnivariateDataType: TypeAlias = Sequence[UnivariateDataType]
TData = TypeVar('TData')
TProps = TypeVar('TProps')
TSmooth = TypeVar('TSmooth')
TXi = TypeVar('TXi')
TNu = TypeVar('TNu')
TExtrapolate = TypeVar('TExtrapolate')
TSpline = TypeVar('TSpline')
csaps-1.3.3/csaps/_version.py 0000644 0000000 0000000 00000000266 00000000000 013031 0 ustar 00 from importlib.metadata import PackageNotFoundError, version
try:
__version__ = version('csaps')
except PackageNotFoundError: # pragma: no cover
__version__ = '0.0.0.dev0'
csaps-1.3.3/csaps/py.typed 0000644 0000000 0000000 00000000000 00000000000 012314 0 ustar 00 csaps-1.3.3/pyproject.toml 0000644 0000000 0000000 00000006301 00000000000 012432 0 ustar 00 [tool.poetry]
name = "csaps"
version = "1.3.3"
description = "Cubic spline approximation (smoothing)"
authors = ["Evgeny Prilepin "]
license = "MIT"
readme = "README.md"
homepage = "https://github.com/espdev/csaps"
repository = "https://github.com/espdev/csaps"
documentation = "https://csaps.readthedocs.io"
keywords = ["cubic", "spline", "approximation", "smoothing", "interpolation", "csaps"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"Topic :: Scientific/Engineering",
"Topic :: Scientific/Engineering :: Mathematics",
"Topic :: Software Development :: Libraries",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
]
include = [
"LICENSE",
"CHANGELOG.md",
"CONTRIBUTORS.txt",
]
packages = [
{include = "csaps"}
]
[tool.poetry.dependencies]
python = ">=3.10"
typing-extensions = "*"
numpy = [
{version = "*", python = "<3.12"},
{version = ">=1.26.2", python = ">=3.12"},
]
scipy = [
{version = "*", python = "<3.12"},
{version = ">=1.11.4", python = ">=3.12"},
]
[tool.poetry.group.dev.dependencies]
setuptools = "^78.1.0"
pytest = "^8.3.5"
pytest-cov = "^6.1.1"
ruff = "^0.11.5"
mypy = "^1.15.0"
scipy-stubs = "*"
pre-commit = "^4.2.0"
poethepoet = "^0.33.1"
[tool.poetry.group.docs.dependencies]
sphinx = "^7.1.2"
docutils = "^0.20.0"
furo = "^2024.8.6"
numpydoc = "^1.6.0"
m2r2 = "^0.3.2"
matplotlib = "^3.7.4"
[build-system]
requires = ["poetry-core", "setuptools"]
build-backend = "poetry.core.masonry.api"
[tool.ruff]
target-version = "py310"
line-length = 120
exclude = [
".ruff_cache",
".venv",
]
[tool.ruff.lint]
select = [
"E", # All pycodestyle errors
"W", # All pycodestyle warnings
"F", # All Pyflakes errors
"A", # All flake8-builtins
"Q", # Quotes
"I", # Sort imports
"T201", # print found
"T203", # pprint found
]
ignore = []
[tool.ruff.lint.flake8-quotes]
docstring-quotes = "double"
inline-quotes = "single"
[tool.ruff.lint.isort]
force-to-top = ["typing", "typing_extensions", "pytest"]
force-sort-within-sections = true
[tool.ruff.format]
quote-style = "single"
[tool.mypy]
python_version = "3.10"
[tool.poe.tasks]
test = "pytest -v "
test-cov = "pytest --cov=csaps"
test-ci = "pytest -v --color=yes --cov=csaps --cov-report=term --cov-report=lcov:coverage.info"
check-format-pre-commit = "ruff format --check"
check-lint-pre-commit = "ruff check"
check-types-pre-commit = "mypy"
check-format = "ruff format . --check"
check-lint = "ruff check ."
check-types = "mypy -p csaps"
check = ["check-format", "check-lint", "check-types"]
fix-format = "ruff format ."
fix-lint = "ruff check --fix ."
fix = ["fix-format", "fix-lint"]
docs = "sphinx-build docs/ docs/_build/ --builder html"
csaps-1.3.3/PKG-INFO 0000644 0000000 0000000 00000013406 00000000000 010617 0 ustar 00 Metadata-Version: 2.3
Name: csaps
Version: 1.3.3
Summary: Cubic spline approximation (smoothing)
License: MIT
Keywords: cubic,spline,approximation,smoothing,interpolation,csaps
Author: Evgeny Prilepin
Author-email: esp.home@gmail.com
Requires-Python: >=3.10
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Topic :: Software Development :: Libraries
Requires-Dist: numpy (>=1.26.2) ; python_version >= "3.12"
Requires-Dist: numpy ; python_version < "3.12"
Requires-Dist: scipy (>=1.11.4) ; python_version >= "3.12"
Requires-Dist: scipy ; python_version < "3.12"
Requires-Dist: typing-extensions
Project-URL: Documentation, https://csaps.readthedocs.io
Project-URL: Homepage, https://github.com/espdev/csaps
Project-URL: Repository, https://github.com/espdev/csaps
Description-Content-Type: text/markdown

**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.
## Installing
Use pip for installing:
```
pip install -U csaps
```
or Poetry:
```
poetry add csaps
```
The module depends only on NumPy and SciPy. Python 3.10 or above is supported.
## Simple Examples
Here is a couple of examples of smoothing data.
An univariate data smoothing:
```python
import numpy as np
import matplotlib.pyplot as plt
from csaps import csaps
np.random.seed(1234)
x = np.linspace(-5., 5., 25)
y = np.exp(-(x/2.5)**2) + (np.random.rand(25) - 0.2) * 0.3
xs = np.linspace(x[0], x[-1], 150)
ys = csaps(x, y, xs, smooth=0.85)
plt.plot(x, y, 'o', xs, ys, '-')
plt.show()
```
A surface data smoothing:
```python
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from csaps import csaps
np.random.seed(1234)
xdata = [np.linspace(-3, 3, 41), np.linspace(-3.5, 3.5, 31)]
i, j = np.meshgrid(*xdata, indexing='ij')
ydata = (3 * (1 - j)**2. * np.exp(-(j**2) - (i + 1)**2)
- 10 * (j / 5 - j**3 - i**5) * np.exp(-j**2 - i**2)
- 1 / 3 * np.exp(-(j + 1)**2 - i**2))
ydata = ydata + (np.random.randn(*ydata.shape) * 0.75)
ydata_s = csaps(xdata, ydata, xdata, smooth=0.988)
fig = plt.figure(figsize=(7, 4.5))
ax = fig.add_subplot(111, projection='3d')
ax.set_facecolor('none')
c = [s['color'] for s in plt.rcParams['axes.prop_cycle']]
ax.plot_wireframe(j, i, ydata, linewidths=0.5, color=c[0], alpha=0.5)
ax.scatter(j, i, ydata, s=10, c=c[0], alpha=0.5)
ax.plot_surface(j, i, ydata_s, color=c[1], linewidth=0, alpha=1.0)
ax.view_init(elev=9., azim=290)
plt.show()
```
## Documentation
More examples of usage and the full documentation can be found at https://csaps.readthedocs.io.
## Development
We use Poetry to manage the project:
```
git clone https://github.com/espdev/csaps.git
cd csaps
poetry install -E docs
```
Also, install pre-commit hooks:
```
poetry run pre-commit install
```
## Testing and Linting
We use pytest for testing and ruff/mypy for linting.
Use `poethepoet` to run tests and linters:
```
poetry run poe test
poetry run poe check
```
## Algorithm and Implementation
**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).
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
C. de Boor, A Practical Guide to Splines, Springer-Verlag, 1978.
## License
[MIT](https://choosealicense.com/licenses/mit/)