Skip to content

Instantly share code, notes, and snippets.

@jeffwright13
Last active December 29, 2022 11:32
Show Gist options
  • Save jeffwright13/a4a17d04dcb2a40b520aff0c18a15972 to your computer and use it in GitHub Desktop.
Save jeffwright13/a4a17d04dcb2a40b520aff0c18a15972 to your computer and use it in GitHub Desktop.
Inflection Points (Python)
Python code to calculate inflection points
import numpy as np
def get_inflection_points(cls, x: list, tol: float = 0) -> List[tuple]:
# Returns index and value of all inflection points in the given list of data.
# Each inflection point is represented by a tuple: (index, value).
# The total collection of inflection points is then returned as a list.
# If 'tol' is > 0, an inflection point is not asserted unless it differs from
# the maximum-magnitude data point by a value of Max[x]*tol
# verify input data
if not x:
raise ValueError("No input data was provided")
if None in x:
raise ValueError(f"None value found in input data {x}, aborting")
if len(x) <= 2:
raise ValueError(
f"A list of length {len(x)} cannot have any inflection points"
)
if tol < 0:
raise ValueError(f"Tolerance {tol} cannot be negative")
max_x = max(abs(_) for _ in x)
# no change in input => no inflection points
dx = np.diff(np.array(x))
if not any(dx):
return []
# determine initial direction
n = 0
while dx[n] == 0:
n += 1
continue
ascending = True if dx[n] > 0 else False
# find all inflection points from input data
inflections = []
for n in range(len(dx)):
if ascending:
if dx[n] >= 0:
continue
else:
if dx[n] <= -(max_x * tol):
inflections.append((n, x[n]))
ascending = False
else:
continue
else:
if dx[n] <= 0:
continue
else:
if dx[n] >= max_x * tol:
inflections.append((n, x[n]))
ascending = True
else:
continue
return inflections
from typing import Union
import pytest
from get_inflection_points import get_inflection_points
@pytest.mark.parametrize(
"input",
[
None,
([]),
([0]),
([4, 1]),
# ([0,0,0], -0.1)
],
)
def test_get_inflection_points_with_invalid_data(
input: Union[None, list, list[int]]
) -> None:
def cause_value_error(i) -> None:
get_inflection_points(i)
with pytest.raises(ValueError):
cause_value_error(input)
def test_get_inflection_points_with_none_value_in_data() -> None:
def cause_value_error() -> None:
get_inflection_points([1, 2, 3, None])
with pytest.raises(ValueError):
cause_value_error()
def test_get_inflection_points_with_invalid_tolerance() -> None:
def cause_value_error() -> None:
get_inflection_points([0, 0, 0], tol=-0.1)
with pytest.raises(ValueError):
cause_value_error()
@pytest.mark.parametrize(
"data, expected",
[
([0, 0, 0, 0, 0], []),
([1, 1, 1], []),
([-1, -1, -1], []),
([0, 0, 0, 0, 1, 0], [(4, 1)]),
([0, 0, 0, 0, 1, 0, 0, 1], [(4, 1), (6, 0)]),
([0, 0, 0, 0, 1, 3], []),
([0, 0, 0, 0, 1, -1, 1], [(4, 1), (5, -1)]),
([-10, -10, -10, 0, 0, 0, 0, 0], []),
([0, 0, 1, 2, 1], [(3, 2)]),
([1, -1, 1], [(1, -1)]),
([0, -10, -10, 10, 0], [(2, -10), (3, 10)]),
],
)
def test_get_inflection_points_with_valid_data(data, expected) -> None:
assert get_inflection_points(data) == expected
@pytest.mark.parametrize(
"data, tolerance, expected",
[
([0, 0, 0, 0, 0], 0, []),
([0, 0, 0, 0, 0], 0.01, []),
([0, 0, 0, 0, 0], 0.01, []),
([1, 2, 3, 4, 3, 2, 1, 0], 0.01, [(3, 4)]),
(
[10, 12, 13, 14, 15, 13.5, 13, 12, 10.5, 12, 10],
0.1,
[(4, 15), (8, 10.5), (9, 12)],
),
(
[-_ for _ in [10, 12, 13, 14, 15, 13.5, 13, 12, 10.5, 10, 11.5]],
0.1,
[(4, -15), (9, -10)],
),
([100, 200, 300, 400, 500, 450], 0.0001, [(4, 500)]),
([100, 200, 300, 330, 300, 331], 0.0001, [(3, 330), (4, 300)]),
([100, 200, 300, 331, 330, -100], 0.0001, [(3, 331)]),
([-1000, -500, -2, 0, 1, 0, 2, 100, 110, 111, 111], 0, [(4, 1), (5, 0)]),
([-1000, -500, -2, 0, 1, 0, 2, 100, 110], 1.0, []),
([-1000, -500, -2, 0, 1, 0, 2, 102, 110], 0.1, []),
([-1000, -500, -2, 0, 1, 0, 2, 100, 110], 0.01, []),
(
[-1000, -500, -2, 0, 1, 0, 2, 102, 110, 10, 11, 111],
0.1,
[(8, 110), (10, 11)],
),
],
)
def test_get_inflection_points_with_tolerance(data, tolerance, expected) -> None:
assert get_inflection_points(data, tol=tolerance) == expected
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment