Testing Class Material
This commit is contained in:
commit
05b1f6cdd5
85 changed files with 102796 additions and 0 deletions
BIN
hands_on_solutions/logistic_fun/bifurcation.png
Normal file
BIN
hands_on_solutions/logistic_fun/bifurcation.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 567 KiB |
28
hands_on_solutions/logistic_fun/conftest.py
Normal file
28
hands_on_solutions/logistic_fun/conftest.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
import numpy as np
|
||||
import pytest
|
||||
|
||||
|
||||
# add a commandline option to pytest
|
||||
def pytest_addoption(parser):
|
||||
"""Add random seed option to py.test.
|
||||
"""
|
||||
parser.addoption('--seed', dest='seed', type=int, action='store',
|
||||
help='set random seed')
|
||||
|
||||
|
||||
# configure pytest to automatically set the rnd seed if not passed on CLI
|
||||
def pytest_configure(config):
|
||||
seed = config.getvalue("seed")
|
||||
# if seed was not set by the user, we set one now
|
||||
if seed is None or seed == ('NO', 'DEFAULT'):
|
||||
config.option.seed = int(np.random.randint(2**31-1))
|
||||
|
||||
|
||||
def pytest_report_header(config):
|
||||
return f'Using random seed: {config.option.seed}'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def random_state(request):
|
||||
random_state = np.random.RandomState(request.config.option.seed)
|
||||
return random_state
|
10
hands_on_solutions/logistic_fun/conftest2.py
Normal file
10
hands_on_solutions/logistic_fun/conftest2.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
import numpy as np
|
||||
import pytest
|
||||
|
||||
SEED = 42
|
||||
|
||||
@pytest.fixture
|
||||
def random_state():
|
||||
print(f'Seed: {SEED}')
|
||||
random_state = np.random.RandomState(SEED)
|
||||
return random_state
|
25
hands_on_solutions/logistic_fun/logistic.py
Normal file
25
hands_on_solutions/logistic_fun/logistic.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
import numpy as np
|
||||
|
||||
|
||||
def f(x, r):
|
||||
"""
|
||||
takes r and x as input and returns r*x*(1-x)
|
||||
"""
|
||||
return r * x * (1 - x)
|
||||
|
||||
|
||||
def iterate_f(it, xi, r):
|
||||
"""
|
||||
takes a number of iterations `it`, a starting value,
|
||||
and a parameter value for r. It should execute f repeatedly (it times),
|
||||
each time using the last result of f as the new input to f. Append each
|
||||
iteration's result to a list l. Finally, convert the list into a numpy
|
||||
array and return it.
|
||||
"""
|
||||
x = xi
|
||||
xs = []
|
||||
for _ in range(it):
|
||||
x = f(x, r)
|
||||
xs.append(x)
|
||||
|
||||
return np.array(xs)
|
70
hands_on_solutions/logistic_fun/plot_logfun.py
Normal file
70
hands_on_solutions/logistic_fun/plot_logfun.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
"""Usage:
|
||||
```
|
||||
plot_trajectory(100, 3.6, 0.1)
|
||||
plot_bifurcation(2.5, 4.2, 0.001)
|
||||
```
|
||||
"""
|
||||
import numpy as np
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
from logistic import iterate_f
|
||||
|
||||
|
||||
def plot_trajectory(n, r, x0, fname="single_trajectory.png"):
|
||||
"""
|
||||
Saves a plot of a single trajectory of the logistic function
|
||||
|
||||
inputs
|
||||
n: int (number of iterations)
|
||||
r: float (r value for the logistic function)
|
||||
x0: float (between 0 and 1, starting point for the iteration)
|
||||
fname: str (filename to which to save the image)
|
||||
|
||||
returns
|
||||
fig, ax (matplotlib objects)
|
||||
"""
|
||||
l = iterate_f(n, x0, r)
|
||||
fig, ax = plt.subplots(figsize=(10, 5))
|
||||
ax.plot(list(range(n)), l)
|
||||
fig.suptitle('Logistic Function')
|
||||
|
||||
fig.savefig(fname)
|
||||
return fig, ax
|
||||
|
||||
|
||||
def plot_bifurcation(start, end, step, fname="bifurcation.png", it=100000,
|
||||
last=300):
|
||||
"""
|
||||
Saves a plot of the bifurcation diagram of the logistic function. The
|
||||
`start`, `end`, and `step` parameters define for which r values to
|
||||
calculate the logistic function. If you space them too closely, it might
|
||||
take a very long time, if you dont plot enough, your bifurcation diagram
|
||||
won't be informative. Choose wisely!
|
||||
|
||||
inputs
|
||||
start, end, step: float (which r values to calculate the logistic
|
||||
function for)
|
||||
fname: str (filename to which to save the image)
|
||||
it: int (how many iterations to run for each r value)
|
||||
last: int (how many of the last iterates to plot)
|
||||
|
||||
|
||||
returns
|
||||
fig, ax (matplotlib objects)
|
||||
"""
|
||||
r_range = np.arange(start, end, step)
|
||||
x = []
|
||||
y = []
|
||||
|
||||
for r in r_range:
|
||||
l = iterate_f(it, 0.1, r)
|
||||
ll = l[len(l) - last::].copy()
|
||||
lll = np.unique(ll)
|
||||
y.extend(lll)
|
||||
x.extend(np.ones(len(lll)) * r)
|
||||
|
||||
fig, ax = plt.subplots(figsize=(20, 10))
|
||||
ax.scatter(x, y, s=0.1, color='k')
|
||||
ax.set_xlabel("r")
|
||||
fig.savefig(fname)
|
||||
return fig, ax
|
3
hands_on_solutions/logistic_fun/plot_script.py
Normal file
3
hands_on_solutions/logistic_fun/plot_script.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from plot_logfun import plot_trajectory
|
||||
|
||||
plot_trajectory(100, 3.4, 0.1)
|
81
hands_on_solutions/logistic_fun/readme.md
Normal file
81
hands_on_solutions/logistic_fun/readme.md
Normal file
|
@ -0,0 +1,81 @@
|
|||
# Testing Project for ASPP 2023 Mexico
|
||||
|
||||
## Exercise 1 -- @parametrize and the logistic map
|
||||
|
||||
Make a file `logistic.py` and `test_logistic.py` in the same folder as this
|
||||
readme and the `plot_logfun.py` file. Implement the code for the logistic map
|
||||
in the `logistic.py` file:
|
||||
|
||||
a) Implement the logistic map f(𝑥)=𝑟∗𝑥∗(1−𝑥) . Use `@parametrize`
|
||||
to test the function for the following cases:
|
||||
```
|
||||
x=0.1, r=2.2 => f(x, r)=0.198
|
||||
x=0.2, r=3.4 => f(x, r)=0.544
|
||||
x=0.75, r=1.7 => f(x, r)=0.31875
|
||||
```
|
||||
|
||||
b) Implement the function `iterate_f` that runs `f` for `it`
|
||||
iterations, each time passing the result back into f.
|
||||
Use `@parametrize` to test the function for the following cases:
|
||||
```
|
||||
x=0.1, r=2.2, it=1 => iterate_f(it, x, r)=[0.198]
|
||||
x=0.2, r=3.4, it=4 => f(x, r)=[0.544, 0.843418, 0.449019, 0.841163]
|
||||
x=0.75, r=1.7, it=2 => f(x, r)=[0.31875, 0.369152]
|
||||
```
|
||||
|
||||
c) Import and call the `plot_trajectory` function from the `plot_logfun`
|
||||
module to look at the trajectories generated by your code. The `plot_logfun`
|
||||
imports and uses your `logistic.py` code. Import the module
|
||||
and call the function in a new `plot_script.py` file.
|
||||
|
||||
Try with values `r<3`, `r>4` and `3<r<4` to get a feeling for how the function
|
||||
behaves differently with different parameters. Note that your input x0 should
|
||||
be between 0 and 1.
|
||||
|
||||
## Exercise 2 -- Check the convergence of an attractor using fuzzing
|
||||
a) Write a numerical fuzzing test that checks that, for `r=1.5`, all
|
||||
starting points converge to the attractor `f(x, r) = 1/3`.
|
||||
|
||||
b) Use `pytest.mark` to mark the tests from the previous exercise with one mark
|
||||
(they relate to the correct implementation of the logistic map) and the
|
||||
test from this exercise with another (relates to the behavior of the logistic
|
||||
map). Try executing first the first set of tests and then the second set of
|
||||
tests separately.
|
||||
|
||||
## Exercise 3 -- Chaotic behavior
|
||||
Some r values for `3<r<4` have some interesting properties. A chaotic
|
||||
trajectory doesn't diverge but also doesn't converge.
|
||||
|
||||
## Visualize the bifurcation diagram
|
||||
a) Use the `plot_trajectory` function from the `plot_logfun` module using your
|
||||
implementation of `f` and `iterate_f` to look at the bifurcation diagram.
|
||||
|
||||
The script generates an output image, `bifurcation_diagram.png`.
|
||||
|
||||
b) Write a test that checks for chaotic behavior when r=3.8. Run the
|
||||
logistic map for 100000 iterations and verify the conditions for
|
||||
chaotic behavior:
|
||||
|
||||
1) The function is deterministic: this does not need to be tested in
|
||||
this case
|
||||
2) Orbits must be bounded: check that all values are between 0 and 1
|
||||
3) Orbits must be aperiodic: check that the last 1000 values are all
|
||||
different
|
||||
4) Sensitive dependence on initial conditions: this is the bonus
|
||||
exercise below
|
||||
|
||||
The test should check conditions 2) and 3)!
|
||||
|
||||
|
||||
## Bonus Exercise 4 -- The Butterfly Effect
|
||||
For the same value of `r`, test the sensitive dependence on initial
|
||||
conditions, a.k.a. the butterfly effect. Use the following definition of SDIC.
|
||||
|
||||
>`f` is a function and `x0` and `y0` are two possible seeds.
|
||||
>If `f` has SDIC then:
|
||||
>there is a number `delta` such that for any `x0` there is a `y0` that is not
|
||||
>more than `init_error` away from `x0`, where the initial condition `y0` has
|
||||
>the property that there is some integer n such that after n iterations, the
|
||||
>orbit is more than `delta` away from the orbit of `x0`. That is
|
||||
>|xn-yn| > delta
|
||||
|
BIN
hands_on_solutions/logistic_fun/single_trajectory.png
Normal file
BIN
hands_on_solutions/logistic_fun/single_trajectory.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 85 KiB |
108
hands_on_solutions/logistic_fun/test_logistic.py
Normal file
108
hands_on_solutions/logistic_fun/test_logistic.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
import numpy as np
|
||||
from numpy.testing import assert_allclose
|
||||
import pytest
|
||||
|
||||
from logistic import f, iterate_f
|
||||
|
||||
# set the random seed for once here
|
||||
SEED = np.random.randint(0, 2**31)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def random_state():
|
||||
print(f'Using seed {SEED}')
|
||||
random_state = np.random.RandomState(SEED)
|
||||
return random_state
|
||||
|
||||
|
||||
|
||||
@pytest.mark.parametrize('a', [1, 2, 3])
|
||||
@pytest.mark.parametrize('b', [5, 6, 7])
|
||||
def test_addition_increases(a, b):
|
||||
print(a, b)
|
||||
assert b + a > a
|
||||
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'x, r, expected',
|
||||
[
|
||||
(0.1, 2.2, 0.198),
|
||||
(0.2, 3.4, 0.544),
|
||||
(0.75, 1.7, 0.31875),
|
||||
]
|
||||
)
|
||||
def test_f(x, r, expected):
|
||||
result = f(x, r)
|
||||
assert_allclose(result, expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'x, r, it, expected',
|
||||
[
|
||||
(0.1, 2.2, 1, [0.198]),
|
||||
(0.2, 3.4, 4, [0.544, 0.843418, 0.449019, 0.841163]),
|
||||
(0.75, 1.7, 2, [0.31875, 0.369152]),
|
||||
]
|
||||
)
|
||||
def test_iterate_f(x, r, it, expected):
|
||||
result = iterate_f(it, x, r)
|
||||
assert_allclose(result, expected, rtol=1e-5)
|
||||
|
||||
|
||||
def test_attractor_converges():
|
||||
SEED = 42
|
||||
random_state = np.random.RandomState(SEED)
|
||||
|
||||
for _ in range(100):
|
||||
x = random_state.uniform(0, 1)
|
||||
result = iterate_f(100, x, 1.5)
|
||||
assert_allclose(result[-1], 1 / 3)
|
||||
|
||||
####################################################################
|
||||
# These only work after adding the fixture
|
||||
####################################################################
|
||||
|
||||
|
||||
@pytest.mark.xfail
|
||||
def test_attractor_converges2(random_state):
|
||||
for _ in range(100):
|
||||
x = random_state.uniform(0, 1)
|
||||
result = iterate_f(100, x, 1.5)
|
||||
assert_allclose(result[-1], 1 / 3)
|
||||
|
||||
|
||||
@pytest.mark.xfail
|
||||
def test_chaotic_behavior(random_state):
|
||||
r = 3.8
|
||||
for _ in range(10):
|
||||
x = random_state.uniform(0, 1)
|
||||
result = iterate_f(100000, x, r)
|
||||
assert np.all(result >= 0.0)
|
||||
assert np.all(result <= 1.0)
|
||||
assert min(np.abs(np.diff(result[-1000:]))) > 1e-6
|
||||
|
||||
|
||||
@pytest.mark.xfail
|
||||
def test_sensitivity_to_initial_conditions(random_state):
|
||||
"""
|
||||
`f` is a function and `x0` and `y0` are two possible seeds.
|
||||
If `f` has SDIC then:
|
||||
there is a number `delta` such that for any `x0` there is a `y0` that is
|
||||
not more than `init_error` away from `x0`, where the initial condition `y0`
|
||||
has the property that there is some integer n such that after n iterations,
|
||||
the orbit is more than `delta` away from the orbit of `x0`. That is
|
||||
|xn-yn| > delta
|
||||
"""
|
||||
delta = 0.1
|
||||
n = 10000
|
||||
x0 = random_state.rand()
|
||||
x0_diffs = random_state.rand(100) * 0.001 - 0.0005
|
||||
|
||||
result_list = []
|
||||
for x0_diff in x0_diffs:
|
||||
x1 = x0 + x0_diff
|
||||
l_x = iterate_f(n, x0, 3.8)
|
||||
l_y = iterate_f(n, x1, 3.8)
|
||||
result_list.append(any(abs(l_x - l_y) > delta))
|
||||
assert any(result_list)
|
Loading…
Add table
Add a link
Reference in a new issue