Last active
December 29, 2022 11:32
-
-
Save jeffwright13/a4a17d04dcb2a40b520aff0c18a15972 to your computer and use it in GitHub Desktop.
Inflection Points (Python)
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
Python code to calculate inflection points |
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
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 |
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
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