From 234895c94037f97690ab069b159650f199cd8dbe Mon Sep 17 00:00:00 2001 From: ASPP Student Date: Tue, 29 Aug 2023 12:09:50 +0300 Subject: [PATCH 01/14] Improve functions and add tests for generic cases --- logistic.py | 11 ++++++++++- test_logistic.py | 17 ++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/logistic.py b/logistic.py index e49d1c2..e523c12 100644 --- a/logistic.py +++ b/logistic.py @@ -1 +1,10 @@ -# Your code goes here +def logistic_step(x, r): + """ Compute the logistic map for a given value of x and r""" + return r * x * (1 - x) + +def run_iterations(start, n_iter, r): + """ Run iterations of logistic function """ + val = [start] + for i_iter in range(n_iter): + val.append(logistic_step(val[i_iter], r)) + return val \ No newline at end of file diff --git a/test_logistic.py b/test_logistic.py index 9391bee..3b1824d 100644 --- a/test_logistic.py +++ b/test_logistic.py @@ -1,16 +1,27 @@ from numpy.testing import assert_allclose -from logistic import f +from logistic import logistic_step # Add here your test for the logistic map -def test_f_corner_cases(): +def test_logistic_step_corner_cases(): # Test cases are (x, r, expected) cases = [ (0, 1.1, 0), (1, 3.7, 0), ] for x, r, expected in cases: - result = f(x, r) + result = logistic_step(x, r) assert_allclose(result, expected) + +def test_logistic_step_generic_cases(): + # Test cases are (x, r, expected) + cases = [ + (0.1, 2.2, 0.198), + (0.2, 3.4, 0.544), + (0.5, 2, 0.5), + ] + for x, r, expected in cases: + result = logistic_step(x, r) + assert_allclose(result, expected) \ No newline at end of file From b9410b27b1b112591b585d56612c57be73bcbbd5 Mon Sep 17 00:00:00 2001 From: ASPP Student Date: Tue, 29 Aug 2023 12:26:15 +0300 Subject: [PATCH 02/14] Use parametrize decorator for testing --- test_logistic.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test_logistic.py b/test_logistic.py index 3b1824d..dcd2727 100644 --- a/test_logistic.py +++ b/test_logistic.py @@ -1,5 +1,5 @@ from numpy.testing import assert_allclose - +import pytest from logistic import logistic_step # Add here your test for the logistic map @@ -15,13 +15,13 @@ def test_logistic_step_corner_cases(): result = logistic_step(x, r) assert_allclose(result, expected) -def test_logistic_step_generic_cases(): - # Test cases are (x, r, expected) - cases = [ +@pytest.mark.parametrize('x, r, expected', [ (0.1, 2.2, 0.198), (0.2, 3.4, 0.544), (0.5, 2, 0.5), ] - for x, r, expected in cases: - result = logistic_step(x, r) - assert_allclose(result, expected) \ No newline at end of file + ) +def test_logistic_step_generic_cases(x, r, expected): + # Test cases are (x, r, expected) + result = logistic_step(x, r) + assert_allclose(result, expected) \ No newline at end of file From 1336a9f82c5dc3e54b40269677235df24fa34b16 Mon Sep 17 00:00:00 2001 From: ASPP Student Date: Tue, 29 Aug 2023 12:29:02 +0300 Subject: [PATCH 03/14] Use parametrize for all test functions --- test_logistic.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/test_logistic.py b/test_logistic.py index dcd2727..d76d87e 100644 --- a/test_logistic.py +++ b/test_logistic.py @@ -4,16 +4,15 @@ # Add here your test for the logistic map - -def test_logistic_step_corner_cases(): - # Test cases are (x, r, expected) - cases = [ +@pytest.mark.parametrize('x, r, expected', [ (0, 1.1, 0), (1, 3.7, 0), ] - for x, r, expected in cases: - result = logistic_step(x, r) - assert_allclose(result, expected) + ) +def test_logistic_step_corner_cases(x, r, expected): + # Test cases are (x, r, expected) + result = logistic_step(x, r) + assert_allclose(result, expected) @pytest.mark.parametrize('x, r, expected', [ (0.1, 2.2, 0.198), From 41fff02814fcc53c9a70ad25685ca6c58d8aa2a8 Mon Sep 17 00:00:00 2001 From: ASPP Student Date: Tue, 29 Aug 2023 12:30:57 +0300 Subject: [PATCH 04/14] Update comments --- test_logistic.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/test_logistic.py b/test_logistic.py index d76d87e..e662a9a 100644 --- a/test_logistic.py +++ b/test_logistic.py @@ -2,7 +2,6 @@ import pytest from logistic import logistic_step -# Add here your test for the logistic map @pytest.mark.parametrize('x, r, expected', [ (0, 1.1, 0), @@ -10,7 +9,6 @@ ] ) def test_logistic_step_corner_cases(x, r, expected): - # Test cases are (x, r, expected) result = logistic_step(x, r) assert_allclose(result, expected) @@ -21,6 +19,5 @@ def test_logistic_step_corner_cases(x, r, expected): ] ) def test_logistic_step_generic_cases(x, r, expected): - # Test cases are (x, r, expected) result = logistic_step(x, r) assert_allclose(result, expected) \ No newline at end of file From df77bc30212ca3124e492e1426e7b4ed17c93bec Mon Sep 17 00:00:00 2001 From: ASPP Student Date: Tue, 29 Aug 2023 12:32:24 +0300 Subject: [PATCH 05/14] Make constant val in logistic function easier to change --- logistic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/logistic.py b/logistic.py index e523c12..390c02e 100644 --- a/logistic.py +++ b/logistic.py @@ -1,6 +1,7 @@ def logistic_step(x, r): """ Compute the logistic map for a given value of x and r""" - return r * x * (1 - x) + constant_val = 1 + return r * x * (constant_val - x) def run_iterations(start, n_iter, r): """ Run iterations of logistic function """ From 6807cba95514c1a26df1606e34e0d6605b50204f Mon Sep 17 00:00:00 2001 From: ASPP Student Date: Tue, 29 Aug 2023 12:42:19 +0300 Subject: [PATCH 06/14] Test growth trajectories; reorder variables --- logistic.py | 2 +- test_logistic.py | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/logistic.py b/logistic.py index 390c02e..a1e6725 100644 --- a/logistic.py +++ b/logistic.py @@ -3,7 +3,7 @@ def logistic_step(x, r): constant_val = 1 return r * x * (constant_val - x) -def run_iterations(start, n_iter, r): +def run_iterations(start, r, n_iter): """ Run iterations of logistic function """ val = [start] for i_iter in range(n_iter): diff --git a/test_logistic.py b/test_logistic.py index e662a9a..82ef8dc 100644 --- a/test_logistic.py +++ b/test_logistic.py @@ -1,6 +1,6 @@ from numpy.testing import assert_allclose import pytest -from logistic import logistic_step +from logistic import logistic_step, run_iterations @pytest.mark.parametrize('x, r, expected', [ @@ -20,4 +20,14 @@ def test_logistic_step_corner_cases(x, r, expected): ) def test_logistic_step_generic_cases(x, r, expected): result = logistic_step(x, r) - assert_allclose(result, expected) \ No newline at end of file + assert_allclose(result, expected) + +@pytest.mark.parametrize('x, r, n_iter, expected', [ + (0.1, 2.2, 1, [0.1, 0.198]), + (0.2, 3.4, 4, [0.2, 0.544, 0.843418, 0.449019, 0.841163]), + (0.5, 2, 3, [0.5, 0.5, 0.5, 0.5]), + ] + ) +def test_logistic_iterations(x, r, n_iter, expected): + result = run_iterations(x, r, n_iter) + assert_allclose(result, expected, rtol=1e-4) \ No newline at end of file From 1cae8c37c15acc6d406bb9cac6ed4c51bc4b30b7 Mon Sep 17 00:00:00 2001 From: ASPP Student Date: Tue, 29 Aug 2023 12:54:08 +0300 Subject: [PATCH 07/14] Plot trajectories --- logistic.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/logistic.py b/logistic.py index a1e6725..62899c3 100644 --- a/logistic.py +++ b/logistic.py @@ -1,3 +1,6 @@ +from matplotlib import pyplot as plt + + def logistic_step(x, r): """ Compute the logistic map for a given value of x and r""" constant_val = 1 @@ -8,4 +11,18 @@ def run_iterations(start, r, n_iter): val = [start] for i_iter in range(n_iter): val.append(logistic_step(val[i_iter], r)) - return val \ No newline at end of file + return val + +if __name__=='__main__': + start_vals = [i/10 for i in range(1, 6)] + r = 1.5 + n_iter = 10 + fig_name = "test_trajectory" + + fig, ax = plt.subplots(figsize=(10, 5)) + for i_start_val in start_vals: + trajectory = run_iterations(i_start_val, r, n_iter) + ax.plot(list(range(n_iter+1)), trajectory, label=str(i_start_val)) + fig.suptitle(f'Logistic function: r={r}, n_iter={n_iter}') + + fig.savefig(fig_name) \ No newline at end of file From 22973a3c773887413d8977d150fe1416a5d6ccf1 Mon Sep 17 00:00:00 2001 From: ASPP Student Date: Tue, 29 Aug 2023 14:43:38 +0300 Subject: [PATCH 08/14] Add test for fitting logistic growth curve --- logistic.py | 4 ++-- logistic_fit.py | 4 ++-- test_logistic.py | 13 ++++++++++++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/logistic.py b/logistic.py index 62899c3..b35508f 100644 --- a/logistic.py +++ b/logistic.py @@ -15,8 +15,8 @@ def run_iterations(start, r, n_iter): if __name__=='__main__': start_vals = [i/10 for i in range(1, 6)] - r = 1.5 - n_iter = 10 + r = 1.2 + n_iter = 20 fig_name = "test_trajectory" fig, ax = plt.subplots(figsize=(10, 5)) diff --git a/logistic_fit.py b/logistic_fit.py index 1d38ac7..c3a6f34 100644 --- a/logistic_fit.py +++ b/logistic_fit.py @@ -1,6 +1,6 @@ import numpy as np -from logistic import iterate_f +from logistic import run_iterations def fit_r(xs): @@ -24,7 +24,7 @@ def fit_r(xs): it = len(xs) - 1 def error(r): - return np.linalg.norm(xs - iterate_f(it, x0, r)) + return np.linalg.norm(xs - run_iterations(x0, r, it)) errors = [] for r in np.linspace(0, 4, 4001): diff --git a/test_logistic.py b/test_logistic.py index 82ef8dc..cbc2f9b 100644 --- a/test_logistic.py +++ b/test_logistic.py @@ -1,6 +1,7 @@ from numpy.testing import assert_allclose import pytest from logistic import logistic_step, run_iterations +from logistic_fit import fit_r @pytest.mark.parametrize('x, r, expected', [ @@ -30,4 +31,14 @@ def test_logistic_step_generic_cases(x, r, expected): ) def test_logistic_iterations(x, r, n_iter, expected): result = run_iterations(x, r, n_iter) - assert_allclose(result, expected, rtol=1e-4) \ No newline at end of file + assert_allclose(result, expected, rtol=1e-4) + +@pytest.mark.parametrize('x, r, n_iter', [ + (0.1, 2.2, 1), + (0.2, 3.4, 4), + (0.5, 2, 3), + ] + ) +def test_logistic_fit_r(x, r, n_iter): + result = fit_r(run_iterations(x, r, n_iter)) + assert_allclose(result, r, rtol=1e-4) \ No newline at end of file From a8625e971a490c299622fdfce4168b62cccec537 Mon Sep 17 00:00:00 2001 From: ASPP Student Date: Tue, 29 Aug 2023 14:49:36 +0300 Subject: [PATCH 09/14] Improve function name --- logistic_fit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logistic_fit.py b/logistic_fit.py index c3a6f34..d3d1061 100644 --- a/logistic_fit.py +++ b/logistic_fit.py @@ -23,10 +23,10 @@ def fit_r(xs): x0 = xs[0] it = len(xs) - 1 - def error(r): + def compute_error(r): return np.linalg.norm(xs - run_iterations(x0, r, it)) errors = [] for r in np.linspace(0, 4, 4001): - errors.append((r, error(r))) + errors.append((r, compute_error(r))) return min(errors, key=lambda x: x[1])[0] From 4583639aea5e1afc9dedac555067bd2fa7c373ff Mon Sep 17 00:00:00 2001 From: ASPP Student Date: Tue, 29 Aug 2023 15:18:05 +0300 Subject: [PATCH 10/14] Test convergence values using random initial values --- test_logistic.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/test_logistic.py b/test_logistic.py index cbc2f9b..08f92ae 100644 --- a/test_logistic.py +++ b/test_logistic.py @@ -2,7 +2,8 @@ import pytest from logistic import logistic_step, run_iterations from logistic_fit import fit_r - +from itertools import product +import numpy as np @pytest.mark.parametrize('x, r, expected', [ (0, 1.1, 0), @@ -33,12 +34,18 @@ def test_logistic_iterations(x, r, n_iter, expected): result = run_iterations(x, r, n_iter) assert_allclose(result, expected, rtol=1e-4) -@pytest.mark.parametrize('x, r, n_iter', [ - (0.1, 2.2, 1), - (0.2, 3.4, 4), - (0.5, 2, 3), - ] - ) -def test_logistic_fit_r(x, r, n_iter): - result = fit_r(run_iterations(x, r, n_iter)) - assert_allclose(result, r, rtol=1e-4) \ No newline at end of file + + + +# @pytest.mark.parametrize('x, r, n_iter', [(i/10,f/10,20) for i,f in product(range(0,20),range(0,30,5))] +# ) +# def test_logistic_fit_r(x, r, n_iter): +# result = fit_r(run_iterations(x, r, n_iter)) +# assert_allclose(result, r, rtol=1e-3) +SEED = 5 +@pytest.mark.parametrize('x', [np.random.RandomState(SEED).uniform(0.0001, 0.9999) for _ in range(50)]) +def test_logistic_convergence(x): + r = 1.5 + n_iter = 100 + result = run_iterations(x, r, n_iter) + assert_allclose(result[-1], 1/3, rtol=1e-4) From 9db6e5f1b18a423889452dcdcb9697f805ffc89a Mon Sep 17 00:00:00 2001 From: ASPP Student Date: Tue, 29 Aug 2023 15:21:34 +0300 Subject: [PATCH 11/14] Fix testing value ranges for r and x0 --- test_logistic.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test_logistic.py b/test_logistic.py index 08f92ae..f90fc78 100644 --- a/test_logistic.py +++ b/test_logistic.py @@ -37,11 +37,13 @@ def test_logistic_iterations(x, r, n_iter, expected): -# @pytest.mark.parametrize('x, r, n_iter', [(i/10,f/10,20) for i,f in product(range(0,20),range(0,30,5))] -# ) -# def test_logistic_fit_r(x, r, n_iter): -# result = fit_r(run_iterations(x, r, n_iter)) -# assert_allclose(result, r, rtol=1e-3) +@pytest.mark.parametrize('x, r, n_iter', [(i/10,f/10,20) for i,f in product(range(1,10),range(10,30,2))] + ) +def test_logistic_fit_r(x, r, n_iter): + result = fit_r(run_iterations(x, r, n_iter)) + assert_allclose(result, r, rtol=1e-3) + + SEED = 5 @pytest.mark.parametrize('x', [np.random.RandomState(SEED).uniform(0.0001, 0.9999) for _ in range(50)]) def test_logistic_convergence(x): From 82c5df9746b12e07395d061e7dfa7c8f39711767 Mon Sep 17 00:00:00 2001 From: ASPP Student Date: Tue, 29 Aug 2023 15:24:38 +0300 Subject: [PATCH 12/14] Make expected value easier to change --- test_logistic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test_logistic.py b/test_logistic.py index f90fc78..ea080b1 100644 --- a/test_logistic.py +++ b/test_logistic.py @@ -49,5 +49,6 @@ def test_logistic_fit_r(x, r, n_iter): def test_logistic_convergence(x): r = 1.5 n_iter = 100 + expected = 1/3 result = run_iterations(x, r, n_iter) - assert_allclose(result[-1], 1/3, rtol=1e-4) + assert_allclose(result[-1], expected, rtol=1e-4) From a0c782de3c6ce452d7660b3f2b19b31b0c6c35d3 Mon Sep 17 00:00:00 2001 From: ASPP Student Date: Tue, 29 Aug 2023 15:35:07 +0300 Subject: [PATCH 13/14] Use fixture for random start value; reduce number of test values --- test_logistic.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/test_logistic.py b/test_logistic.py index ea080b1..65c471f 100644 --- a/test_logistic.py +++ b/test_logistic.py @@ -5,6 +5,17 @@ from itertools import product import numpy as np + +SEED = 5 + + +@pytest.fixture +def random_state(): + print(f'Using seed{SEED}') + random_state = np.random.RandomState(SEED) + return random_state + + @pytest.mark.parametrize('x, r, expected', [ (0, 1.1, 0), (1, 3.7, 0), @@ -37,18 +48,18 @@ def test_logistic_iterations(x, r, n_iter, expected): -@pytest.mark.parametrize('x, r, n_iter', [(i/10,f/10,20) for i,f in product(range(1,10),range(10,30,2))] +@pytest.mark.parametrize('x, r, n_iter', [(i/10,f/10,20) for i,f in product(range(1,10,3),range(10,30,4))] ) def test_logistic_fit_r(x, r, n_iter): result = fit_r(run_iterations(x, r, n_iter)) assert_allclose(result, r, rtol=1e-3) -SEED = 5 -@pytest.mark.parametrize('x', [np.random.RandomState(SEED).uniform(0.0001, 0.9999) for _ in range(50)]) -def test_logistic_convergence(x): +def test_logistic_convergence(random_state): r = 1.5 n_iter = 100 expected = 1/3 - result = run_iterations(x, r, n_iter) - assert_allclose(result[-1], expected, rtol=1e-4) + for _ in range(100): + x = random_state.uniform(0.0001, 0.9999) + result = run_iterations(x, r, n_iter) + assert_allclose(result[-1], expected, rtol=1e-4) From 2be8ccc87fb7a7bbe38f54cf988452cd13adbc7a Mon Sep 17 00:00:00 2001 From: ASPP Student Date: Tue, 29 Aug 2023 15:44:36 +0300 Subject: [PATCH 14/14] Set a random random seed --- test_logistic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_logistic.py b/test_logistic.py index 65c471f..3ce233d 100644 --- a/test_logistic.py +++ b/test_logistic.py @@ -6,12 +6,12 @@ import numpy as np -SEED = 5 +SEED = np.random.randint(0, 2**31) @pytest.fixture def random_state(): - print(f'Using seed{SEED}') + print(f'Using seed {SEED}') random_state = np.random.RandomState(SEED) return random_state