Skip to content

Commit

Permalink
Docs improvement vol.1 (#32)
Browse files Browse the repository at this point in the history
* Change theme from alabaster (don't like it) to insegel (like it very much)

* I'm kinda getting a hang of this reStructuredText

* Kneedle (#29)

* Update all dependencies and migrate to python3.10

* Finish up kneedle

* Reject `python3.6`, embrace `python3.10`. But be careful about `numpy`

* Upgrade github workflow

* Debug CI/CD pipeline: actions/setup-python#249 (comment)

* I found myself in some dependency hell

* This may fix my dependency hell problems

* Get rid of the constraint file

* Add asterisks

* Remove quotes

* Repo cleanup (preparation for ditching the `pip-tools`)

* Get rid of python3.6 officially

* Bump to 0.4.0

* Fix workflow

* Bump to 0.4.1

* Add `mariolpantunes/knee` as an inspiration

* Add `black` shield

* Improve docs by including the readme

* Add insegel theme back. I need to learn git

* Get rid of `npt.NDArray`

* Update C-method docs

* Optimize `index.rst`

* Change theme back to `furo`. `insegel` seems to have some drawbacks

* Fix linter
  • Loading branch information
InCogNiTo124 authored Jan 19, 2022
1 parent faef09c commit d48cc5d
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 19 deletions.
2 changes: 2 additions & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ isort
Sphinx
sphinx-autobuild
furo
insegel
m2r2

## User Guide.
mkdocs-material
Expand Down
4 changes: 4 additions & 0 deletions docs/api/apidoc/c_method.rst
Original file line number Diff line number Diff line change
@@ -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}`
6 changes: 2 additions & 4 deletions docs/api/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions docs/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
Welcome to knarrow's documentation!
===================================

.. mdinclude:: ../../README.md

.. toctree::
:maxdepth: 2
:caption: Contents:

apidoc/c_method.rst
apidoc/modules.rst


Expand Down
4 changes: 2 additions & 2 deletions src/knarrow/angle_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
90 changes: 90 additions & 0 deletions src/knarrow/c_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions src/knarrow/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
16 changes: 8 additions & 8 deletions src/knarrow/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit d48cc5d

Please sign in to comment.