Source code for c4dynamics.filters.lowpass
import numpy as np
import sys
from typing import Optional
sys.path.append('.')
import c4dynamics as c4d
[docs]
class lowpass(c4d.state):
"""
A first-order low-pass filter for smoothing signals, supporting both discrete and continuous systems.
Parameters
----------
alpha : float, optional
Smoothing factor for a discrete system. Must be in the range (0, 1). Defaults to None.
dt : float, optional
Time step for a continuous system. Must be positive. Defaults to None.
tau : float, optional
Time constant for a continuous system. Must be positive. Defaults to None.
y0 : float, optional
Initial value of the state. Defaults to 0.
Raises
------
ValueError
If neither `alpha` nor both `dt` and `tau` are provided.
If `alpha` is out of the range (0, 1) for a discrete system.
If `dt` or `tau` is non-positive for a continuous system.
Notes
-----
- For a continuous system, `dt` and `tau` are required, and `alpha` is calculated as `dt / tau`.
- For a discrete system, `alpha` alone is required and directly specifies the smoothing factor.
Example
-------
.. code::
>>> filter_continuous = lowpass(dt=0.01, tau=0.1, y0=0)
>>> filter_discrete = lowpass(alpha=0.5, y0=1)
>>> filter_continuous.sample(1.0) # doctest: +ELLIPSIS
0.09...
>>> filter_discrete.sample(2.0)
1.5
"""
def __init__(self, alpha: Optional[float] = None, dt: Optional[float] = None,
tau: Optional[float] = None, y0: float = 0) -> None:
# Initialize alpha based on the provided parameters
if dt is not None and tau is not None:
if dt <= 0 or tau <= 0:
raise ValueError("For a continuous system, `dt` and `tau` must be positive.")
self.alpha = dt / tau
elif alpha is not None:
if not (0 < alpha < 1):
raise ValueError("For a discrete system, `alpha` must be in the range (0, 1).")
self.alpha = alpha
else:
raise ValueError("Provide either `alpha` for a discrete system or both `dt` and `tau` for a continuous system.")
self.y = y0 # Initial state value
[docs]
def sample(self, x: float) -> float:
"""
Applies the low-pass filter to the input value and returns the filtered output.
Parameters
----------
x : float
Input value to be filtered.
Returns
-------
float
The filtered output value after applying the low-pass filter.
Notes
-----
- For a continuous system: `y'(t) = -y(t) / tau + x(t) / tau`
- For a discrete system: `y[k] = (1 - alpha) * y[k-1] + alpha * x[k]`
- The filter's state (`self.y`) is updated in place.
Example
-------
.. code::
>>> lp_filter = lowpass(alpha=0.5)
>>> lp_filter.sample(2.0)
1.0
>>> lp_filter.sample(3.0)
2.0
"""
# Update the filter's state
self.y = (1 - self.alpha) * self.y + self.alpha * x
return self.y
if __name__ == "__main__":
import doctest
import contextlib
import os
from c4dynamics import IgnoreOutputChecker, cprint
# Register the custom OutputChecker
doctest.OutputChecker = IgnoreOutputChecker
tofile = False
optionflags = doctest.FAIL_FAST
if tofile:
with open('tests/_out/output.txt', 'w') as f:
with contextlib.redirect_stdout(f), contextlib.redirect_stderr(f):
result = doctest.testmod(optionflags=optionflags)
else:
result = doctest.testmod(optionflags=optionflags)
if result.failed == 0:
cprint(os.path.basename(__file__) + ": all tests passed!", 'g')
else:
print(f"{result.failed} test(s) failed.")