Testing Class Material
This commit is contained in:
commit
05b1f6cdd5
85 changed files with 102796 additions and 0 deletions
8
hands_on_solutions/first/first.py
Normal file
8
hands_on_solutions/first/first.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
def times_3(x):
|
||||
""" Multiply x by 3.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : The item to multiply by 3.
|
||||
"""
|
||||
return x * 3
|
28
hands_on_solutions/first/test_first.py
Normal file
28
hands_on_solutions/first/test_first.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
from first import times_3
|
||||
|
||||
|
||||
def test_times_3_integer():
|
||||
value = 7
|
||||
expected = 21
|
||||
|
||||
result = times_3(value)
|
||||
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_times_3_string():
|
||||
value = 'wow'
|
||||
expected = 'wowwowwow'
|
||||
|
||||
result = times_3(value)
|
||||
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_times_3_list():
|
||||
value = [1]
|
||||
expected = [1, 1, 1]
|
||||
|
||||
result = times_3(value)
|
||||
|
||||
assert result == expected
|
44
hands_on_solutions/local_maxima/local_maxima.py
Normal file
44
hands_on_solutions/local_maxima/local_maxima.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
def find_maxima(x):
|
||||
"""Find local maxima of x.
|
||||
|
||||
Example:
|
||||
>>> x = [1, 3, -2, 0, 2, 1]
|
||||
>>> find_maxima(x)
|
||||
[1, 4]
|
||||
|
||||
If in a local maximum several elements have the same value,
|
||||
return the left-most index.
|
||||
Example:
|
||||
>>> x = [1, 2, 2, 1]
|
||||
>>> find_maxima(x)
|
||||
[1]
|
||||
|
||||
Input arguments:
|
||||
x -- 1D list of real numbers
|
||||
|
||||
Output:
|
||||
idx -- list of indices of the local maxima in x
|
||||
"""
|
||||
|
||||
idx = []
|
||||
up = False
|
||||
down = False
|
||||
for i in range(len(x)):
|
||||
if i == 0 or x[i-1] < x[i]:
|
||||
up = True
|
||||
up_idx = i
|
||||
elif x[i-1] > x[i]:
|
||||
up = False
|
||||
|
||||
# if x[i-1] == x[i], no change
|
||||
|
||||
if i+1 == len(x) or x[i+1] < x[i]:
|
||||
down = True
|
||||
elif x[i+1] > x[i]:
|
||||
down = False
|
||||
|
||||
if up and down:
|
||||
idx.append(up_idx)
|
||||
|
||||
return idx
|
||||
|
36
hands_on_solutions/local_maxima/test_local_maxima.py
Normal file
36
hands_on_solutions/local_maxima/test_local_maxima.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
from local_maxima import find_maxima
|
||||
|
||||
|
||||
def test_find_maxima():
|
||||
values = [1, 3, -2, 0, 2, 1]
|
||||
expected = [1, 4]
|
||||
maxima = find_maxima(values)
|
||||
assert maxima == expected
|
||||
|
||||
|
||||
def test_find_maxima_edges():
|
||||
values = [4, 2, 1, 3, 1, 5]
|
||||
expected = [0, 3, 5]
|
||||
maxima = find_maxima(values)
|
||||
assert maxima == expected
|
||||
|
||||
|
||||
def test_find_maxima_empty():
|
||||
values = []
|
||||
expected = []
|
||||
maxima = find_maxima(values)
|
||||
assert maxima == expected
|
||||
|
||||
|
||||
def test_find_maxima_plateau():
|
||||
values = [1, 2, 2, 1]
|
||||
expected = [1]
|
||||
maxima = find_maxima(values)
|
||||
assert maxima == expected
|
||||
|
||||
|
||||
def test_find_maxima_not_a_plateau():
|
||||
values = [1, 2, 2, 3, 1]
|
||||
expected = [3]
|
||||
maxima = find_maxima(values)
|
||||
assert maxima == expected
|
|
@ -0,0 +1,67 @@
|
|||
import math
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def find_maxima(x):
|
||||
"""Find local maxima of x.
|
||||
|
||||
Input arguments:
|
||||
x -- 1D list of real numbers
|
||||
|
||||
Output:
|
||||
idx -- list of indices of the local maxima in x
|
||||
"""
|
||||
maxima = []
|
||||
len_x = len(x)
|
||||
if len_x == 0:
|
||||
return maxima
|
||||
elif len_x == 1:
|
||||
return [0]
|
||||
|
||||
# additional checks
|
||||
if np.all([isinstance(item, str) for item in x]):
|
||||
x = [item.lower() for item in x]
|
||||
if np.all([item == x[0] for item in x]):
|
||||
return [0]
|
||||
|
||||
maxima = check_first_element(x, maxima)
|
||||
# Check numbers in between
|
||||
i = 1
|
||||
while i < len_x - 1:
|
||||
if x[i] >= x[i - 1]:
|
||||
# We have found a potential maximum or start of a plateau
|
||||
# breakpoint()
|
||||
if i == 1 and x[i] == x[i - 1]:
|
||||
plateau_start = i - 1
|
||||
else:
|
||||
plateau_start = i
|
||||
while i < len_x - 1 and x[i] == x[i + 1]:
|
||||
i += 1
|
||||
plateau_end = i
|
||||
if plateau_end == len_x - 1:
|
||||
maxima.append((plateau_end + plateau_start) // 2)
|
||||
elif x[plateau_end] > x[plateau_end + 1]:
|
||||
maxima.append((plateau_end + plateau_start) // 2)
|
||||
i += 1
|
||||
maxima = check_last_element(x, maxima)
|
||||
|
||||
return maxima
|
||||
|
||||
|
||||
def check_first_element(x, maxima):
|
||||
if x[0] > x[1]:
|
||||
maxima.append(0)
|
||||
return maxima
|
||||
|
||||
|
||||
def check_last_element(x, maxima):
|
||||
if x[-1] > x[-2]:
|
||||
maxima.append(len(x) - 1)
|
||||
return maxima
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# result = find_maxima([1, 3, -2, 0, 2, 1])
|
||||
result = find_maxima([1, 2, 2, 1])
|
||||
print(result)
|
|
@ -0,0 +1,97 @@
|
|||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from local_maxima_solution import find_maxima
|
||||
|
||||
|
||||
def test_find_maxima():
|
||||
values = [1, 3, -2, 0, 2, 1]
|
||||
expected = [1, 4]
|
||||
maxima = find_maxima(values)
|
||||
assert maxima == expected
|
||||
|
||||
|
||||
def test_find_maxima_edges():
|
||||
values = [4, 2, 1, 0, 1, 5]
|
||||
expected = [0, 5]
|
||||
maxima = find_maxima(values)
|
||||
assert maxima == expected
|
||||
|
||||
|
||||
def test_find_maxima_empty():
|
||||
values = []
|
||||
expected = []
|
||||
maxima = find_maxima(values)
|
||||
assert maxima == expected
|
||||
|
||||
|
||||
def test_find_maxima_plateau():
|
||||
values = [1, 2, 2, 1]
|
||||
expected = [1]
|
||||
maxima = find_maxima(values)
|
||||
assert maxima == expected
|
||||
|
||||
|
||||
def test_find_maxima_not_a_plateau():
|
||||
values = [1, 2, 2, 3, 1]
|
||||
expected = [3]
|
||||
maxima = find_maxima(values)
|
||||
assert maxima == expected
|
||||
|
||||
|
||||
# the tests below here fail, can you get them to pass?
|
||||
|
||||
|
||||
def test_find_maxima_correct_order():
|
||||
values = [2, 1, 5, 1, 9]
|
||||
expected = [0, 2, 4]
|
||||
maxima = find_maxima(values)
|
||||
assert maxima == expected
|
||||
|
||||
|
||||
def test_find_maxima_one_value():
|
||||
values = [1]
|
||||
expected = [0]
|
||||
maxima = find_maxima(values)
|
||||
assert maxima == expected
|
||||
|
||||
|
||||
def test_find_maxima_long_plateau():
|
||||
values = [1, 2, 2, 2, 2, 2, 1, 8, 0]
|
||||
expected = [3, 7]
|
||||
maxima = find_maxima(values)
|
||||
assert maxima == expected
|
||||
|
||||
|
||||
def test_find_maxima_plateau_at_end():
|
||||
values = [1, 2, 2]
|
||||
expected = [1]
|
||||
maxima = find_maxima(values)
|
||||
assert maxima == expected
|
||||
|
||||
|
||||
def test_find_maxima_plateau_at_start():
|
||||
values = [1, 1, 0, 0]
|
||||
expected = [0]
|
||||
maxima = find_maxima(values)
|
||||
assert maxima == expected
|
||||
|
||||
|
||||
def test_find_maxima_all_same_values():
|
||||
values = [1, 1]
|
||||
expected = [0]
|
||||
maxima = find_maxima(values)
|
||||
assert maxima == expected
|
||||
|
||||
|
||||
def test_find_maxima_letters():
|
||||
values = ["T", "e", "s", "t", "s", "!"]
|
||||
expected = [0, 3]
|
||||
maxima = find_maxima(values)
|
||||
assert maxima == expected
|
||||
|
||||
|
||||
def test_find_maxima_new_inputs_to_make_current_function_fail():
|
||||
# should you actually be done with all tests, then you can think of other cases where the current function fails
|
||||
# and write tests for them and fix them
|
||||
assert True
|
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)
|
408
hands_on_solutions/massmail_solution/massmail
Normal file
408
hands_on_solutions/massmail_solution/massmail
Normal file
|
@ -0,0 +1,408 @@
|
|||
#!/usr/bin/env python
|
||||
# Script to send mass email
|
||||
#
|
||||
# Copyright (C) 2003-2017 Tiziano Zito <opossumnano@gmail.com>, Jakob Jordan <jakobjordan@posteo.de>
|
||||
#
|
||||
# This script is free software and comes without any warranty, to
|
||||
# the extent permitted by applicable law. You can redistribute it
|
||||
# and/or modify it under the terms of the Do What The Fuck You Want
|
||||
# To Public License, Version 2, as published by Sam Hocevar.
|
||||
# http://www.wtfpl.net
|
||||
#
|
||||
# Full license text:
|
||||
#
|
||||
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
# Version 2, December 2004.
|
||||
#
|
||||
# Everyone is permitted to copy and distribute verbatim or modified
|
||||
# copies of this license document, and changing it is allowed as long
|
||||
# as the name is changed.
|
||||
#
|
||||
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
#
|
||||
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
import smtplib, getopt, sys, os, email, getpass
|
||||
import email.header
|
||||
import email.mime.text
|
||||
import tempfile
|
||||
|
||||
from py.test import raises
|
||||
|
||||
|
||||
PROGNAME = os.path.basename(sys.argv[0])
|
||||
USAGE = """Send mass mail
|
||||
Usage:
|
||||
%s [...] PARAMETER_FILE < BODY
|
||||
|
||||
Options:
|
||||
-F FROM set the From: header for all messages.
|
||||
Must be ASCII. This argument is required
|
||||
|
||||
-S SUBJECT set the Subject: header for all messages
|
||||
|
||||
-B BCC set the Bcc: header for all messages.
|
||||
Must be ASCII
|
||||
|
||||
-s SEPARATOR set field separator in parameter file,
|
||||
default: ";"
|
||||
|
||||
-e ENCODING set PARAMETER_FILE *and* BODY character set
|
||||
encoding, default: "UTF-8". Note that if you fuck
|
||||
up this one, your email will be full of rubbish:
|
||||
You have been warned!
|
||||
|
||||
-f fake run: don't really send emails, just print to
|
||||
standard output what would be done. Don't be scared
|
||||
if you can not read the body: it is base64 encoded
|
||||
UTF8 text
|
||||
|
||||
-z SERVER the SMTP server to use. This argument is required
|
||||
|
||||
-P PORT the SMTP port to use.
|
||||
|
||||
-u SMTP user name. If not set, use anonymous SMTP
|
||||
connection
|
||||
|
||||
-p SMTP password. If not set you will be prompted for one
|
||||
|
||||
-h show this usage message
|
||||
|
||||
Notes:
|
||||
The message body is read from standard input or
|
||||
typed in interactively (exit with ^D) and keywords are subsituted with values
|
||||
read from a parameter file. The first line of the parameter file defines
|
||||
the keywords. The keyword $EMAIL$ must always be present and contains a comma
|
||||
separated list of email addresses.
|
||||
Keep in mind shell escaping when setting headers with white spaces or special
|
||||
characters.
|
||||
Character set encodings are those supported by python.
|
||||
|
||||
Examples:
|
||||
|
||||
* Example of a parameter file:
|
||||
|
||||
$NAME$; $SURNAME$; $EMAIL$
|
||||
John; Smith; j@guys.com
|
||||
Anne; Joyce; a@girls.com
|
||||
|
||||
* Example of body:
|
||||
|
||||
Dear $NAME$ $SURNAME$,
|
||||
|
||||
I think you are a great guy/girl!
|
||||
|
||||
Cheers,
|
||||
|
||||
My self.
|
||||
|
||||
* Example usage:
|
||||
|
||||
%s -F "Great Guy <gg@guys.com>" -S "You are a great guy" -B "Not so great Guy <ngg@guys.com>" parameter-file < body
|
||||
|
||||
"""%(PROGNAME, PROGNAME)
|
||||
|
||||
def error(s):
|
||||
sys.stderr.write(PROGNAME+': ')
|
||||
sys.stderr.write(s+'\n')
|
||||
sys.stderr.flush()
|
||||
sys.exit(-1)
|
||||
|
||||
def parse_command_line_options(arguments):
|
||||
# parse options
|
||||
try:
|
||||
opts, args = getopt.getopt(arguments, "hfs:F:S:B:R:e:u:p:P:z:")
|
||||
except getopt.GetoptError, err:
|
||||
error(str(err)+USAGE)
|
||||
|
||||
# set default options
|
||||
options = {
|
||||
'sep': u';',
|
||||
'fake': False,
|
||||
'from': '',
|
||||
'subject': '',
|
||||
'bcc': '',
|
||||
'encoding': 'utf-8',
|
||||
'smtpuser': None,
|
||||
'smtppassword': None,
|
||||
'server': None,
|
||||
'port': 0,
|
||||
'in_reply_to': '',
|
||||
}
|
||||
|
||||
for option, value in opts:
|
||||
if option == "-e":
|
||||
options['encoding'] = value
|
||||
if option == "-s":
|
||||
options['sep'] = value
|
||||
elif option == "-F":
|
||||
options['from'] = value
|
||||
elif option == "-S":
|
||||
options['subject'] = value
|
||||
elif option == "-B":
|
||||
options['bcc'] = value
|
||||
elif option == "-R":
|
||||
options['in_reply_to'] = value
|
||||
elif option == "-h":
|
||||
error(USAGE)
|
||||
elif option == "-f":
|
||||
options['fake'] = True
|
||||
elif option == "-u":
|
||||
options['smtpuser'] = value
|
||||
elif option == "-p":
|
||||
options['smtppassword'] = value
|
||||
elif option == "-P":
|
||||
options['port'] = int(value)
|
||||
elif option == "-z":
|
||||
options['server'] = value
|
||||
|
||||
if len(args) == 0:
|
||||
error('You must specify a parameter file')
|
||||
|
||||
if len(options['from']) == 0:
|
||||
error('You must set a from address with option -F')
|
||||
|
||||
if options['server'] is None:
|
||||
error('You must set a SMTP server with option -z')
|
||||
|
||||
if options['sep'] == ",":
|
||||
error('Separator can not be a comma')
|
||||
|
||||
# get password if needed
|
||||
if options['smtpuser'] is not None and options['smtppassword'] is None:
|
||||
options['smtppassword'] = getpass.getpass('Enter password for %s: '%options['smtpuser'])
|
||||
|
||||
# set filenames of parameter and mail body
|
||||
options['fn_parameters'] = args[0]
|
||||
|
||||
return options
|
||||
|
||||
def parse_parameter_file(options):
|
||||
pars_fh = open(options['fn_parameters'], 'rbU')
|
||||
pars = pars_fh.read()
|
||||
pars_fh.close()
|
||||
|
||||
try:
|
||||
pars = unicode(pars, encoding=options['encoding'])
|
||||
except UnicodeDecodeError, e:
|
||||
error('Error decoding "'+options['fn_parameters']+'": '+str(e))
|
||||
|
||||
try:
|
||||
options['subject'] = unicode(options['subject'], encoding=options['encoding'])
|
||||
except UnicodeDecodeError, e:
|
||||
error('Error decoding SUBJECT: '+str(e))
|
||||
|
||||
try:
|
||||
options['from'] = unicode(options['from'], encoding=options['encoding'])
|
||||
except UnicodeDecodeError, e:
|
||||
error('Error decoding FROM: '+str(e))
|
||||
|
||||
if options['in_reply_to'] and not options['in_reply_to'].startswith('<'):
|
||||
options['in_reply_to'] = '<{}>'.format(options['in_reply_to'])
|
||||
|
||||
# split lines
|
||||
pars = pars.splitlines()
|
||||
|
||||
# get keywords from first line
|
||||
keywords_list = [key.strip() for key in pars[0].split(options['sep'])]
|
||||
|
||||
# fail immediately if no EMAIL keyword is found
|
||||
if '$EMAIL$' not in keywords_list:
|
||||
error('No $EMAIL$ keyword found in %s'%options['fn_parameters'])
|
||||
|
||||
# check that all keywords start and finish with a '$' character
|
||||
for key in keywords_list:
|
||||
if not key.startswith('$') or not key.endswith('$'):
|
||||
error(('Keyword "%s" malformed: should be $KEYWORD$'%key).encode(options['encoding']))
|
||||
|
||||
# gather all values
|
||||
email_count = 0
|
||||
keywords = dict([(key, []) for key in keywords_list])
|
||||
for count, line in enumerate(pars[1:]):
|
||||
# ignore empty lines
|
||||
if len(line) == 0:
|
||||
continue
|
||||
values = [key.strip() for key in line.split(options['sep'])]
|
||||
if len(values) != len(keywords_list):
|
||||
error(('Line %d in "%s" malformed: %d values found instead of'
|
||||
' %d: %s'%(count+1,options['fn_parameters'],len(values),len(keywords_list),line)).encode(options['encoding']))
|
||||
for i, key in enumerate(keywords_list):
|
||||
keywords[key].append(values[i])
|
||||
email_count += 1
|
||||
|
||||
return keywords, email_count
|
||||
|
||||
def create_email_bodies(options, keywords, email_count, body):
|
||||
try:
|
||||
body = unicode(body, encoding=options['encoding'])
|
||||
except UnicodeDecodeError, e:
|
||||
error('Error decoding email body: '+str(e))
|
||||
|
||||
# find keywords and substitute with values
|
||||
# we need to create email_count bodies
|
||||
msgs = {}
|
||||
|
||||
for i in range(email_count):
|
||||
lbody = body
|
||||
for key in keywords:
|
||||
lbody = lbody.replace(key, keywords[key][i])
|
||||
|
||||
# Any single dollar left? That means that the body was malformed
|
||||
single_dollar_exists = lbody.count('$') != 2 * lbody.count('$$')
|
||||
if single_dollar_exists:
|
||||
raise ValueError('Malformed email body: unclosed placeholder or non-escaped dollar sign.')
|
||||
|
||||
# Replace double dollars with single dollars
|
||||
lbody = lbody.replace('$$', '$')
|
||||
|
||||
# encode body
|
||||
lbody = email.mime.text.MIMEText(lbody.encode(options['encoding']), 'plain', options['encoding'])
|
||||
msgs[keywords['$EMAIL$'][i]] = lbody
|
||||
|
||||
return msgs
|
||||
|
||||
def add_email_headers(options, msgs):
|
||||
# msgs is now a dictionary with {emailaddr:body}
|
||||
# we need to add the headers
|
||||
|
||||
for emailaddr in msgs:
|
||||
msg = msgs[emailaddr]
|
||||
msg['To'] = str(emailaddr)
|
||||
msg['From'] = email.header.Header(options['from'])
|
||||
if options['subject']:
|
||||
msg['Subject'] = email.header.Header(options['subject'].encode(options['encoding']), options['encoding'])
|
||||
if options['in_reply_to']:
|
||||
msg['In-Reply-To'] = email.header.Header(options['in_reply_to'])
|
||||
msgs[emailaddr] = msg
|
||||
|
||||
return None
|
||||
|
||||
def send_messages(options, msgs):
|
||||
server = smtplib.SMTP(options['server'], port=options['port'])
|
||||
|
||||
if options['smtpuser'] is not None:
|
||||
server.starttls()
|
||||
server.login(options['smtpuser'], options['smtppassword'])
|
||||
|
||||
for emailaddr in msgs:
|
||||
print 'Sending email to:', emailaddr
|
||||
emails = [e.strip() for e in emailaddr.split(',')]
|
||||
if len(options['bcc']) > 0:
|
||||
emails.append(options['bcc'])
|
||||
if options['fake']:
|
||||
print msgs[emailaddr].as_string()
|
||||
else:
|
||||
try:
|
||||
out = server.sendmail(options['from'], emails, msgs[emailaddr].as_string())
|
||||
except Exception, err:
|
||||
error(str(err))
|
||||
|
||||
if len(out) != 0:
|
||||
error(str(out))
|
||||
|
||||
server.close()
|
||||
|
||||
def test_dummy():
|
||||
pass
|
||||
|
||||
def test_parse_parameter_file():
|
||||
expected_keywords = {u'$VALUE$': [u'this is a test'], u'$EMAIL$': [u'testrecv@test']}
|
||||
with tempfile.NamedTemporaryFile() as f:
|
||||
f.write('$EMAIL$;$VALUE$\ntestrecv@test;this is a test')
|
||||
f.flush()
|
||||
cmd_options = [
|
||||
'-F', 'testfrom@test',
|
||||
'-z', 'localhost',
|
||||
f.name,
|
||||
]
|
||||
options = parse_command_line_options(cmd_options)
|
||||
keywords, email_count = parse_parameter_file(options)
|
||||
assert keywords == expected_keywords
|
||||
|
||||
def test_local_sending():
|
||||
parameter_string = '$EMAIL$;$NAME$;$VALUE$\ntestrecv@test.org;TestName;531'
|
||||
email_body = 'Dear $NAME$,\nthis is a test: $VALUE$\nBest regards'
|
||||
email_to = 'testrecv@test.org'
|
||||
email_from = 'testfrom@test.org'
|
||||
email_subject = 'Test Subject'
|
||||
email_encoding = 'utf-8'
|
||||
|
||||
expected_email = email.mime.text.MIMEText('Dear TestName,\nthis is a test: 531\nBest regards'.encode(email_encoding), 'plain', email_encoding)
|
||||
expected_email['To'] = email_to
|
||||
expected_email['From'] = email_from
|
||||
expected_email['Subject'] = email.header.Header(email_subject.encode(email_encoding), email_encoding)
|
||||
|
||||
with tempfile.NamedTemporaryFile() as f:
|
||||
f.write(parameter_string)
|
||||
f.flush()
|
||||
cmd_options = [
|
||||
'-F', email_from,
|
||||
'-S', email_subject,
|
||||
'-z', 'localhost',
|
||||
'-e', email_encoding,
|
||||
f.name
|
||||
]
|
||||
options = parse_command_line_options(cmd_options)
|
||||
keywords, email_count = parse_parameter_file(options)
|
||||
msgs = create_email_bodies(options, keywords, email_count, email_body)
|
||||
add_email_headers(options, msgs)
|
||||
assert msgs['testrecv@test.org'].as_string() == expected_email.as_string()
|
||||
|
||||
def test_malformed_body():
|
||||
parameter_string = '$EMAIL$;$NAME$;$VALUE$\ntestrecv@test.org;TestName;531'
|
||||
email_body = '$NAME$VALUE$'
|
||||
email_from = 'testfrom@test.org'
|
||||
email_subject = 'Test Subject'
|
||||
email_encoding = 'utf-8'
|
||||
|
||||
with tempfile.NamedTemporaryFile() as f:
|
||||
f.write(parameter_string)
|
||||
f.flush()
|
||||
cmd_options = [
|
||||
'-F', email_from,
|
||||
'-S', email_subject,
|
||||
'-z', 'localhost',
|
||||
'-e', email_encoding,
|
||||
f.name
|
||||
]
|
||||
options = parse_command_line_options(cmd_options)
|
||||
keywords, email_count = parse_parameter_file(options)
|
||||
with raises(ValueError):
|
||||
msgs = create_email_bodies(options, keywords, email_count, email_body)
|
||||
|
||||
def test_double_dollar():
|
||||
parameter_string = '$EMAIL$;$NAME$;$VALUE$\ntestrecv@test.org;TestName;531'
|
||||
email_body = 'Dear $NAME$,\nyou owe us 254$$'
|
||||
email_to = 'testrecv@test.org'
|
||||
email_from = 'testfrom@test.org'
|
||||
email_subject = 'Test Subject'
|
||||
email_encoding = 'utf-8'
|
||||
|
||||
expected_email = email.mime.text.MIMEText('Dear TestName,\nyou owe us 254$'.encode(email_encoding), 'plain', email_encoding)
|
||||
expected_email['To'] = email_to
|
||||
expected_email['From'] = email_from
|
||||
expected_email['Subject'] = email.header.Header(email_subject.encode(email_encoding), email_encoding)
|
||||
|
||||
with tempfile.NamedTemporaryFile() as f:
|
||||
f.write(parameter_string)
|
||||
f.flush()
|
||||
cmd_options = [
|
||||
'-F', email_from,
|
||||
'-S', email_subject,
|
||||
'-z', 'localhost',
|
||||
'-e', email_encoding,
|
||||
f.name
|
||||
]
|
||||
options = parse_command_line_options(cmd_options)
|
||||
keywords, email_count = parse_parameter_file(options)
|
||||
msgs = create_email_bodies(options, keywords, email_count, email_body)
|
||||
assert msgs['testrecv@test.org'].get_payload(decode=email_encoding) == expected_email.get_payload(decode=email_encoding)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
options = parse_command_line_options(sys.argv[1:])
|
||||
keywords, email_count = parse_parameter_file(options)
|
||||
msgs = create_email_bodies(options, keywords, email_count, sys.stdin.read())
|
||||
add_email_headers(options, msgs)
|
||||
send_messages(options, msgs)
|
22
hands_on_solutions/numerical_fuzzing/test_var.py
Normal file
22
hands_on_solutions/numerical_fuzzing/test_var.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from math import isclose
|
||||
|
||||
import numpy
|
||||
|
||||
|
||||
def test_var_deterministic():
|
||||
x = numpy.array([-2.0, 2.0])
|
||||
expected = 4.0
|
||||
assert isclose(numpy.var(x), expected)
|
||||
|
||||
|
||||
def test_var_fuzzing():
|
||||
rand_state = numpy.random.RandomState(8393)
|
||||
|
||||
N, D = 100000, 5
|
||||
# Goal variances: [0.1 , 0.45, 0.8 , 1.15, 1.5]
|
||||
expected = numpy.linspace(0.1, 1.5, D)
|
||||
|
||||
# Generate random, D-dimensional data
|
||||
x = rand_state.randn(N, D) * numpy.sqrt(expected)
|
||||
variance = numpy.var(x, axis=0)
|
||||
numpy.testing.assert_allclose(variance, expected, rtol=1e-2)
|
23
hands_on_solutions/numpy_equality/test_numpy_equality.py
Normal file
23
hands_on_solutions/numpy_equality/test_numpy_equality.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
import numpy as np
|
||||
from numpy.testing import assert_allclose, assert_equal
|
||||
|
||||
|
||||
def test_equality():
|
||||
x = np.array([1, 1])
|
||||
y = np.array([2, 2])
|
||||
z = np.array([3, 3])
|
||||
assert_equal(x + y, z)
|
||||
|
||||
|
||||
def test_equality_with_nan():
|
||||
x = np.array([1, np.nan])
|
||||
y = np.array([2, np.nan])
|
||||
z = np.array([3, np.nan])
|
||||
assert_equal(x + y, z)
|
||||
|
||||
|
||||
def test_allclose_with_nan():
|
||||
x = np.array([1.1, np.nan])
|
||||
y = np.array([2.2, np.nan])
|
||||
z = np.array([3.3, np.nan])
|
||||
assert_allclose(x + y, z)
|
Loading…
Add table
Add a link
Reference in a new issue