-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[10] Add coverage to CI pipeline (#12)
* BLD: - first test of a GitHub CI for testing * FIX: - fixed wrong pip install and python versions in CI ? * FIX: - fixed loading of test files that do not contain test but dependencies that cannot be loaded ? * BUG: - escaped imports of unaccessible dependencies for test-file generation in GitHub CI tests by making the `if __name__ == "__main__"` include these imports and function definitions * TST: - test whether Cython build can be removed from GitHub actions for testing ? * TST: - reverted removal of Cython for testing * PKG: - moved the full package back into an `src`-folder BLD: - updated `pyproject.toml` and `setup.py` to account for the movement to the `src`-folder - removed dedicated Cython build from CI test pipeline * BLD: - changed CI pipeline target branches * BLD: - augmented CI checks with format, type, and lint checks * BUG: - fixed CI pipeline wrong folder include ? BLD: - added isort check * BUG: - fixed broken `isort` usage in CI? * FIX: - fixed missing `isort` dependency in CI? - fixed wrong import sort order in Cython Hermite functions * FIX: - fixed missing `colorama` dependency for `isort` in CI ? * FIX: - type-ignored Cython import that was not properly resolved by `pyright` ? * FIX: - fixed missing import of Cython module for `pyright` in CI ? * FIX: - again trying to resolve the wrong import error of Cython module by `pyright` in CI ? * FIX: - fixed wrong `pyright` Cython import error of Cython import in CI * BLD: - removed pushes to `develop` from the GitHub CI actions * BLD: - added missing comma to the name of the GitHub CI action * [10 develop] Add coverage to CI pipeline (#11) * DOC: - added Python versions and `black` code style to `README` * DOC: - added `isort` badge to `README` * TST: - added `--no-jit`-flag to `pytest` to enable proper coverage of Numba functions * tmp: - first test of CI with coverage report ? * BUG: - fixed failure of `pytest-xdist` and `pytest-cov` in GitHub CI (works locally) ? * BUG: - fixed accidentally placed `\` for `./tests` in coverage CI action ? * BUG: - added codecov to CI pipeline ? * TST: - tried to readd `pytest-xdist` for coverage reports ? * wip: - reset example Jupyter notebook number 3 * MAINT: - made `_get_num_workers` a function of the `_utils`-model TST: - increased coverage to 100% by testing `np.float32` x-values for the Hermite functions was well as super negative numbers of requested workers * DOC: - added setup, installation, and development instructions to `README` * DOC: - switched back from `README.rst` to `README.md` BLD: - made CI pipeline push actions apply to the main branch only
- Loading branch information
Showing
12 changed files
with
471 additions
and
190 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
# `robust_hermite_ft` | ||
|
||
[![python-3.9](https://img.shields.io/badge/python-3.9-blue.svg)](https://www.python.org/downloads/release/python-390/) | ||
[![python-3.10](https://img.shields.io/badge/python-3.10-blue.svg)](https://www.python.org/downloads/release/python-3100/) | ||
[![python-3.11](https://img.shields.io/badge/python-3.11-blue.svg)](https://www.python.org/downloads/release/python-3110/) | ||
[![python-3.12](https://img.shields.io/badge/python-3.12-blue.svg)](https://www.python.org/downloads/release/python-3120/) | ||
[![code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) | ||
[![code style: isort](https://img.shields.io/badge/code%20style-isort-000000.svg)](https://pycqa.github.io/isort/) | ||
[![codecov](https://codecov.io/gh/MothNik/robust_hermite_ft/branch/10-improve-and-add-coverage-to-CI/graph/badge.svg)](https://codecov.io/gh/MothNik/robust_hermite_ft/branch/10-improve-and-add-coverage-to-CI) | ||
<br><br> | ||
|
||
You want to compute the Fourier transform of a signal, but your signal can be corrupted by outliers? If so, this package is for you even though you will have to say goodbye to the _"fast"_ in _Fast Fourier Transform_ 🏃🙅♀️ | ||
|
||
🏗️🚧 👷👷♂️👷♀️🏗️🚧 | ||
|
||
Currently under construction. Please check back later. | ||
|
||
## ⚙️ Setup and 🪛 Development | ||
|
||
### 🎁 Installation | ||
|
||
Currently, the package is not yet available on PyPI. To install it, you can clone the repository | ||
|
||
```bash | ||
git clone https://github.com/MothNik/robust_hermite_ft.git | ||
``` | ||
|
||
and from within the repositories root directory, install it with | ||
|
||
```bash | ||
pip install -e . | ||
``` | ||
|
||
for normal use or | ||
|
||
```bash | ||
pip install -e .["dev"] | ||
``` | ||
|
||
for development which will also install the development dependencies. | ||
|
||
⚠️ **Warning**: This will require a C-compiler to be installed on your system to | ||
compile the Cython code. | ||
|
||
### 🔎 Code quality | ||
|
||
The following checks for `black`, `isort`, `pyright`, `ruff`, and | ||
`cython-lint` - that are also part of the CI pipeline - can be run with | ||
|
||
```bash | ||
black --check --diff --color ./examples ./src ./tests | ||
isort --check --diff --color ./examples ./src ./tests | ||
pyright | ||
ruff check ./examples ./src ./tests | ||
cython-lint src/robust_hermite_ft/hermite_functions/_c_hermite.pyx | ||
``` | ||
|
||
### ✅❌ Tests | ||
|
||
To run the tests - almost like in the CI pipeline - you can use | ||
|
||
```bash | ||
pytest --cov=robust_hermite_ft ./tests -n="auto" --cov-report=xml -x --no-jit | ||
``` | ||
|
||
for parallelized testing whose coverage report will be stored in the folder | ||
`./htmlcov`. | ||
|
||
## 〰️ Hermite functions | ||
|
||
Being the eigenfunctions of the Fourier transform, Hermite functions are excellent | ||
candidates for the basis functions for a Least Squares Regression approach to the Fourier | ||
transform. However, their evaluation can be a bit tricky. | ||
|
||
The module `hermite_functions` offers a numerically stable way to evaluate Hermite | ||
functions or arbitrary order $n$ and argument - that can be scaled with a factor | ||
$\alpha$: | ||
|
||
<p align="center"> | ||
<img src="docs/hermite_functions/DilatedHermiteFunctions_DifferentScales.png" width="1000px" /> | ||
</p> | ||
|
||
The Hermite functions are defined as | ||
|
||
<p align="left"> | ||
<img src="docs/hermite_functions/equations/DilatedHermiteFunctions.png" width="500px" /> | ||
</p> | ||
|
||
with the Hermite polynomials | ||
|
||
<p align="left"> | ||
<img src="docs/hermite_functions/equations/DilatedHermitePolynomials.png" width="681px" /> | ||
</p> | ||
|
||
By making use of logarithm tricks, the evaluation that might involve infinitely high | ||
polynomial values and at the same time infinitely small Gaussians - that are on top of | ||
that scaled by an infinitely high factorial - can be computed safely and yield accurate | ||
results. | ||
|
||
For doing so, the relation between the dilated and the non-dilated Hermite functions | ||
|
||
<p align="left"> | ||
<img src="docs/hermite_functions/equations/HermiteFunctions_UndilatedToDilated.png" width="321px" /> | ||
</p> | ||
|
||
and the recurrence relation for the Hermite functions | ||
|
||
<p align="left"> | ||
<img src="docs/hermite_functions/equations/HermiteFunctions_RecurrenceRelation.png" width="699px" /> | ||
</p> | ||
|
||
are used, but not directly. Instead, the latest evaluated Hermite function is kept at a | ||
value of either -1, 0, or +1 during the recursion and the logarithm of a correction | ||
factor is tracked and applied when the respective Hermite function is finally evaluated | ||
and stored. This approach is based on [[1]](#references). | ||
|
||
The implementation is tested against a symbolic evaluation with `sympy` that uses 200 | ||
digits of precision and it can be shown that even orders as high as 2,000 can still be | ||
computed even though neither the polynomial, the Gaussian nor the factorial can be | ||
evaluated for this anymore. The factorial for example would already have overflown for | ||
orders of 170 in `float64`-precision. | ||
|
||
<p align="center"> | ||
<img src="docs/hermite_functions/DilatedHermiteFunctions_Stability.png" width="1000px" /> | ||
</p> | ||
|
||
As a sanity check, their orthogonality is part of the tests together with a test for | ||
the fact that the absolute values of the Hermite functions for real input cannot exceed | ||
the value $\frac{1}{\pi^{-\frac{1}{4}}\cdot\sqrt{\alpha}}$. | ||
|
||
## References | ||
|
||
- [1] Bunck B. F., A fast algorithm for evaluation of normalized Hermite | ||
functions, BIT Numer Math (2009), 49, pp. 281–295, DOI: [https://doi.org/10.1007/s10543-009-0216-1](https://doi.org/10.1007/s10543-009-0216-1) |
This file was deleted.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
""" | ||
Module :mod:`_utils` | ||
This module provides utility functionalities that are used throughout the package, e.g., | ||
- handling of Numba-related tasks | ||
""" | ||
|
||
# === Imports === | ||
|
||
from .numba_helpers import ( # noqa: F401 | ||
NUMBA_NO_JIT_ARGV, | ||
NUMBA_NO_JIT_ENV_KEY, | ||
NumbaJitActions, | ||
do_numba_normal_jit_action, | ||
no_jit, | ||
) | ||
from .parallel_helpers import _get_num_workers # noqa: F401 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
""" | ||
Module :mod:`_utils.numba_helpers` | ||
This module implements auxiliary functionalities to handle Numba-related tasks, such as | ||
- checking whether Numba ``jit``-compilation has been explicitly specified to take no | ||
effect, e.g., for test coverage | ||
""" | ||
|
||
# === Imports === | ||
|
||
import os | ||
from enum import Enum | ||
from typing import Callable | ||
|
||
# === Models === | ||
|
||
# an Enum that specifies the possible actions that can be taken regarding Numba | ||
# ``jit``-compilation | ||
|
||
|
||
class NumbaJitActions(Enum): | ||
""" | ||
Specifies the possible actions that can be taken regarding Numba | ||
``jit``-compilation. | ||
""" | ||
|
||
NORMAL = "0" | ||
DEACTIVATE = "1" | ||
|
||
|
||
# === Constants === | ||
|
||
# the runtime argument that is used to specify that Numba ``jit``-compilation should | ||
# take no effect | ||
NUMBA_NO_JIT_ARGV = "--no-jit" | ||
|
||
# the environment variable that is used to specify that Numba ``jit``-compilation should | ||
# take no effect | ||
NUMBA_NO_JIT_ENV_KEY = "CUSTOM_NUMBA_NO_JIT" | ||
|
||
|
||
# whether the environment variable is set to specify that Numba ``jit``-compilation | ||
# should take effect or not in the current runtime environment | ||
do_numba_normal_jit_action = ( | ||
os.environ.get(NUMBA_NO_JIT_ENV_KEY, NumbaJitActions.NORMAL.value) | ||
== NumbaJitActions.NORMAL.value | ||
) | ||
|
||
|
||
# === Functions === | ||
|
||
|
||
def no_jit(*args, **kwargs) -> Callable: | ||
""" | ||
Fake decorator that can be used to make sure that Numba ``jit``-compilation has no | ||
effect. | ||
Parameters | ||
---------- | ||
func : :class:`Callable` | ||
The function that is decorated. | ||
args : :class:`tuple` | ||
The fake positional arguments. | ||
kwargs : :class:`dict` | ||
The fake keyword arguments. | ||
Returns | ||
------- | ||
decorated_func : :class:`Callable` | ||
The decorated function. | ||
""" | ||
|
||
def decorator(func: Callable) -> Callable: | ||
return func | ||
|
||
return decorator |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
""" | ||
Module :mod:`_utils.parallel_helpers` | ||
This module provides functionalities to handle parallel computations, e.g., | ||
- obtaining the number of threads available for the process | ||
""" | ||
|
||
# === Imports === | ||
|
||
import psutil | ||
|
||
# === Functions === | ||
|
||
|
||
def _get_num_workers(workers: int) -> int: | ||
""" | ||
Gets the number of available workers for the process calling this function. | ||
Parameters | ||
---------- | ||
workers : :class:`int` | ||
Number of workers requested. | ||
Returns | ||
------- | ||
workers : :class:`int` | ||
Number of workers available. | ||
""" | ||
|
||
# the number of workers may not be less than -1 | ||
if workers < -1: | ||
raise ValueError( | ||
f"Expected 'workers' to be greater or equal to -1 but got {workers}." | ||
) | ||
|
||
# then, the maximum number of workers is determined ... | ||
# NOTE: the following does not count the number of total threads, but the number of | ||
# threads available to the process calling this function | ||
process = psutil.Process() | ||
max_workers = len(process.cpu_affinity()) # type: ignore | ||
del process | ||
|
||
# ... and overwrites the number of workers if it is set to -1 | ||
workers = max_workers if workers == -1 else workers | ||
|
||
# the number of workers is limited between 1 and the number of available threads | ||
return max(1, min(workers, max_workers)) |
Oops, something went wrong.