Material for ASPP 2024

This commit is contained in:
Pietro Berkes 2024-08-27 15:52:41 +03:00
commit 849682b13b
97 changed files with 8170 additions and 0 deletions

30
.gitignore vendored Normal file
View file

@ -0,0 +1,30 @@
# OS specific
.DS_Store
# Editors
.vscode
.idea
*.swp
# Python
.venv/
build/
_build/
dist/
eggs/
.eggs/
sdist/
*.egg-info/
*.egg
*.pyc
__pycache__/
tests-reports/
.mypy_cache/
.pytest_cache/
.env
# Jupyter
.ipynb_checkpoints/
# Project
_archive/

6
LICENSE.txt Normal file
View file

@ -0,0 +1,6 @@
The material in this repository is released under the
CC Attribution-Share Alike 4.0 International
license.
Full license text available at
https://creativecommons.org/licenses/by-sa/4.0/

10
README.md Normal file
View file

@ -0,0 +1,10 @@
# Scientific programming patterns
Material for the class "Scientific programming patterns", first given as ASPP 2022 in Bilbao
# From this:
![](generate_assets/DallE/DALL%C2%B7E%202022-08-11%2020.28.02%20-%20An%20apocalyptic%20monster%20with%20many%20heads%20that%20is%20destroying%20a%20computer.%20Digital%20art.png)
# To this:
![](generate_assets/DallE/DALL%C2%B7E%202022-08-11%2020.29.56%20-%20A%20tame%20happy%20python%20cuddling%20with%20a%20happy%20scientist%20in%20front%20of%20a%20computer%3B%20Digital%20art.png)

View file

@ -0,0 +1,182 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2022-08-12T12:14:28.926189Z",
"start_time": "2022-08-12T12:14:28.923089Z"
},
"collapsed": true
},
"outputs": [],
"source": [
"import json\n",
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"ExecuteTime": {
"end_time": "2022-08-12T12:21:00.003395Z",
"start_time": "2022-08-12T12:20:59.997851Z"
},
"collapsed": false
},
"outputs": [],
"source": [
"dict_ = {'a': 3.1, 'b': 4.2}\n",
"with open('my_class.json', 'w') as f:\n",
" json.dump(dict_, f)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"ExecuteTime": {
"end_time": "2022-08-12T12:23:03.889266Z",
"start_time": "2022-08-12T12:23:03.883050Z"
},
"collapsed": true
},
"outputs": [],
"source": [
"class MyClass:\n",
" \n",
" def __init__(self, a, b):\n",
" \"\"\"The basic constructor takes 'raw' values.\"\"\"\n",
" self.a = a\n",
" self.b = b\n",
" \n",
" @classmethod\n",
" def from_random_values(cls, random_state=np.random):\n",
" \"\"\"Create a MyClass instance with random parameters.\"\"\"\n",
" a = random_state.rand()\n",
" b = random_state.randn()\n",
" return cls(a, b)\n",
" \n",
" @classmethod\n",
" def from_json(cls, json_fname):\n",
" \"\"\"Create a MyClass instance with parameters read form a json file.\"\"\"\n",
" with open(json_fname, 'r') as f:\n",
" dict_ = json.load(f)\n",
" a = dict_['a']\n",
" b = dict_['b']\n",
" return cls(a, b)\n",
"\n",
"my_class = MyClass.from_random_values()"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"ExecuteTime": {
"end_time": "2022-08-12T12:23:12.242599Z",
"start_time": "2022-08-12T12:23:12.237477Z"
},
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"{'a': 0.842940228048758, 'b': 0.2797222990193814}"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"my_class = MyClass.from_random_values()\n",
"my_class.__dict__"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"ExecuteTime": {
"end_time": "2022-08-12T12:23:44.439726Z",
"start_time": "2022-08-12T12:23:44.432540Z"
},
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"{'a': 3.1, 'b': 4.2}"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"my_class = MyClass.from_json('my_class.json')\n",
"my_class.__dict__"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
}
],
"metadata": {
"hide_input": false,
"kernelspec": {
"display_name": "Python [conda env:bog]",
"language": "python",
"name": "conda-env-bog-py"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.5"
},
"toc": {
"nav_menu": {
"height": "12px",
"width": "252px"
},
"navigate_menu": true,
"number_sections": true,
"sideBar": true,
"threshold": 4,
"toc_cell": false,
"toc_section_display": "block",
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View file

@ -0,0 +1,287 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"ExecuteTime": {
"end_time": "2022-09-02T11:13:12.648377Z",
"start_time": "2022-09-02T11:13:12.165387Z"
},
"collapsed": true
},
"outputs": [],
"source": [
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2022-09-02T11:14:10.246402Z",
"start_time": "2022-09-02T11:14:10.235135Z"
},
"collapsed": false
},
"outputs": [],
"source": [
"class Walker:\n",
" \"\"\" The Walker knows how to walk at random on a context map. \"\"\"\n",
"\n",
" def __init__(self, sigma_i, sigma_j, size, map_type='flat'):\n",
" self.sigma_i = sigma_i\n",
" self.sigma_j = sigma_j\n",
" self.size = size\n",
"\n",
" if map_type == 'flat':\n",
" context_map = np.ones((size, size))\n",
" elif map_type == 'hills':\n",
" grid_ii, grid_jj = np.mgrid[0:size, 0:size]\n",
" i_waves = np.sin(grid_ii / 130) + np.sin(grid_ii / 10)\n",
" i_waves /= i_waves.max()\n",
" j_waves = np.sin(grid_jj / 100) + np.sin(grid_jj / 50) + \\\n",
" np.sin(grid_jj / 10)\n",
" j_waves /= j_waves.max()\n",
" context_map = j_waves + i_waves\n",
" elif map_type == 'labyrinth':\n",
" context_map = np.ones((size, size))\n",
" context_map[50:100, 50:60] = 0\n",
" context_map[20:89, 80:90] = 0\n",
" context_map[90:120, 0:10] = 0\n",
" context_map[120:size, 30:40] = 0\n",
" context_map[180:190, 50:60] = 0\n",
"\n",
" context_map[50:60, 50:200] = 0\n",
" context_map[179:189, 80:130] = 0\n",
" context_map[110:120, 0:190] = 0\n",
" context_map[120:size, 30:40] = 0\n",
" context_map[180:190, 50:60] = 0\n",
" context_map /= context_map.sum()\n",
" self.context_map = context_map\n",
"\n",
" # Pre-compute a 2D grid of coordinates for efficiency\n",
" self._grid_ii, self._grid_jj = np.mgrid[0:size, 0:size]\n",
"\n",
"walker = Walker(sigma_i=3, sigma_j=4, size=200, map_type='hills')"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"ExecuteTime": {
"end_time": "2022-09-02T11:15:44.357965Z",
"start_time": "2022-09-02T11:15:44.351558Z"
},
"collapsed": false
},
"outputs": [],
"source": [
"class Walker:\n",
" \"\"\" The Walker knows how to walk at random on a context map. \"\"\"\n",
"\n",
" def __init__(self, sigma_i, sigma_j, size, context_map):\n",
" self.sigma_i = sigma_i\n",
" self.sigma_j = sigma_j\n",
" self.size = size\n",
" self.context_map = context_map\n",
" # Pre-compute a 2D grid of coordinates for efficiency\n",
" self._grid_ii, self._grid_jj = np.mgrid[0:size, 0:size]\n",
"\n",
" @classmethod\n",
" def from_context_map_type(cls, sigma_i, sigma_j, size, map_type):\n",
" \"\"\" Create an instance of Walker with a context map defined by type.\"\"\"\n",
" if map_type == 'flat':\n",
" context_map = np.ones((size, size))\n",
" elif map_type == 'hills':\n",
" grid_ii, grid_jj = np.mgrid[0:size, 0:size]\n",
" i_waves = np.sin(grid_ii / 130) + np.sin(grid_ii / 10)\n",
" i_waves /= i_waves.max()\n",
" j_waves = np.sin(grid_jj / 100) + np.sin(grid_jj / 50) +\\\n",
" np.sin(grid_jj / 10)\n",
" j_waves /= j_waves.max()\n",
" context_map = j_waves + i_waves\n",
" elif map_type == 'labyrinth':\n",
" context_map = np.ones((size, size))\n",
" context_map[50:100, 50:60] = 0\n",
" context_map[20:89, 80:90] = 0\n",
" context_map[90:120, 0:10] = 0\n",
" context_map[120:size, 30:40] = 0\n",
" context_map[180:190, 50:60] = 0\n",
"\n",
" context_map[50:60, 50:200] = 0\n",
" context_map[179:189, 80:130] = 0\n",
" context_map[110:120, 0:190] = 0\n",
" context_map[120:size, 30:40] = 0\n",
" context_map[180:190, 50:60] = 0\n",
"\n",
" context_map /= context_map.sum()\n",
" return cls(sigma_i, sigma_j, size, context_map)\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"ExecuteTime": {
"end_time": "2022-09-02T11:15:49.092194Z",
"start_time": "2022-09-02T11:15:49.086575Z"
},
"collapsed": true
},
"outputs": [],
"source": [
"walker = Walker.from_context_map_type(sigma_i=3, sigma_j=4, size=200, map_type='hills')"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"ExecuteTime": {
"end_time": "2022-09-02T11:19:37.723607Z",
"start_time": "2022-09-02T11:19:37.717518Z"
},
"collapsed": true
},
"outputs": [],
"source": [
"def flat_context_map_builder(size):\n",
" \"\"\" A context map where all positions are equally likely. \"\"\"\n",
" return np.ones((size, size))\n",
"\n",
"\n",
"def hills_context_map_builder(size):\n",
" \"\"\" A context map with bumps and valleys. \"\"\"\n",
" grid_ii, grid_jj = np.mgrid[0:size, 0:size]\n",
" i_waves = np.sin(grid_ii / 130) + np.sin(grid_ii / 10)\n",
" i_waves /= i_waves.max()\n",
" j_waves = np.sin(grid_jj / 100) + np.sin(grid_jj / 50) + \\\n",
" np.sin(grid_jj / 10)\n",
" j_waves /= j_waves.max()\n",
" context_map = j_waves + i_waves\n",
" return context_map\n",
"\n",
"\n",
"def labyrinth_context_map_builder(size):\n",
" \"\"\" A context map that looks like a labyrinth. \"\"\"\n",
" context_map = np.ones((size, size))\n",
" context_map[50:100, 50:60] = 0\n",
" context_map[20:89, 80:90] = 0\n",
" context_map[90:120, 0:10] = 0\n",
" context_map[120:size, 30:40] = 0\n",
" context_map[180:190, 50:60] = 0\n",
"\n",
" context_map[50:60, 50:200] = 0\n",
" context_map[179:189, 80:130] = 0\n",
" context_map[110:120, 0:190] = 0\n",
" context_map[120:size, 30:40] = 0\n",
" context_map[180:190, 50:60] = 0\n",
"\n",
" return context_map"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"ExecuteTime": {
"end_time": "2022-09-02T11:20:25.815725Z",
"start_time": "2022-09-02T11:20:25.811489Z"
},
"collapsed": true
},
"outputs": [],
"source": [
"class Walker:\n",
"\n",
" def __init__(self, sigma_i, sigma_j, size, context_map):\n",
" self.sigma_i = sigma_i\n",
" self.sigma_j = sigma_j\n",
" self.size = size\n",
" self.context_map = context_map\n",
" # Pre-compute a 2D grid of coordinates for efficiency\n",
" self._grid_ii, self._grid_jj = np.mgrid[0:size, 0:size]\n",
"\n",
" @classmethod\n",
" def from_context_map_builder(cls, sigma_i, sigma_j, size, context_map_builder):\n",
" \"\"\"Initialize the context map from an external builder.\n",
"\n",
" `builder` is a callable that takes a `size` as input parameter\n",
" and outputs a `size` x `size` numpy array of positive values.\n",
" \"\"\"\n",
" context_map = context_map_builder(size)\n",
" context_map /= context_map.sum()\n",
" return cls(sigma_i, sigma_j, size, context_map)\n"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"ExecuteTime": {
"end_time": "2022-09-02T11:20:26.367914Z",
"start_time": "2022-09-02T11:20:26.362287Z"
},
"collapsed": true
},
"outputs": [],
"source": [
"walker = Walker.from_context_map_builder(\n",
" sigma_i=3, \n",
" sigma_j=4, \n",
" size=200, \n",
" context_map_builder=hills_context_map_builder,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"a"
]
}
],
"metadata": {
"hide_input": false,
"kernelspec": {
"display_name": "Python [conda env:bog]",
"language": "python",
"name": "conda-env-bog-py"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.5"
},
"toc": {
"nav_menu": {
"height": "12px",
"width": "252px"
},
"navigate_menu": true,
"number_sections": true,
"sideBar": true,
"threshold": 4,
"toc_cell": false,
"toc_section_display": "block",
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View file

@ -0,0 +1,223 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "8ad9fe94",
"metadata": {},
"outputs": [],
"source": [
"class Walker:\n",
" # ...\n",
" \n",
" def sample_next_step(self, current_i, current_j, random_state=np.random):\n",
" \"\"\" Sample a new position for the walker. \"\"\"\n",
" # Combine the next-step proposal with the context map to get a\n",
" # next-step probability map\n",
" next_step_map = self._next_step_proposal(current_i, current_j)\n",
" selection_map = self._compute_next_step_probability(next_step_map)\n",
"\n",
" # Draw a new position from the next-step probability map\n",
" r = random_state.rand()\n",
" cumulative_map = np.cumsum(selection_map)\n",
" cumulative_map = cumulative_map.reshape(selection_map.shape)\n",
" i_next, j_next = np.argwhere(cumulative_map >= r)[0]\n",
"\n",
" return i_next, j_next\n",
"\n",
" def _next_step_proposal(self, current_i, current_j):\n",
" \"\"\" Create the 2D proposal map for the next step of the walker. \"\"\"\n",
" # 2D Gaussian distribution , centered at current position,\n",
" # and with different standard deviations for i and j\n",
" grid_ii, grid_jj = self._grid_ii, self._grid_jj\n",
" sigma_i, sigma_j = self.sigma_i, self.sigma_j\n",
"\n",
" rad = (\n",
" (((grid_ii - current_i) ** 2) / (sigma_i ** 2))\n",
" + (((grid_jj - current_j) ** 2) / (sigma_j ** 2))\n",
" )\n",
"\n",
" p_next_step = np.exp(-(rad / 2.0)) / (2.0 * np.pi * sigma_i * sigma_j)\n",
" return p_next_step / p_next_step.sum()\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "95148e45",
"metadata": {},
"outputs": [],
"source": [
"class Walker:\n",
" # ...\n",
"\n",
" def _next_step_proposal(self, current_i, current_j):\n",
" \"\"\" Create the 2D proposal map for the next step of the walker. \"\"\"\n",
" raise NotImplementedError(\"`_next_step_proposal` not implemented\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "da34226f",
"metadata": {},
"outputs": [],
"source": [
"class GaussianWalker(Walker):\n",
" # ...\n",
"\n",
" def _next_step_proposal(self, current_i, current_j):\n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
"class RectangularWalker(Walker):\n",
" # ...\n",
"\n",
" def _next_step_proposal(self, current_i, current_j):\n",
"\n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
"class JumpingWalker(Walker):\n",
" # ...\n",
"\n",
" def _next_step_proposal(self, current_i, current_j):\n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cfd90d16",
"metadata": {},
"outputs": [],
"source": [
"class Walker:\n",
" # ...\n",
"\n",
"\n",
" def _compute_next_step_probability(self, next_step_map):\n",
" \"\"\" Compute the next step probability map from next step proposal and\n",
" context map. \"\"\"\n",
" next_step_probability = next_step_map * self.context_map\n",
" next_step_probability /= next_step_probability.sum()\n",
" return next_step_probability\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "72041675",
"metadata": {},
"outputs": [],
"source": [
"class GaussianWalkerWithProductInteraction(Walker):\n",
" def _next_step_proposal(self, current_i, current_j):\n",
" # ...\n",
" def _compute_next_step_probability(self, next_step_map):\n",
" # ...\n",
"\n",
" \n",
"class GaussianWalkerWithSumInteraction(Walker):\n",
" def _next_step_proposal(self, current_i, current_j):\n",
" # ...\n",
" def _compute_next_step_probability(self, next_step_map):\n",
" # ...\n",
"\n",
" \n",
"class RectangularWalkerWithProductInteraction(Walker):\n",
" def _next_step_proposal(self, current_i, current_j):\n",
" # ...\n",
" def _compute_next_step_probability(self, next_step_map):\n",
" # ...\n",
"\n",
" \n",
"class RectangularWalkerWithSumInteraction(Walker):\n",
" def _next_step_proposal(self, current_i, current_j):\n",
" # ...\n",
" def _compute_next_step_probability(self, next_step_map):\n",
" # ...\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5ee2e200",
"metadata": {},
"outputs": [],
"source": [
"class Walker:\n",
" def __init__(self, size, context_map, next_step_proposal, next_step_proposal_arguments):\n",
" self.next_step_proposal = next_step_proposal\n",
" # ...\n",
"\n",
" \n",
" def sample_next_step(self, current_i, current_j, random_state=np.random):\n",
" \"\"\" Sample a new position for the walker. \"\"\"\n",
" # Combine the next-step proposal with the context map to get a\n",
" # next-step probability map\n",
" next_step_map = self.next_step_proposal(current_i, current_j, **next_step_proposal_arguments)\n",
" selection_map = self._compute_next_step_probability(next_step_map)\n",
"\n",
" # Draw a new position from the next-step probability map\n",
" r = random_state.rand()\n",
" cumulative_map = np.cumsum(selection_map)\n",
" cumulative_map = cumulative_map.reshape(selection_map.shape)\n",
" i_next, j_next = np.argwhere(cumulative_map >= r)[0]\n",
"\n",
" return i_next, j_next\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,105 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "2120045b",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"\n",
"\n",
"class Walker:\n",
" \"\"\" The Walker knows how to walk at random on a context map. \"\"\"\n",
"\n",
" def __init__(self, sigma_i, sigma_j, size, map_type='flat'):\n",
" # ...\n",
"\n",
" def plot_trajectory(self, trajectory):\n",
" \"\"\" Plot a trajectory over a context map. \"\"\"\n",
" trajectory = np.asarray(trajectory)\n",
" plt.matshow(self.context_map)\n",
" plt.plot(trajectory[:, 1], trajectory[:, 0], color='r')\n",
" plt.show()\n",
"\n",
" def plot_trajectory_hexbin(self, trajectory):\n",
" \"\"\" Plot an hexagonal density map of a trajectory. \"\"\"\n",
" trajectory = np.asarray(trajectory)\n",
" with plt.rc_context({'figure.figsize': (4, 4), \n",
" 'axes.labelsize': 16, \n",
" 'xtick.labelsize': 14, \n",
" 'ytick.labelsize': 14}):\n",
" plt.hexbin(\n",
" trajectory[:, 1], trajectory[:, 0], \n",
" gridsize=30, extent=(0, 200, 0, 200), \n",
" edgecolors='none', cmap='Reds'\n",
" )\n",
" plt.gca().invert_yaxis()\n",
" plt.xlabel('X')\n",
" plt.ylabel('Y')\n",
" \n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e8b1035c",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"\n",
"\n",
"class Walker:\n",
" \"\"\" The Walker knows how to walk at random on a context map. \"\"\"\n",
"\n",
" def __init__(self, sigma_i, sigma_j, size, map_type='flat'):\n",
" # ...\n",
"\n",
" def plot_trajectory(self, trajectory):\n",
" \"\"\" Plot a trajectory over a context map. \"\"\"\n",
" trajectory = np.asarray(trajectory)\n",
" plt.matshow(self.context_map)\n",
" plt.plot(trajectory[:, 1], trajectory[:, 0], color='r')\n",
" plt.show()\n",
"\n",
" def plot_trajectory_hexbin(self, trajectory):\n",
" \"\"\" Plot an hexagonal density map of a trajectory. \"\"\"\n",
" trajectory = np.asarray(trajectory)\n",
" plt.hexbin(\n",
" trajectory[:, 1], trajectory[:, 0], \n",
" gridsize=30, extent=(0, 200, 0, 200), \n",
" edgecolors='none', cmap='Reds'\n",
" )\n",
" plt.gca().invert_yaxis()\n",
" plt.xlabel('X')\n",
" plt.ylabel('Y')\n",
" \n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

File diff suppressed because one or more lines are too long

BIN
generate_assets/walk1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
generate_assets/walk2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
generate_assets/walk3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
generate_assets/walk4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
generate_assets/walk5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
generate_assets/walk6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
generate_assets/walk7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

590
notebooks/01a_Classes.ipynb Normal file
View file

@ -0,0 +1,590 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"# The smell of classes"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"## The \"data bundle\" smell"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"ExecuteTime": {
"end_time": "2018-07-27T15:05:51.531289Z",
"start_time": "2018-07-27T17:05:51.526519+02:00"
},
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"def momentum(mass, velocity):\n",
" return mass * velocity\n",
"\n",
"def energy(mass, velocity):\n",
" return 0.5 * mass * velocity ** 2\n",
"\n",
"def update_position(velocity, position, dt):\n",
" return position + velocity * dt"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"ExecuteTime": {
"end_time": "2018-07-27T15:05:51.905235Z",
"start_time": "2018-07-27T17:05:51.900153+02:00"
},
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"9.0\n",
"1.2000000000000002\n",
"1.0\n"
]
}
],
"source": [
"# Naive\n",
"mass1 = 10.0\n",
"velocity1 = 0.9\n",
"\n",
"mass2 = 12.0\n",
"velocity2 = 0.1\n",
"\n",
"print(momentum(mass1, velocity1))\n",
"print(momentum(mass2, velocity2))\n",
"print(momentum(mass1, velocity2)) # ??"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"We have two parameters that will be sent to these functions over and over again: `mass` and `velocity`.\n",
"\n",
"Moreover, the parameters cannot be mixed up (e.g. the velocity of one particle with the mass of another)."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2018-07-27T15:05:52.116795Z",
"start_time": "2018-07-27T17:05:52.112569+02:00"
},
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"9.0\n",
"1.2000000000000002\n"
]
}
],
"source": [
"masses = [10.0, 12.0]\n",
"velocities = [0.9, 0.1]\n",
"\n",
"print(momentum(masses[0], velocities[0]))\n",
"print(momentum(masses[1], velocities[1]))"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2018-07-27T15:05:52.548364Z",
"start_time": "2018-07-27T17:05:52.544726+02:00"
},
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"9.0\n",
"1.2000000000000002\n"
]
}
],
"source": [
"particle1 = {'mass': 10.0, 'velocity': 0.9}\n",
"particle2 = {'mass': 12.0, 'velocity': 0.1}\n",
"\n",
"print(momentum(particle1['mass'], particle1['velocity']))\n",
"print(momentum(particle2['mass'], particle2['velocity']))\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"All of the functions above can be rewritten as a function of this particle \"instance\", eliminating the bookkeeping for the individual parameters."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"ExecuteTime": {
"end_time": "2018-07-27T15:05:53.571400Z",
"start_time": "2018-07-27T17:05:53.567192+02:00"
},
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"9.0\n",
"1.2000000000000002\n"
]
}
],
"source": [
"def momentum(particle):\n",
" return particle['mass'] * particle['velocity']\n",
"\n",
"print(momentum(particle1))\n",
"print(momentum(particle2))\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"An annoying thing of this solution is that we have to remember the name of the keys in the dictionary, and the solution is sensitive to typos.\n",
"\n",
"To solve this, we could write a function to build a particle, a.k.a a \"constructor\""
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"ExecuteTime": {
"end_time": "2018-07-27T15:06:20.004037Z",
"start_time": "2018-07-27T17:06:19.998500+02:00"
},
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"9.0\n",
"1.2000000000000002\n"
]
}
],
"source": [
"def init_particle(mass, velocity):\n",
" self = {\n",
" 'mass': mass,\n",
" 'velocity': velocity\n",
" }\n",
" return self\n",
"\n",
"particle1 = init_particle(10.0, 0.9)\n",
"particle2 = init_particle(12.0, 0.1)\n",
"print(momentum(particle1))\n",
"print(momentum(particle2))\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"`particle1` and `particle2` are called \"instances\" of the particle \"class\".\n",
"\n",
"Python classes are a way to formalize this pattern: creating a bundle of data that belongs together. E.g. the parameters of an experiment, the results of a simulation, etc."
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"## Introducing classes as a data bundle template"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"ExecuteTime": {
"end_time": "2018-08-04T13:04:49.208543Z",
"start_time": "2018-08-04T15:04:49.203180+02:00"
},
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"class Particle:\n",
" def __init__(self, mass, velocity):\n",
" self.mass = mass\n",
" self.velocity = velocity\n",
"\n",
"particle1 = Particle(10.0, 0.9)\n",
"particle2 = Particle(12.0, 0.1)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"ExecuteTime": {
"end_time": "2018-07-27T15:07:09.818544Z",
"start_time": "2018-07-27T17:07:09.814535+02:00"
},
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [
{
"data": {
"text/plain": [
"0.9"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"particle1.velocity"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"ExecuteTime": {
"end_time": "2018-07-27T15:07:09.997264Z",
"start_time": "2018-07-27T17:07:09.994114+02:00"
},
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [
{
"data": {
"text/plain": [
"12.0"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"particle2.mass"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"ExecuteTime": {
"end_time": "2018-07-27T15:07:11.559629Z",
"start_time": "2018-07-27T17:07:11.555632+02:00"
},
"pycharm": {
"name": "#%%\n"
},
"scrolled": true
},
"outputs": [
{
"data": {
"text/plain": [
"{'mass': 10.0, 'velocity': 0.9}"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"particle1.__dict__"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"## Class methods"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"ExecuteTime": {
"end_time": "2018-07-27T12:13:56.972938Z",
"start_time": "2018-07-27T14:13:56.969323+02:00"
},
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"9.0\n",
"1.2000000000000002\n"
]
}
],
"source": [
"def momentum(particle):\n",
" return particle.mass * particle.velocity\n",
"\n",
"print(momentum(particle1))\n",
"print(momentum(particle2))"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"9.0\n"
]
}
],
"source": [
"class Particle:\n",
" def __init__(self, mass, velocity):\n",
" self.mass = mass\n",
" self.velocity = velocity\n",
"\n",
" def momentum(self):\n",
" return self.mass * self.velocity\n",
"\n",
"particle1 = Particle(10.0, 0.9)\n",
"print(particle1.momentum())"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"We have been using class instances and methods all along..."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'A scanner darkly'"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"s = 'A scanner Darkly'\n",
"s.capitalize()"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [
{
"data": {
"text/plain": [
"{'apple', 'banana', 'pineapple'}"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"x = set(['apple', 'banana', 'apple', 'pineapple'])\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [
{
"data": {
"text/plain": [
"{'apple', 'banana', 'kiwi', 'pineapple'}"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"x.union(['banana', 'kiwi'])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": []
}
],
"metadata": {
"hide_input": false,
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.3"
},
"toc": {
"nav_menu": {
"height": "174px",
"width": "252px"
},
"navigate_menu": true,
"number_sections": true,
"sideBar": true,
"threshold": 4,
"toc_cell": false,
"toc_position": {
"height": "953px",
"left": "0px",
"right": "1253px",
"top": "127px",
"width": "320px"
},
"toc_section_display": "block",
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View file

@ -0,0 +1,279 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# The smell of classes"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## The \"data bundle\" smell"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"ExecuteTime": {
"end_time": "2021-08-11T12:50:34.342224Z",
"start_time": "2021-08-11T14:50:34.336560+02:00"
}
},
"outputs": [],
"source": [
"def momentum(mass, velocity):\n",
" return mass * velocity\n",
"\n",
"def energy(mass, velocity):\n",
" return 0.5 * mass * velocity ** 2\n",
"\n",
"def update_position(velocity, position, dt):\n",
" return position + velocity * dt"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"ExecuteTime": {
"end_time": "2021-08-11T12:50:34.776689Z",
"start_time": "2021-08-11T14:50:34.769617+02:00"
}
},
"outputs": [],
"source": [
"# Naive\n",
"mass1 = 10.0\n",
"velocity1 = 0.9\n",
"\n",
"mass2 = 12.0\n",
"velocity2 = 0.1\n",
"\n",
"print(momentum(mass1, velocity1))\n",
"print(momentum(mass2, velocity2))"
]
},