diff --git a/dev-requirements.txt b/dev-requirements.txt index 0d5c14f..5d4fba1 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -24,6 +24,8 @@ isort Sphinx sphinx-autobuild furo +insegel +m2r2 ## User Guide. mkdocs-material diff --git a/docs/api/apidoc/c_method.rst b/docs/api/apidoc/c_method.rst new file mode 100644 index 0000000..44377e5 --- /dev/null +++ b/docs/api/apidoc/c_method.rst @@ -0,0 +1,4 @@ +C-method +======== + +C-method fits a function: :math:`f\left(x; c\right) = \frac{x\left(e^c + 1\right)}{x e^c + 1}` \ No newline at end of file diff --git a/docs/api/conf.py b/docs/api/conf.py index 8e04f9a..8632b79 100644 --- a/docs/api/conf.py +++ b/docs/api/conf.py @@ -28,9 +28,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [ - "sphinx.ext.napoleon", -] +extensions = ["sphinx.ext.napoleon", "m2r2"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -46,7 +44,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = "alabaster" +html_theme = "furo" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, diff --git a/docs/api/index.rst b/docs/api/index.rst index 3956828..16f4d3c 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -6,10 +6,13 @@ Welcome to knarrow's documentation! =================================== +.. mdinclude:: ../../README.md + .. toctree:: :maxdepth: 2 :caption: Contents: + apidoc/c_method.rst apidoc/modules.rst diff --git a/src/knarrow/angle_method.py b/src/knarrow/angle_method.py index 0d9bd5b..3e1564f 100644 --- a/src/knarrow/angle_method.py +++ b/src/knarrow/angle_method.py @@ -8,8 +8,8 @@ def angle(x, y, **kwargs): Quite sensitive to noise, use with cubic spline smoothing. Args: - x: npt.NDArray, the x coordinates of the points - y: npt.NDArray, the y coordinates of the points + x: `np.ndarray`, the x coordinates of the points + y: `np.ndarray`, the y coordinates of the points **kwargs: possible additional arguments (none are used) Returns: int, the index of the knee diff --git a/src/knarrow/c_method.py b/src/knarrow/c_method.py index ab35ef2..6555bed 100644 --- a/src/knarrow/c_method.py +++ b/src/knarrow/c_method.py @@ -4,28 +4,96 @@ def f(x, c): + """ + The knee curve. + + This function is being fitted to the input data by tweaking the `c` parameter. + + Args: + x: `np.ndarray`, the `x` coordinates + c: `float`, the shape parameter + + Returns: `np.ndarray`, the values of the function evaluated at the `x` w.r.t. `c` + + """ return x * (np.exp(c) + 1) / (x * np.exp(c) + 1) def df_dc(x, c): + """ + The first derivative of the knee curve `f(x)` w.r.t x, conditioned on `c`. + + Args: + x: `np.ndarray`, the `x` coordinates + c: `float`, the shape parameter + + Returns: `np.ndarray`, values of the slopes of tangents on `f(x)` at `x` + + """ t = x * np.exp(c) return (x - 1) * t / (t + 1) ** 2 def d2f_dc2(x, c): + """ + The second derivative of the knee curve `f(x)` w.r.t x, conditioned on `c`. + + Args: + x: `np.ndarray`, the `x` coordinates + c: `float`, the shape parameter + + Returns: `np.ndarray`, values of the second derivative of `f(x)` at `x` + + """ t = x * np.exp(c) return (x - 1) * x * np.exp(c) * (t - 1) / (t + 1) ** 3 def de_dc(y, x, c): + """ + The first derivative of the energy function `E(x)` w.r.t `c`. + + Used in Newton-Raphson method for the optimization of the shape parameter `c`. + + Args: + y: `np.ndarray`, the ground truth function values we wish to fit the knee curve `f(x)` on + x: `np.ndarray`, the `x` coordinates + c: `float`, the shape parameter + + Returns: `np.ndarray`, values of the first derivative of `E(x)` at `x` + """ return np.mean((f(x, c) - y) * df_dc(x, c)) def d2e_dc2(y, x, c): + """ + The second derivative of the energy function `E(x)` w.r.t `c`. + + Used in Newton-Raphson method for the optimization of the shape parameter `c`. + + Args: + y: `np.ndarray`, the ground truth function values we wish to fit the knee curve `f(x)` on + x: `np.ndarray`, the `x` coordinates + c: `float`, the shape parameter + + Returns: `np.ndarray`, values of the second derivative of `E(x)` at `x` + """ return np.mean(df_dc(x, c) + (f(x, c) - y) * d2f_dc2(x, c)) def newton_raphson(x, y): + """ + The implementation of the [Newton-Raphson](https://en.wikipedia.org/wiki/Newton%27s_method) optimization procedure. + It fits the knee curve `f(x)` to the `y` s of the corresponding `x` s by tweaking the shape parameter `c` from an + initial guess. + + Args: + x: `np.ndarray`, the ground truth `x` coordinates + y: `np.ndarray`, the ground truth `y` coordinates + + Returns: `float`, the optimal shape parameter `c` which minimizes the squared error, up to a predefined tolerance + level + """ c = 0 new_c = 3 while abs(new_c - c) > TOLERANCE: @@ -35,10 +103,32 @@ def newton_raphson(x, y): def get_knee(c): + """ + Calculates the position of the point of maximal curvature. + + The position depends solely on the shape parameter `c`. + + Args: + c: `float`, the shape parameter + + Returns: `float`, the `x` position where the knee curve `f(x)` is most curved + + """ return (np.sqrt(1 + np.exp(c)) - 1) / np.exp(c) def c_method(x, y, **kwargs): + """ + Implements the C-method as described in https://blog.msmetko.xyz/posts/2 + + Args: + x: `np.ndarray`, the ground truth `x` coordinates + y: `np.ndarray`, the ground truth `y` coordinates + **kwargs: `dict`, optional arguments (should be empty) + + Returns: + + """ assert len(kwargs) == 0 best_c = newton_raphson(x, y) knee = get_knee(best_c) diff --git a/src/knarrow/main.py b/src/knarrow/main.py index 99dcde7..02430a8 100644 --- a/src/knarrow/main.py +++ b/src/knarrow/main.py @@ -13,12 +13,12 @@ def find_knee(x, y, method="menger_successive", **kwargs): Public method for finding the knee Args: - x: npt.NDArray, the x coordinates of the points - y: npt.NDArray, the y coordinates of the points - method: str, denotes the method to be used (default: menger_successive) + x: `np.ndarray`, the x coordinates of the points + y: `np.ndarray`, the y coordinates of the points + method: `str`, denotes the method to be used (default: menger_successive) **kwargs: possible additional arguments for the knee-finding method - Returns: int, the index of the knee + Returns: `int`, the index of the knee """ assert method in [ "angle", diff --git a/src/knarrow/util.py b/src/knarrow/util.py index 361907c..b21bf06 100644 --- a/src/knarrow/util.py +++ b/src/knarrow/util.py @@ -10,19 +10,19 @@ EPS = 1e-5 -def np_windowed(length: int, window_size: int, stride: int = 1, dilation: int = 1) -> npt.NDArray[np.int_]: +def np_windowed(length: int, window_size: int, stride: int = 1, dilation: int = 1) -> np.ndarray: """ Return indices x such that every row in array[x] is a windowed slice of the array Helper function which uses numpy broadcasting to work Adapted from: https://stackoverflow.com/a/42258242 Args: - length: int, the total length of the array to be indexed - window_size: int, the size of the window to be slided across the array - stride: int, the size of the "jumps" between the consecutive windows (default: 1) - dilation: int, the distance between the elements in the window (default: 1) + length: `int`, the total length of the array to be indexed + window_size: `int`, the size of the window to be slided across the array + stride: `int`, the size of the "jumps" between the consecutive windows (default: 1) + dilation: `int`, the distance between the elements in the window (default: 1) - Returns: npt.NDArray, indices for windowing an array + Returns: `np.ndarray`, indices for windowing an array """ base_indices: npt.NDArray[np.int_] = stride * np.arange((length - window_size) // (stride * dilation) + 1).reshape( -1, 1 @@ -37,8 +37,8 @@ def np_anchored(length: int) -> npt.NDArray[np.int_]: Helper function which uses numpy broadcasting to work Args: - length: int, the total length of the array to be indexed in the anchored way - Returns: npt.NDArray, indices for windowing an array + length: `int`, the total length of the array to be indexed in the anchored way + Returns: `np.ndarray`, indices for windowing an array """ indices = list(range(1, length - 1)) diff --git a/tox.ini b/tox.ini index d90bdc3..4864c24 100644 --- a/tox.ini +++ b/tox.ini @@ -82,7 +82,7 @@ deps = -r{toxinidir}/dev-requirements.txt changedir = {toxinidir}/docs/api commands = - {envpython} clean_docs.py + #{envpython} clean_docs.py sphinx-apidoc --force --output-dir apidoc {toxinidir}/src/knarrow sphinx-build -a -W . _build