From 1773c305b162723e16d3d21b14772fe55e4b3797 Mon Sep 17 00:00:00 2001 From: zingale Date: Sat, 14 Feb 2026 00:07:21 +0000 Subject: [PATCH] Update documentation --- .buildinfo | 4 + .nojekyll | 0 01-python/basics.html | 673 +++++ 01-python/functions-classes.html | 607 ++++ 01-python/installing.html | 658 +++++ 01-python/misc.html | 605 ++++ 01-python/python-io.html | 952 +++++++ 01-python/python.html | 583 ++++ 01-python/using.html | 668 +++++ 01-python/w1-jupyter.html | 726 +++++ 01-python/w1-python-datatypes.html | 1355 +++++++++ 01-python/w2-python-advanced-datatypes.html | 1130 ++++++++ 01-python/w2-python-control-flow.html | 933 +++++++ 01-python/w2-python-exercises.html | 1507 ++++++++++ 01-python/w3-python-exceptions.html | 744 +++++ 01-python/w3-python-exercises.html | 983 +++++++ 01-python/w3-python-functions.html | 990 +++++++ 01-python/w4-python-classes.html | 1305 +++++++++ 01-python/w4-python-exercises.html | 1035 +++++++ 01-python/w4-python-modules.html | 790 ++++++ 01-python/w5-python-more-examples.html | 762 +++++ 02-numpy/numpy-advanced.html | 1226 ++++++++ 02-numpy/numpy-basics.html | 1295 +++++++++ 02-numpy/numpy-exercises.html | 776 ++++++ 02-numpy/numpy.html | 605 ++++ 03-practices/git-single.html | 730 +++++ 03-practices/python-style.html | 683 +++++ 04-matplotlib/ipyvolume-example.html | 758 +++++ 04-matplotlib/matplotlib-basics.html | 1222 ++++++++ 04-matplotlib/matplotlib-basics.html.backup | 1236 ++++++++ 04-matplotlib/matplotlib-exercises.html | 835 ++++++ .../matplotlib-exercises.html.backup | 836 ++++++ 04-matplotlib/matplotlib.html | 605 ++++ 05-scipy/scipy-basics-2.html | 1500 ++++++++++ 05-scipy/scipy-basics-2.html.backup | 1511 ++++++++++ 05-scipy/scipy-basics.html | 1510 ++++++++++ 05-scipy/scipy-basics.html.backup | 1520 ++++++++++ 05-scipy/scipy-exercises-2.html | 894 ++++++ 05-scipy/scipy-exercises-2.html.backup | 896 ++++++ 05-scipy/scipy-exercises.html | 907 ++++++ 05-scipy/scipy.html | 605 ++++ 06-sympy/sympy-examples.html | 1807 ++++++++++++ 06-sympy/sympy-exercises.html | 793 ++++++ 06-sympy/sympy.html | 607 ++++ 09-packages/python-arguments.html | 656 +++++ 09-packages/python-modules.html | 707 +++++ 09-packages/python-more-modules.html | 646 +++++ 09-packages/python-packages.html | 801 ++++++ 09-packages/python-tools.html | 683 +++++ 10-testing/more-pytest.html | 777 ++++++ 10-testing/pytest.html | 736 +++++ 10-testing/real-world-example.html | 712 +++++ 10-testing/testing.html | 687 +++++ 11-machine-learning/gradient-descent.html | 851 ++++++ .../gradient-descent.html.backup | 853 ++++++ 11-machine-learning/keras-clustering.html | 1509 ++++++++++ 11-machine-learning/keras-mnist.html | 1666 +++++++++++ 11-machine-learning/keras-mnist.html.backup | 1667 +++++++++++ .../machine-learning-basics.html | 806 ++++++ .../machine-learning-libraries.html | 717 +++++ 11-machine-learning/machine-learning.html | 583 ++++ 11-machine-learning/neural-net-basics.html | 781 ++++++ .../neural-net-derivation.html | 716 +++++ 11-machine-learning/neural-net-hidden.html | 726 +++++ .../neural-net-improvements.html | 741 +++++ 12-extensions/extensions-example.html | 1364 +++++++++ 12-extensions/extensions-overview.html | 703 +++++ Introduction.html | 687 +++++ ...c6785ef2c54d0a8ded3cc3d404a1d49e57ead3.png | Bin 0 -> 27827 bytes ...746c559a66654295459a31d56575abd4226096.png | Bin 0 -> 179710 bytes ...cddab1053d71c343dfc1c7c1df90e2eb02cf76.png | Bin 0 -> 7448 bytes ...bc1c6444e47d37058e3182be60f71a63af8fa5.png | Bin 0 -> 37656 bytes ...5f615e6dccb3f245e259af494d20119331fbe7.png | Bin 0 -> 106806 bytes ...3a23546722fd40585a27f99318e66e528bee84.png | Bin 0 -> 29318 bytes ...778ff95ba6f3c3ccf0baa447e6022883f4a0e7.png | Bin 0 -> 27801 bytes ...b47600eeb692383554e04d0549a6c1e360293a.png | Bin 0 -> 11071 bytes ...dfa7a13609cabc38460ba215bd912932326486.png | Bin 0 -> 47346 bytes ...3f17dbfc53956e8dd10a2759234d6ca2e0c629.png | Bin 0 -> 7239 bytes ...963ee84959a0af7c2bf3aed56d12b9d0dc4fa4.png | Bin 0 -> 49923 bytes ...d35f09293d9aaa6a3271a014463bd53c77613f.png | Bin 0 -> 134747 bytes ...99e17cdfa2108351c56d6d11a840fd782f14a3.png | Bin 0 -> 14809 bytes ...57bd76a174429d7c754b847618ee8a04974bc3.png | Bin 0 -> 23900 bytes ...eef410e319daa5f2f917b9366051c4e9f6b13d.png | Bin 0 -> 23846 bytes ...930782d9cafe653b4234466b00034c22f8f4c7.png | Bin 0 -> 2539 bytes ...5c03b2a4ce67b2904092490c6a2b9dc7107b30.png | Bin 0 -> 7267 bytes ...20d3f30915b23d9d997f8675ac844c00e19c29.png | Bin 0 -> 41527 bytes ...59432167e90edc971cdc98c37610d9cc7e7f4c.png | Bin 0 -> 66392 bytes ...473f33e404efe75b61045d1b311ea520450202.png | Bin 0 -> 85924 bytes ...747f19aceabb1b381df374961dae4b6e883716.png | Bin 0 -> 7076 bytes ...76b997d3deff24f08686a8548303dc6c3d334c.png | Bin 0 -> 15562 bytes ...85111b46d544693ed61be588b1a338177b4788.png | Bin 0 -> 20385 bytes ...091d507beaad7c146c99e2fc8c3481e7ed1185.png | Bin 0 -> 24145 bytes ...d41f0aef30708641631267b24dbafcc0cea420.png | Bin 0 -> 97184 bytes ...512abdd2e69cf123085dfb9f88fb2c0cb225a3.png | Bin 0 -> 88758 bytes ...2cbffe2ed935ab38ea9d30bcfe690d80c847f3.png | Bin 0 -> 84804 bytes ...46d5a1b3c429cc8cf31bebed9c203fda6b40d9.png | Bin 0 -> 24015 bytes ...4f4a902c6f34847590b61274f8a9b8380916b1.png | Bin 0 -> 24893 bytes ...ff09c68008dbf76d6bcb101f395150b9d6b153.png | Bin 0 -> 22775 bytes ...f4162bea2142c17024708eb2e068cc777e852f.png | Bin 0 -> 7185 bytes ...de4ee4f50d860b4a8e1e8796107a753cbcfabf.png | Bin 0 -> 7306 bytes ...4cda78b2df12f8e9d61a968cab4d3ecaecd970.png | Bin 0 -> 16969 bytes ...3b10e2cdbd8048d1b2e886a77d05438f3f1ccb.png | Bin 0 -> 90844 bytes ...1fd8d5638149b718d17317ac1fbc18d8bf5ed3.png | Bin 0 -> 11469 bytes ...a2a2d517d57d84887392d87efab1d8f9d5fd94.png | Bin 0 -> 20549 bytes ...0847ac6a63895412a6d03f86c08f8e3f328f37.png | Bin 0 -> 7293 bytes _images/anatomy1.png | Bin 0 -> 101142 bytes ...bf9c2044a5bf2811adf7cb6f93af01d1c1da38.png | Bin 0 -> 101453 bytes ...39bfdb4fd20e74472e1fcbd358ecace9ed6981.png | Bin 0 -> 10853 bytes ...83e8742aee42c4fa9ceaefb55d6e13c81f6c45.png | Bin 0 -> 63312 bytes ...c3674d7660bb90ae3025a62565adab77515500.png | Bin 0 -> 37075 bytes ...27df939f63a85b567a3bd2dbf8a35f386abd83.png | Bin 0 -> 1412 bytes ...6d43bb4bc5a754ef48f6b7a6a16440848bdb20.png | Bin 0 -> 31658 bytes ...380771dd67af5d0377162c755c8a723636c989.png | Bin 0 -> 84696 bytes ...4ae0d552f4a26654861da536d16eea043dd641.png | Bin 0 -> 7081 bytes ...9f1f54fa904a8c1ffdcd6337a6f738f0d98336.png | Bin 0 -> 58672 bytes ...c70390b3cfe2458b4c55e3a75f4a8462d6dd90.png | Bin 0 -> 15644 bytes ...03a5ebda9f71fd6cdd2a2d93a6f2cf0a606806.png | Bin 0 -> 48804 bytes _images/distributed_version_control.png | Bin 0 -> 241532 bytes ...8b05626dcbc0bbdccfc36aa5896cda1097ad1d.png | Bin 0 -> 7136 bytes ...8c0248194f13547b08e0a540147be7889ca599.png | Bin 0 -> 36966 bytes ...aa635ee9bd52cae1bcf328e7c1319748e4c646.png | Bin 0 -> 18717 bytes ...bd2ea93dcb6c61745dc293d035532da31af2ac.png | Bin 0 -> 7365 bytes ...32345060fe752538e655250838ad92c5d3b836.png | Bin 0 -> 36248 bytes ...be9cff9c3d04e75390440cb7f125480aa50ee5.png | Bin 0 -> 71166 bytes ...68af1f60dd275bdb42787829fb1b8bce9c50ec.png | Bin 0 -> 17864 bytes ...685159d4c292312da298aac782027770fecb9c.png | Bin 0 -> 7078 bytes _images/github-clone.png | Bin 0 -> 166349 bytes _images/github-copy-ssh.png | Bin 0 -> 147364 bytes _images/github-create.png | Bin 0 -> 160018 bytes _images/github-fork.png | Bin 0 -> 127863 bytes _images/github-new.png | Bin 0 -> 539993 bytes _images/github-pr.png | Bin 0 -> 137205 bytes _images/github-pr2.png | Bin 0 -> 131475 bytes _images/github-workflow.png | Bin 0 -> 44099 bytes _images/nn_fig.png | Bin 0 -> 40707 bytes _images/nn_fig2.png | Bin 0 -> 52387 bytes _images/nn_fig_hidden.png | Bin 0 -> 86097 bytes _images/orbit_setup.png | Bin 0 -> 24561 bytes _images/python.png | Bin 0 -> 90835 bytes _images/python_environment.png | Bin 0 -> 52253 bytes _images/row_column_major.png | Bin 0 -> 22382 bytes _images/sigmoid.png | Bin 0 -> 16537 bytes _images/test.png | Bin 0 -> 25864 bytes _images/test1.png | Bin 0 -> 108732 bytes _sources/01-python/basics.md | 51 + _sources/01-python/functions-classes.md | 5 + _sources/01-python/installing.md | 41 + _sources/01-python/misc.md | 5 + _sources/01-python/python-io.ipynb | 465 ++++ _sources/01-python/python.md | 3 + _sources/01-python/using.md | 45 + _sources/01-python/w1-jupyter.ipynb | 184 ++ _sources/01-python/w1-python-datatypes.ipynb | 1365 +++++++++ .../w2-python-advanced-datatypes.ipynb | 916 ++++++ .../01-python/w2-python-control-flow.ipynb | 485 ++++ _sources/01-python/w2-python-exercises.ipynb | 453 +++ _sources/01-python/w3-python-exceptions.ipynb | 184 ++ _sources/01-python/w3-python-exercises.ipynb | 500 ++++ _sources/01-python/w3-python-functions.ipynb | 549 ++++ _sources/01-python/w4-python-classes.ipynb | 833 ++++++ _sources/01-python/w4-python-exercises.ipynb | 426 +++ _sources/01-python/w4-python-modules.ipynb | 117 + .../01-python/w5-python-more-examples.ipynb | 177 ++ _sources/02-numpy/numpy-advanced.ipynb | 941 +++++++ _sources/02-numpy/numpy-basics.ipynb | 788 ++++++ _sources/02-numpy/numpy-exercises.ipynb | 233 ++ _sources/02-numpy/numpy.md | 3 + _sources/03-practices/git-single.md | 162 ++ _sources/03-practices/python-style.ipynb | 118 + .../04-matplotlib/ipyvolume-example.ipynb | 310 +++ .../04-matplotlib/matplotlib-basics.ipynb | 1188 ++++++++ .../04-matplotlib/matplotlib-exercises.ipynb | 322 +++ _sources/04-matplotlib/matplotlib.md | 3 + _sources/05-scipy/scipy-basics-2.ipynb | 1324 +++++++++ _sources/05-scipy/scipy-basics.ipynb | 1391 +++++++++ _sources/05-scipy/scipy-exercises-2.ipynb | 377 +++ _sources/05-scipy/scipy-exercises.ipynb | 398 +++ _sources/05-scipy/scipy.md | 3 + _sources/06-sympy/sympy-examples.ipynb | 2420 ++++++++++++++++ _sources/06-sympy/sympy-exercises.ipynb | 261 ++ _sources/06-sympy/sympy.md | 5 + _sources/09-packages/python-arguments.md | 21 + _sources/09-packages/python-modules.md | 91 + _sources/09-packages/python-more-modules.md | 57 + _sources/09-packages/python-packages.md | 205 ++ _sources/09-packages/python-tools.md | 63 + _sources/10-testing/more-pytest.md | 130 + _sources/10-testing/pytest.md | 110 + _sources/10-testing/real-world-example.md | 92 + _sources/10-testing/testing.md | 55 + .../gradient-descent.ipynb | 357 +++ .../keras-clustering.ipynb | 807 ++++++ .../11-machine-learning/keras-mnist.ipynb | 1130 ++++++++ .../machine-learning-basics.ipynb | 246 ++ .../machine-learning-libraries.md | 106 + .../11-machine-learning/machine-learning.md | 3 + .../11-machine-learning/neural-net-basics.md | 151 + .../neural-net-derivation.md | 93 + .../11-machine-learning/neural-net-hidden.md | 111 + .../neural-net-improvements.md | 103 + _sources/12-extensions/extensions-example.md | 306 ++ _sources/12-extensions/extensions-overview.md | 77 + _sources/Introduction.md | 55 + _sources/git/git-branches.md | 374 +++ _sources/git/git-remotes.md | 110 + _sources/git/git.md | 222 ++ _sources/git/github.md | 197 ++ _sources/git/pull-requests.md | 135 + _sources/git/version-control.md | 154 + _sphinx_design_static/design-tabs.js | 101 + _sphinx_design_static/sphinx-design.min.css | 1 + _static/basic.css | 925 ++++++ _static/check-solid.svg | 4 + _static/clipboard.min.js | 7 + _static/copy-button.svg | 5 + _static/copybutton.css | 94 + _static/copybutton.js | 248 ++ _static/copybutton_funcs.js | 73 + _static/design-tabs.js | 101 + _static/doctools.js | 156 ++ _static/documentation_options.js | 13 + _static/download_patch.js | 15 + _static/file.png | Bin 0 -> 286 bytes _static/fix_admonition_style.css | 3 + _static/fix_align_code.css | 6 + _static/fix_align_text_captions.css | 3 + _static/fix_code_header_style.css | 8 + _static/fix_dropdown_style.css | 3 + _static/fix_hash_jump.js | 9 + _static/fix_margin.css | 26 + _static/fix_sidebar_scroll.css | 3 + _static/images/logo_binder.svg | 19 + _static/images/logo_colab.png | Bin 0 -> 7601 bytes _static/images/logo_deepnote.svg | 1 + _static/images/logo_jupyterhub.svg | 1 + _static/images/logo_jupyterlite.svg | 17 + _static/language_data.js | 199 ++ _static/locales/ar/LC_MESSAGES/booktheme.po | 75 + _static/locales/bg/LC_MESSAGES/booktheme.po | 75 + _static/locales/bn/LC_MESSAGES/booktheme.po | 63 + _static/locales/ca/LC_MESSAGES/booktheme.po | 66 + _static/locales/cs/LC_MESSAGES/booktheme.po | 75 + _static/locales/da/LC_MESSAGES/booktheme.po | 75 + _static/locales/de/LC_MESSAGES/booktheme.po | 75 + _static/locales/el/LC_MESSAGES/booktheme.po | 75 + _static/locales/eo/LC_MESSAGES/booktheme.po | 75 + _static/locales/es/LC_MESSAGES/booktheme.po | 75 + _static/locales/et/LC_MESSAGES/booktheme.po | 75 + _static/locales/fi/LC_MESSAGES/booktheme.po | 75 + _static/locales/fr/LC_MESSAGES/booktheme.po | 75 + _static/locales/hr/LC_MESSAGES/booktheme.po | 75 + _static/locales/id/LC_MESSAGES/booktheme.po | 75 + _static/locales/it/LC_MESSAGES/booktheme.po | 75 + _static/locales/iw/LC_MESSAGES/booktheme.po | 75 + _static/locales/ja/LC_MESSAGES/booktheme.po | 75 + _static/locales/ko/LC_MESSAGES/booktheme.po | 75 + _static/locales/lt/LC_MESSAGES/booktheme.po | 75 + _static/locales/lv/LC_MESSAGES/booktheme.po | 75 + _static/locales/ml/LC_MESSAGES/booktheme.po | 66 + _static/locales/mr/LC_MESSAGES/booktheme.po | 66 + _static/locales/ms/LC_MESSAGES/booktheme.po | 66 + _static/locales/nl/LC_MESSAGES/booktheme.po | 75 + _static/locales/no/LC_MESSAGES/booktheme.po | 75 + _static/locales/pl/LC_MESSAGES/booktheme.po | 75 + _static/locales/pt/LC_MESSAGES/booktheme.po | 75 + _static/locales/ro/LC_MESSAGES/booktheme.po | 75 + _static/locales/ru/LC_MESSAGES/booktheme.po | 75 + _static/locales/sk/LC_MESSAGES/booktheme.po | 75 + _static/locales/sl/LC_MESSAGES/booktheme.po | 75 + _static/locales/sr/LC_MESSAGES/booktheme.po | 75 + _static/locales/sv/LC_MESSAGES/booktheme.po | 75 + _static/locales/ta/LC_MESSAGES/booktheme.po | 66 + _static/locales/te/LC_MESSAGES/booktheme.po | 66 + _static/locales/tg/LC_MESSAGES/booktheme.po | 75 + _static/locales/th/LC_MESSAGES/booktheme.po | 75 + _static/locales/tl/LC_MESSAGES/booktheme.po | 66 + _static/locales/tr/LC_MESSAGES/booktheme.po | 75 + _static/locales/uk/LC_MESSAGES/booktheme.po | 75 + _static/locales/ur/LC_MESSAGES/booktheme.po | 66 + _static/locales/vi/LC_MESSAGES/booktheme.po | 75 + .../locales/zh_CN/LC_MESSAGES/booktheme.po | 75 + .../locales/zh_TW/LC_MESSAGES/booktheme.po | 75 + _static/margin_patch.css | 17 + _static/mathjax_patch.css | 15 + _static/mathjax_patch.js | 11 + _static/minus.png | Bin 0 -> 90 bytes _static/myfile.css | 10 + ...296f466b2cfe2517ffebfabe82451661e28f02.css | 2474 +++++++++++++++++ _static/no-search.css | 4 + _static/play-solid.svg | 1 + _static/plus.png | Bin 0 -> 90 bytes _static/preserve_dropdown_state.js | 81 + _static/pygments.css | 152 + _static/sbt-webpack-macros.html | 11 + _static/scripts/bootstrap.js | 3 + _static/scripts/bootstrap.js.LICENSE.txt | 5 + _static/scripts/bootstrap.js.map | 1 + _static/scripts/fontawesome.js | 3 + _static/scripts/fontawesome.js.LICENSE.txt | 5 + _static/scripts/fontawesome.js.map | 1 + _static/scripts/pydata-sphinx-theme.js | 2 + _static/scripts/pydata-sphinx-theme.js.map | 1 + _static/scripts/sphinx-book-theme.js | 2 + _static/scripts/sphinx-book-theme.js.map | 1 + _static/searchtools.js | 620 +++++ _static/sphinx-design.min.css | 1 + _static/sphinx-thebe.css | 129 + _static/sphinx-thebe.js | 127 + _static/sphinx_highlight.js | 154 + _static/styles/pydata-sphinx-theme.css | 32 + _static/styles/pydata-sphinx-theme.css.map | 1 + _static/styles/sphinx-book-theme.css | 19 + _static/styles/sphinx-book-theme.css.map | 1 + _static/styles/theme.css | 2 + _static/togglebutton.css | 166 ++ _static/togglebutton.js | 257 ++ .../fontawesome/webfonts/fa-brands-400.ttf | Bin 0 -> 209128 bytes .../fontawesome/webfonts/fa-brands-400.woff2 | Bin 0 -> 117852 bytes .../fontawesome/webfonts/fa-regular-400.ttf | Bin 0 -> 67860 bytes .../fontawesome/webfonts/fa-regular-400.woff2 | Bin 0 -> 25392 bytes .../fontawesome/webfonts/fa-solid-900.ttf | Bin 0 -> 420332 bytes .../fontawesome/webfonts/fa-solid-900.woff2 | Bin 0 -> 156400 bytes _static/webpack-macros.html | 24 + genindex.html | 517 ++++ git/git-branches.html | 915 ++++++ git/git-remotes.html | 687 +++++ git/git.html | 806 ++++++ git/github.html | 779 ++++++ git/pull-requests.html | 704 +++++ git/version-control.html | 766 +++++ index.html | 1 + objects.inv | Bin 0 -> 1421 bytes search.html | 528 ++++ searchindex.js | 1 + 334 files changed, 99123 insertions(+) create mode 100644 .buildinfo create mode 100644 .nojekyll create mode 100644 01-python/basics.html create mode 100644 01-python/functions-classes.html create mode 100644 01-python/installing.html create mode 100644 01-python/misc.html create mode 100644 01-python/python-io.html create mode 100644 01-python/python.html create mode 100644 01-python/using.html create mode 100644 01-python/w1-jupyter.html create mode 100644 01-python/w1-python-datatypes.html create mode 100644 01-python/w2-python-advanced-datatypes.html create mode 100644 01-python/w2-python-control-flow.html create mode 100644 01-python/w2-python-exercises.html create mode 100644 01-python/w3-python-exceptions.html create mode 100644 01-python/w3-python-exercises.html create mode 100644 01-python/w3-python-functions.html create mode 100644 01-python/w4-python-classes.html create mode 100644 01-python/w4-python-exercises.html create mode 100644 01-python/w4-python-modules.html create mode 100644 01-python/w5-python-more-examples.html create mode 100644 02-numpy/numpy-advanced.html create mode 100644 02-numpy/numpy-basics.html create mode 100644 02-numpy/numpy-exercises.html create mode 100644 02-numpy/numpy.html create mode 100644 03-practices/git-single.html create mode 100644 03-practices/python-style.html create mode 100644 04-matplotlib/ipyvolume-example.html create mode 100644 04-matplotlib/matplotlib-basics.html create mode 100644 04-matplotlib/matplotlib-basics.html.backup create mode 100644 04-matplotlib/matplotlib-exercises.html create mode 100644 04-matplotlib/matplotlib-exercises.html.backup create mode 100644 04-matplotlib/matplotlib.html create mode 100644 05-scipy/scipy-basics-2.html create mode 100644 05-scipy/scipy-basics-2.html.backup create mode 100644 05-scipy/scipy-basics.html create mode 100644 05-scipy/scipy-basics.html.backup create mode 100644 05-scipy/scipy-exercises-2.html create mode 100644 05-scipy/scipy-exercises-2.html.backup create mode 100644 05-scipy/scipy-exercises.html create mode 100644 05-scipy/scipy.html create mode 100644 06-sympy/sympy-examples.html create mode 100644 06-sympy/sympy-exercises.html create mode 100644 06-sympy/sympy.html create mode 100644 09-packages/python-arguments.html create mode 100644 09-packages/python-modules.html create mode 100644 09-packages/python-more-modules.html create mode 100644 09-packages/python-packages.html create mode 100644 09-packages/python-tools.html create mode 100644 10-testing/more-pytest.html create mode 100644 10-testing/pytest.html create mode 100644 10-testing/real-world-example.html create mode 100644 10-testing/testing.html create mode 100644 11-machine-learning/gradient-descent.html create mode 100644 11-machine-learning/gradient-descent.html.backup create mode 100644 11-machine-learning/keras-clustering.html create mode 100644 11-machine-learning/keras-mnist.html create mode 100644 11-machine-learning/keras-mnist.html.backup create mode 100644 11-machine-learning/machine-learning-basics.html create mode 100644 11-machine-learning/machine-learning-libraries.html create mode 100644 11-machine-learning/machine-learning.html create mode 100644 11-machine-learning/neural-net-basics.html create mode 100644 11-machine-learning/neural-net-derivation.html create mode 100644 11-machine-learning/neural-net-hidden.html create mode 100644 11-machine-learning/neural-net-improvements.html create mode 100644 12-extensions/extensions-example.html create mode 100644 12-extensions/extensions-overview.html create mode 100644 Introduction.html create mode 100644 _images/05b93724c7fe07ec65ef5c1c21c6785ef2c54d0a8ded3cc3d404a1d49e57ead3.png create mode 100644 _images/082b81005f9f3f3c1ddd51267a746c559a66654295459a31d56575abd4226096.png create mode 100644 _images/0e2cd18fde8a96eb62e54a21f4cddab1053d71c343dfc1c7c1df90e2eb02cf76.png create mode 100644 _images/12158ea15c928d43a30e2afd5fbc1c6444e47d37058e3182be60f71a63af8fa5.png create mode 100644 _images/14a55c4b37454039b454f3503c5f615e6dccb3f245e259af494d20119331fbe7.png create mode 100644 _images/18bb87f4a28b0bb4588355d6203a23546722fd40585a27f99318e66e528bee84.png create mode 100644 _images/1a8b730832e97d8d0b31d24d2b778ff95ba6f3c3ccf0baa447e6022883f4a0e7.png create mode 100644 _images/1c1de585c1b5f18074c2dd3773b47600eeb692383554e04d0549a6c1e360293a.png create mode 100644 _images/1ed90fdaf901f74754a93aeac3dfa7a13609cabc38460ba215bd912932326486.png create mode 100644 _images/227f2d3a8e3db865c48a39a8063f17dbfc53956e8dd10a2759234d6ca2e0c629.png create mode 100644 _images/29b1ce756a8461259f118d9cc3963ee84959a0af7c2bf3aed56d12b9d0dc4fa4.png create mode 100644 _images/2ec66a83b5fc1eaac44c83d4f5d35f09293d9aaa6a3271a014463bd53c77613f.png create mode 100644 _images/347a411bd510f339f1112f612899e17cdfa2108351c56d6d11a840fd782f14a3.png create mode 100644 _images/3ac4d61e01e4ad2f40fe01f62e57bd76a174429d7c754b847618ee8a04974bc3.png create mode 100644 _images/3ec9e8820552c29bedb8733a09eef410e319daa5f2f917b9366051c4e9f6b13d.png create mode 100644 _images/433ba52e047896b8c7177f2df8930782d9cafe653b4234466b00034c22f8f4c7.png create mode 100644 _images/4567d8f9bd61f12d86168899465c03b2a4ce67b2904092490c6a2b9dc7107b30.png create mode 100644 _images/5617d55160afd4564a01ed069520d3f30915b23d9d997f8675ac844c00e19c29.png create mode 100644 _images/567340fd5c0f4220f685c50adc59432167e90edc971cdc98c37610d9cc7e7f4c.png create mode 100644 _images/5707a02e8cca32ab41ddc3e507473f33e404efe75b61045d1b311ea520450202.png create mode 100644 _images/58d940405d5b2d97a8ac4387c0747f19aceabb1b381df374961dae4b6e883716.png create mode 100644 _images/6bc78f3fe2a9e65e3d606852a276b997d3deff24f08686a8548303dc6c3d334c.png create mode 100644 _images/6e87cfca18df7ece7614e5650e85111b46d544693ed61be588b1a338177b4788.png create mode 100644 _images/76208d9bf1fefa3640dd42e087091d507beaad7c146c99e2fc8c3481e7ed1185.png create mode 100644 _images/7ced87012e88958170d22c7d32d41f0aef30708641631267b24dbafcc0cea420.png create mode 100644 _images/7d75ee826cda9aea1c634d659e512abdd2e69cf123085dfb9f88fb2c0cb225a3.png create mode 100644 _images/8f22444e032fd5b8480eb4e0752cbffe2ed935ab38ea9d30bcfe690d80c847f3.png create mode 100644 _images/9117b21318fe3c3165d87c5d4c46d5a1b3c429cc8cf31bebed9c203fda6b40d9.png create mode 100644 _images/9231241e6bd766784356e576c64f4a902c6f34847590b61274f8a9b8380916b1.png create mode 100644 _images/9464b0c0bcee714b070f959c01ff09c68008dbf76d6bcb101f395150b9d6b153.png create mode 100644 _images/95b9f0fd23894c2cbbb25bb94ff4162bea2142c17024708eb2e068cc777e852f.png create mode 100644 _images/99aa1a1124655bc04ed0c253cede4ee4f50d860b4a8e1e8796107a753cbcfabf.png create mode 100644 _images/9ad115d2a594e0d189e996c0204cda78b2df12f8e9d61a968cab4d3ecaecd970.png create mode 100644 _images/a087c096f127028aed4565dfdf3b10e2cdbd8048d1b2e886a77d05438f3f1ccb.png create mode 100644 _images/a93bdf927f3ddd98df4c44c2591fd8d5638149b718d17317ac1fbc18d8bf5ed3.png create mode 100644 _images/ac8d3ce8eecdd5cb24b438db7ea2a2d517d57d84887392d87efab1d8f9d5fd94.png create mode 100644 _images/ae7d94ffa26d5baa2e15a13dae0847ac6a63895412a6d03f86c08f8e3f328f37.png create mode 100644 _images/anatomy1.png create mode 100644 _images/b59abeb56bbd6be6c26cb8b33dbf9c2044a5bf2811adf7cb6f93af01d1c1da38.png create mode 100644 _images/b830e2cee583d9a13af607d32039bfdb4fd20e74472e1fcbd358ecace9ed6981.png create mode 100644 _images/b897fc22d6a1d0326dabca816283e8742aee42c4fa9ceaefb55d6e13c81f6c45.png create mode 100644 _images/ba892442d450d2d47e3d6ae549c3674d7660bb90ae3025a62565adab77515500.png create mode 100644 _images/bd2dc15da4e6fe950717320e2e27df939f63a85b567a3bd2dbf8a35f386abd83.png create mode 100644 _images/c5438daabd7e7caf26009be4c16d43bb4bc5a754ef48f6b7a6a16440848bdb20.png create mode 100644 _images/c62cb3987d333d4e135d6e54b6380771dd67af5d0377162c755c8a723636c989.png create mode 100644 _images/c8c2834b4172a70240f93d1cb14ae0d552f4a26654861da536d16eea043dd641.png create mode 100644 _images/cff06e6db54c63aafc594447299f1f54fa904a8c1ffdcd6337a6f738f0d98336.png create mode 100644 _images/da56e7aa5a4fec26cc6454ce53c70390b3cfe2458b4c55e3a75f4a8462d6dd90.png create mode 100644 _images/db2583cf6aec7a55e71d7e16af03a5ebda9f71fd6cdd2a2d93a6f2cf0a606806.png create mode 100644 _images/distributed_version_control.png create mode 100644 _images/e4e9c1c1a046a645e43f47b6e48b05626dcbc0bbdccfc36aa5896cda1097ad1d.png create mode 100644 _images/e57874be518cde2fb535060dc58c0248194f13547b08e0a540147be7889ca599.png create mode 100644 _images/e87ab7494f0da848d087afd17caa635ee9bd52cae1bcf328e7c1319748e4c646.png create mode 100644 _images/e89ded31bb0805eaed85d00c64bd2ea93dcb6c61745dc293d035532da31af2ac.png create mode 100644 _images/ea36916fe87eb56d146e30bc3732345060fe752538e655250838ad92c5d3b836.png create mode 100644 _images/fb0a6c80516310178afbce3044be9cff9c3d04e75390440cb7f125480aa50ee5.png create mode 100644 _images/ff442053d64f0baf70c89195cd68af1f60dd275bdb42787829fb1b8bce9c50ec.png create mode 100644 _images/ffee7b61de1ff038024f9ad240685159d4c292312da298aac782027770fecb9c.png create mode 100644 _images/github-clone.png create mode 100644 _images/github-copy-ssh.png create mode 100644 _images/github-create.png create mode 100644 _images/github-fork.png create mode 100644 _images/github-new.png create mode 100644 _images/github-pr.png create mode 100644 _images/github-pr2.png create mode 100644 _images/github-workflow.png create mode 100644 _images/nn_fig.png create mode 100644 _images/nn_fig2.png create mode 100644 _images/nn_fig_hidden.png create mode 100644 _images/orbit_setup.png create mode 100644 _images/python.png create mode 100644 _images/python_environment.png create mode 100644 _images/row_column_major.png create mode 100644 _images/sigmoid.png create mode 100644 _images/test.png create mode 100644 _images/test1.png create mode 100644 _sources/01-python/basics.md create mode 100644 _sources/01-python/functions-classes.md create mode 100644 _sources/01-python/installing.md create mode 100644 _sources/01-python/misc.md create mode 100644 _sources/01-python/python-io.ipynb create mode 100644 _sources/01-python/python.md create mode 100644 _sources/01-python/using.md create mode 100644 _sources/01-python/w1-jupyter.ipynb create mode 100644 _sources/01-python/w1-python-datatypes.ipynb create mode 100644 _sources/01-python/w2-python-advanced-datatypes.ipynb create mode 100644 _sources/01-python/w2-python-control-flow.ipynb create mode 100644 _sources/01-python/w2-python-exercises.ipynb create mode 100644 _sources/01-python/w3-python-exceptions.ipynb create mode 100644 _sources/01-python/w3-python-exercises.ipynb create mode 100644 _sources/01-python/w3-python-functions.ipynb create mode 100644 _sources/01-python/w4-python-classes.ipynb create mode 100644 _sources/01-python/w4-python-exercises.ipynb create mode 100644 _sources/01-python/w4-python-modules.ipynb create mode 100644 _sources/01-python/w5-python-more-examples.ipynb create mode 100644 _sources/02-numpy/numpy-advanced.ipynb create mode 100644 _sources/02-numpy/numpy-basics.ipynb create mode 100644 _sources/02-numpy/numpy-exercises.ipynb create mode 100644 _sources/02-numpy/numpy.md create mode 100644 _sources/03-practices/git-single.md create mode 100644 _sources/03-practices/python-style.ipynb create mode 100644 _sources/04-matplotlib/ipyvolume-example.ipynb create mode 100644 _sources/04-matplotlib/matplotlib-basics.ipynb create mode 100644 _sources/04-matplotlib/matplotlib-exercises.ipynb create mode 100644 _sources/04-matplotlib/matplotlib.md create mode 100644 _sources/05-scipy/scipy-basics-2.ipynb create mode 100644 _sources/05-scipy/scipy-basics.ipynb create mode 100644 _sources/05-scipy/scipy-exercises-2.ipynb create mode 100644 _sources/05-scipy/scipy-exercises.ipynb create mode 100644 _sources/05-scipy/scipy.md create mode 100644 _sources/06-sympy/sympy-examples.ipynb create mode 100644 _sources/06-sympy/sympy-exercises.ipynb create mode 100644 _sources/06-sympy/sympy.md create mode 100644 _sources/09-packages/python-arguments.md create mode 100644 _sources/09-packages/python-modules.md create mode 100644 _sources/09-packages/python-more-modules.md create mode 100644 _sources/09-packages/python-packages.md create mode 100644 _sources/09-packages/python-tools.md create mode 100644 _sources/10-testing/more-pytest.md create mode 100644 _sources/10-testing/pytest.md create mode 100644 _sources/10-testing/real-world-example.md create mode 100644 _sources/10-testing/testing.md create mode 100644 _sources/11-machine-learning/gradient-descent.ipynb create mode 100644 _sources/11-machine-learning/keras-clustering.ipynb create mode 100644 _sources/11-machine-learning/keras-mnist.ipynb create mode 100644 _sources/11-machine-learning/machine-learning-basics.ipynb create mode 100644 _sources/11-machine-learning/machine-learning-libraries.md create mode 100644 _sources/11-machine-learning/machine-learning.md create mode 100644 _sources/11-machine-learning/neural-net-basics.md create mode 100644 _sources/11-machine-learning/neural-net-derivation.md create mode 100644 _sources/11-machine-learning/neural-net-hidden.md create mode 100644 _sources/11-machine-learning/neural-net-improvements.md create mode 100644 _sources/12-extensions/extensions-example.md create mode 100644 _sources/12-extensions/extensions-overview.md create mode 100644 _sources/Introduction.md create mode 100644 _sources/git/git-branches.md create mode 100644 _sources/git/git-remotes.md create mode 100644 _sources/git/git.md create mode 100644 _sources/git/github.md create mode 100644 _sources/git/pull-requests.md create mode 100644 _sources/git/version-control.md create mode 100644 _sphinx_design_static/design-tabs.js create mode 100644 _sphinx_design_static/sphinx-design.min.css create mode 100644 _static/basic.css create mode 100644 _static/check-solid.svg create mode 100644 _static/clipboard.min.js create mode 100644 _static/copy-button.svg create mode 100644 _static/copybutton.css create mode 100644 _static/copybutton.js create mode 100644 _static/copybutton_funcs.js create mode 100644 _static/design-tabs.js create mode 100644 _static/doctools.js create mode 100644 _static/documentation_options.js create mode 100644 _static/download_patch.js create mode 100644 _static/file.png create mode 100644 _static/fix_admonition_style.css create mode 100644 _static/fix_align_code.css create mode 100644 _static/fix_align_text_captions.css create mode 100644 _static/fix_code_header_style.css create mode 100644 _static/fix_dropdown_style.css create mode 100644 _static/fix_hash_jump.js create mode 100644 _static/fix_margin.css create mode 100644 _static/fix_sidebar_scroll.css create mode 100644 _static/images/logo_binder.svg create mode 100644 _static/images/logo_colab.png create mode 100644 _static/images/logo_deepnote.svg create mode 100644 _static/images/logo_jupyterhub.svg create mode 100644 _static/images/logo_jupyterlite.svg create mode 100644 _static/language_data.js create mode 100644 _static/locales/ar/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/bg/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/bn/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/ca/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/cs/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/da/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/de/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/el/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/eo/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/es/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/et/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/fi/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/fr/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/hr/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/id/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/it/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/iw/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/ja/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/ko/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/lt/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/lv/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/ml/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/mr/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/ms/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/nl/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/no/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/pl/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/pt/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/ro/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/ru/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/sk/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/sl/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/sr/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/sv/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/ta/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/te/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/tg/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/th/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/tl/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/tr/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/uk/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/ur/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/vi/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/zh_CN/LC_MESSAGES/booktheme.po create mode 100644 _static/locales/zh_TW/LC_MESSAGES/booktheme.po create mode 100644 _static/margin_patch.css create mode 100644 _static/mathjax_patch.css create mode 100644 _static/mathjax_patch.js create mode 100644 _static/minus.png create mode 100644 _static/myfile.css create mode 100644 _static/mystnb.8ecb98da25f57f5357bf6f572d296f466b2cfe2517ffebfabe82451661e28f02.css create mode 100644 _static/no-search.css create mode 100644 _static/play-solid.svg create mode 100644 _static/plus.png create mode 100644 _static/preserve_dropdown_state.js create mode 100644 _static/pygments.css create mode 100644 _static/sbt-webpack-macros.html create mode 100644 _static/scripts/bootstrap.js create mode 100644 _static/scripts/bootstrap.js.LICENSE.txt create mode 100644 _static/scripts/bootstrap.js.map create mode 100644 _static/scripts/fontawesome.js create mode 100644 _static/scripts/fontawesome.js.LICENSE.txt create mode 100644 _static/scripts/fontawesome.js.map create mode 100644 _static/scripts/pydata-sphinx-theme.js create mode 100644 _static/scripts/pydata-sphinx-theme.js.map create mode 100644 _static/scripts/sphinx-book-theme.js create mode 100644 _static/scripts/sphinx-book-theme.js.map create mode 100644 _static/searchtools.js create mode 100644 _static/sphinx-design.min.css create mode 100644 _static/sphinx-thebe.css create mode 100644 _static/sphinx-thebe.js create mode 100644 _static/sphinx_highlight.js create mode 100644 _static/styles/pydata-sphinx-theme.css create mode 100644 _static/styles/pydata-sphinx-theme.css.map create mode 100644 _static/styles/sphinx-book-theme.css create mode 100644 _static/styles/sphinx-book-theme.css.map create mode 100644 _static/styles/theme.css create mode 100644 _static/togglebutton.css create mode 100644 _static/togglebutton.js create mode 100644 _static/vendor/fontawesome/webfonts/fa-brands-400.ttf create mode 100644 _static/vendor/fontawesome/webfonts/fa-brands-400.woff2 create mode 100644 _static/vendor/fontawesome/webfonts/fa-regular-400.ttf create mode 100644 _static/vendor/fontawesome/webfonts/fa-regular-400.woff2 create mode 100644 _static/vendor/fontawesome/webfonts/fa-solid-900.ttf create mode 100644 _static/vendor/fontawesome/webfonts/fa-solid-900.woff2 create mode 100644 _static/webpack-macros.html create mode 100644 genindex.html create mode 100644 git/git-branches.html create mode 100644 git/git-remotes.html create mode 100644 git/git.html create mode 100644 git/github.html create mode 100644 git/pull-requests.html create mode 100644 git/version-control.html create mode 100644 index.html create mode 100644 objects.inv create mode 100644 search.html create mode 100644 searchindex.js diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 00000000..c15fb941 --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: cf9f84adef08026bb3b96c31bc9ca37d +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/01-python/basics.html b/01-python/basics.html new file mode 100644 index 00000000..e3ca716b --- /dev/null +++ b/01-python/basics.html @@ -0,0 +1,673 @@ + + + + + + + + + + + Python Basics — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Python Basics

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

Python Basics#

+

The following references give helpful introductions to python:

+ +
+

Practicing#

+

Some resources for practicing on your own:

+ +
+
+

Online books:#

+ +
+
+

Domain-specific libraries#

+ +
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/01-python/functions-classes.html b/01-python/functions-classes.html new file mode 100644 index 00000000..8c0b2693 --- /dev/null +++ b/01-python/functions-classes.html @@ -0,0 +1,607 @@ + + + + + + + + + + + Functions and Classes — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Functions and Classes

+ +
+
+ +
+
+
+ + + + +
+ +
+

Functions and Classes#

+

Functions and classes are the building blocks of complex programs. +These allow you to organize your code into logical units that can +reused.

+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/01-python/installing.html b/01-python/installing.html new file mode 100644 index 00000000..6f30d2f5 --- /dev/null +++ b/01-python/installing.html @@ -0,0 +1,658 @@ + + + + + + + + + + + Introduction — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Introduction

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

Introduction#

+

This class will introduce the basics of the python programming +language and the libraries used for scientific computing.

+
+

Tip

+

To get the most from this class, you should work on your own laptop. That +way you practice using python and the scientific libraries on the in the +environment you are most comfortable with.

+
+
+

Getting python#

+

You will want to install python and the associated libraries on your +laptop that you can bring to the seminar.

+

On Linux machines, you probably already have python and you can get +the needed libraries through your system package manager.

+

For Mac and Windows, I recommend the free Anaconda distribution:

+

https://www.anaconda.com/products/individual

+

This will install everything that you need.

+
+

Tip

+

If you have trouble getting a local install working, most of the class +material will work automatically in the cloud, either on +binder or google +colab.

+
+

If you have python successfully installed, you should be able to start +the python interpreter at the command line as: python. A shell will +come up, and you can try out your first program:

+
print("hello, world")
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/01-python/misc.html b/01-python/misc.html new file mode 100644 index 00000000..1978f35f --- /dev/null +++ b/01-python/misc.html @@ -0,0 +1,605 @@ + + + + + + + + + + + Miscellaneous — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Miscellaneous

+ +
+
+ +
+
+
+ + + + +
+ +
+

Miscellaneous#

+

There are a lot of topics that we didn’t cover, as well as a lot of +the python standard library that we won’t address. Here we introduce +a few more concepts.

+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/01-python/python-io.html b/01-python/python-io.html new file mode 100644 index 00000000..de95bdb4 --- /dev/null +++ b/01-python/python-io.html @@ -0,0 +1,952 @@ + + + + + + + + + + + Print — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Print

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+
+
from __future__ import print_function
+
+
+
+
+

One of the main things that we want to do in scientific computing is get data into and out of our programs. In addition to plain text files, there are modules that can read lots of different data formats we might encounter.

+
+

Print#

+

We’ve already been using print quite a bit, but now we’ll look at how to control how information is printed. Note that there is an older and newer way to format print statements – we’ll focus only on the newer way (it’s nicer).

+

This is compatible with both python 2 and 3

+
+
+
x = 1
+y = 0.0000354
+z = 3.0
+s = "my string"
+
+
+
+
+
+
+
print(x)
+
+
+
+
+
1
+
+
+
+
+

We write a string with {} embedded to indicate where variables are to be inserted. Note that {} can take arguments. We use the format() method on the string to match the variables to the {}.

+
+
+
print("x = {}, y = {}, z = {}, s = {}".format(x, y, z, s))
+
+
+
+
+
x = 1, y = 3.54e-05, z = 3.0, s = my string
+
+
+
+
+

Before a semi-colon, we can give an optional index/position/descriptor of the value we want to print.

+

After the semi-colon we give a format specifier. It has a number field and a type, like f and g to describe how floating point numbers appear and how much precision to show. Other bits are possible as well (like justification).

+
+
+
print("x = {0}, y = {1:10.5g}, z = {2:.3f}, s = {3}".format(x, y, z, s))
+
+
+
+
+
x = 1, y =   3.54e-05, z = 3.000, s = my string
+
+
+
+
+

there are other formatting things, like justification, etc. See the tutorial

+
+
+
print("{:^80}".format("centered string"))
+
+
+
+
+
                                centered string                                 
+
+
+
+
+
+
+

File I/O#

+

as expected, a file is an object. Here we’ll use the try, except block to capture exceptions (like if the file cannot be opened).

+
+
+
try: f = open("./sample.txt", "w")   # open for writing -- any file of the same name will be overwritten
+except: 
+    print("cannot open the file")
+
+print(f)
+
+
+
+
+
<_io.TextIOWrapper name='./sample.txt' mode='w' encoding='UTF-8'>
+
+
+
+
+
+
+
f.write("this is my first write\n")
+f.close()
+
+
+
+
+

we can easily loop over the lines in a file

+
+
+
try: 
+    f = open("./test.txt", "r")
+except:
+    print("error: cannot open the file")
+    
+for line in f:
+    print(line.split())
+    
+f.close()
+
+
+
+
+
['Lorem', 'ipsum', 'dolor', 'sit', 'amet,', 'consectetur', 'adipisicing', 'elit,', 'sed', 'do']
+['eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'et', 'dolore', 'magna', 'aliqua.', 'Ut', 'enim', 'ad']
+['minim', 'veniam,', 'quis', 'nostrud', 'exercitation', 'ullamco', 'laboris', 'nisi', 'ut']
+['aliquip', 'ex', 'ea', 'commodo', 'consequat.', 'Duis', 'aute', 'irure', 'dolor', 'in']
+['reprehenderit', 'in', 'voluptate', 'velit', 'esse', 'cillum', 'dolore', 'eu', 'fugiat', 'nulla']
+['pariatur.', 'Excepteur', 'sint', 'occaecat', 'cupidatat', 'non', 'proident,', 'sunt', 'in']
+['culpa', 'qui', 'officia', 'deserunt', 'mollit', 'anim', 'id', 'est', 'laborum.']
+[]
+
+
+
+
+

as mentioned earlier, there are lots of string functions. Above we used strip() to remove the trailing whitespace and returns

+
+
+

CSV Files#

+

comma-separated values are an easy way to exchange data – you can generate these from a spreadsheet program. In the example below, we are assuming that the first line of the spreadsheet/csv file gives the headings that identify the columns.

+

Note that there is an amazing amount of variation in terms of what can be in a CSV file and what the format is – the csv module does a good job sorting this all out for you.

+
+
+
class Item(object):
+    def __init__(self):
+        self.name = ""
+        self.quantity = 0
+        self.unitprice = 0.0
+        self.total = 0.0
+    
+import csv
+
+reader = csv.reader(open("shopping.csv", "r"))
+
+headings = None
+
+shopping_list = []
+
+for row in reader:
+    if headings == None:
+        # first row
+        headings = row
+    else:
+        my_item = Item()
+        my_item.name = row[headings.index("item")]
+        my_item.quantity = row[headings.index("quantity")]
+        my_item.unitprice = row[headings.index("unit price")]
+        my_item.total = row[headings.index("total")]
+        shopping_list.append(my_item)
+    
+
+for i in shopping_list:
+    print ("item: {}, quantity: {}, unit price: {}, total: {}".format(i.name, i.quantity, i.unitprice, i.total))
+
+
+
+
+
item: apples, quantity: 2, unit price: 0.33, total: 0.66
+item: bananas, quantity: 5, unit price: 0.1, total: 0.5
+item: milk, quantity: 1, unit price: 2.5, total: 2.5
+item: soda, quantity: 3, unit price: 1, total: 3
+item: rolls, quantity: 12, unit price: 0.33, total: 3.96
+item: eggs, quantity: 1, unit price: 2.5, total: 2.5
+
+
+
+
+
+
+

INI Files#

+

INI or Config files are a common way of specifying options to a program. They can take the form (from the ConfigParser page):

+
[My Section]
+foodir: %(dir)s/whatever
+dir=frob
+long: this value continues
+   in the next line
+
+
+

Here we look at how to read in options and store them in a dictionary of the +form dict["sec.option"] = value

+

We’ll use a sample .ini file from a regression test suite (VARDEN-tests.ini)

+

(Note: the name of the module is ConfigParser in python 2 but configparser in python 3 – the latter confirms to the python style guidelines.)

+
+
+
import configparser
+
+options = {}
+
+
+cp = configparser.ConfigParser()
+cp.optionxform = str    # this makes options case-sensitive
+cp.read("VARDEN-tests.ini")
+
+for sec in cp.sections():
+    for opt in cp.options(sec):
+        key = str(sec) + "." + str(opt)
+        value = cp.get(sec,opt)
+        options[key] = value
+    
+for k, v in options.items():
+    print("{:32s}: {}".format(k, v))
+
+
+
+
+
main.boxLibDir                  : /home/regtester/RegTesting/BoxLib/
+main.sourceDir                  : /home/regtester/RegTesting/VARDEN/
+main.testTopDir                 : /home/regtester/RegTesting/rt-VARDEN/
+main.webTopDir                  : /home/regtester/RegTesting/rt-VARDEN/web
+main.compareToolDir             : /home/regtester/RegTesting/AmrPostprocessing/F_Src
+main.MAKE                       : make
+main.sourceTree                 : F_Src
+main.numMakeJobs                : 8
+main.COMP                       : g++
+main.FCOMP                      : gfortran
+main.suiteName                  : VARDEN
+main.reportActiveTestsOnly      : 1
+main.goUpLink                   : 1
+main.MPIcommand                 : /usr/local/bin/mpiexec -n @nprocs@ @command@
+main.MPIhost                    : 
+bubble-2d.buildDir              : varden/test
+bubble-2d.inputFile             : inputs_2d-regt
+bubble-2d.dim                   : 2
+bubble-2d.restartTest           : 0
+bubble-2d.useMPI                : 1
+bubble-2d.numprocs              : 2
+bubble-2d.useOMP                : 0
+bubble-2d.numthreads            : 2
+bubble-2d.compileTest           : 0
+bubble-2d.doVis                 : 0
+bubble-3d.buildDir              : varden/test
+bubble-3d.inputFile             : inputs_3d-regt
+bubble-3d.dim                   : 3
+bubble-3d.restartTest           : 0
+bubble-3d.useMPI                : 1
+bubble-3d.numprocs              : 3
+bubble-3d.useOMP                : 1
+bubble-3d.numthreads            : 2
+bubble-3d.compileTest           : 0
+bubble-3d.doVis                 : 0
+bubble-restart.buildDir         : varden/test
+bubble-restart.inputFile        : inputs-restart-regt
+bubble-restart.dim              : 3
+bubble-restart.restartTest      : 1
+bubble-restart.restartFileNum   : 4
+bubble-restart.useMPI           : 1
+bubble-restart.numprocs         : 3
+bubble-restart.useOMP           : 1
+bubble-restart.numthreads       : 2
+bubble-restart.compileTest      : 0
+bubble-restart.doVis            : 0
+
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/01-python/python.html b/01-python/python.html new file mode 100644 index 00000000..c58f911c --- /dev/null +++ b/01-python/python.html @@ -0,0 +1,583 @@ + + + + + + + + + + + python — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

python

+ +
+
+ +
+
+
+ + + + +
+ +
+

python#

+

These notebooks introduce the core python language

+
+ + + + +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/01-python/using.html b/01-python/using.html new file mode 100644 index 00000000..f3b000fb --- /dev/null +++ b/01-python/using.html @@ -0,0 +1,668 @@ + + + + + + + + + + + Using These Notes — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Using These Notes

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

Using These Notes#

+

These notes are built via Jupyter book, as +a collection of Jupyter notebooks and markdown +pages.

+

The course is on Github at: +sbu-python-class/python-science, and the course +website is built automatically via a Github action each time a change +is pushed.

+

If you find any problems or have suggestions for improving the notes, +feel free to create an issue or pull request at the Github repo.

+
+

Interactive Usage#

+

For the Jupyter notebooks in this collection, there are a few ways to +access them to run them on your own.

+
    +
  • clicking on the icon in the upper right let’s +you download the raw notebook so you can run it on your local +computer.

  • +
  • clicking on the icon in the upper right will allow +you to run the notebook directly in the cloud. There are 2 different +compute clouds:

    +
      +
    • mybinder : this is an open project with +ties to the Jupyter project. It can take a few minutes for the +page to appear if it hasn’t been accessed recently, but then it +will give you the standard Jupyter experience.

    • +
    • Google colab : this is +Google’s version of an online notebook, which runs directly in +Google’s cloud. This starts up almost instantly.

    • +
    +
  • +
+
+

Note

+

Some notebooks use MyST Markdown to +allow for more styling. To see these styles, you need to install jupyterlab-myst, which +can be done via:

+
pip install jupyterlab_myst
+
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/01-python/w1-jupyter.html b/01-python/w1-jupyter.html new file mode 100644 index 00000000..851729d1 --- /dev/null +++ b/01-python/w1-jupyter.html @@ -0,0 +1,726 @@ + + + + + + + + + + + Jupyter — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Jupyter

+ +
+
+ +
+
+
+ + + + +
+ +
+

Jupyter#

+

We’ll be using Jupyter for all of our examples – this allows us to run python in a web-based notebook, keeping a history of input and output, along with text and images.

+

For Jupyter help, visit: +https://jupyter.readthedocs.io/en/latest/content-quickstart.html

+
+

Note

+

There are several interfaces to Jupyter.

+

We will use JupyterLab, which is traditionally started at the command line via:

+
jupyter lab
+
+
+

The older (classic) interface is Jupyter Notebook, which can be started via:

+
jupyter notebook
+
+
+
+

We interact with python by typing into cells in the notebook.

+

By default, a cell is a code cell, which means that you can enter any valid python code into it and run it.

+

Another important type of cell is a markdown cell. This lets you put text, with different formatting (italics, bold, etc) that describes what the notebook is doing.

+

A “markdown cell” enables you to typeset LaTeX equations right in your notebook. Just put them in $ or $$:

+
+\[\frac{\partial \rho}{\partial t} + \nabla \cdot (\rho U) = 0\]
+
+

Tip

+

You can change the cell type via the menu at the top, or using the shortcuts:

+
    +
  • ctrl-m m : mark down cell

  • +
  • ctrl-m y : code cell

  • +
+
+

Some useful short-cuts:

+
    +
  • shift+enter = run cell and jump to the next (creating a new cell if there is no other new one)

  • +
  • ctrl+enter = run cell-in place

  • +
  • alt+enter = run cell and insert a new one below

  • +
+
+

Warning

+

When you work through a notebook, everything you did in previous cells is still in memory and known by python, so you can refer to functions and variables that were previously defined. Even if you go up to the top of a notebook and insert a cell, all the information done earlier in your notebook session is still defined – it doesn’t matter where physically you are in the notebook. If you want to reset things, you can use the options under the Kernel menu.

+
+
+

Quick Exercise

+

Create a new cell below this one. Make sure that it is a code cell, and enter the following code and run it:

+

+ print("Hello, World")
+ 
+
+
+
+

print() is a function in python that takes arguments (in the ()) and outputs to the screen. You can print multiple quantities at once like:

+
+
+
print(1, 2, 3)
+
+
+
+
+
1 2 3
+
+
+
+
+

Note that the default behavior in Jupyter is to print the return value from the last statement in a cell, so we don’t need to print if we just want the value of something like:

+
+
+
a = 10
+a
+
+
+
+
+
10
+
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/01-python/w1-python-datatypes.html b/01-python/w1-python-datatypes.html new file mode 100644 index 00000000..401956d4 --- /dev/null +++ b/01-python/w1-python-datatypes.html @@ -0,0 +1,1355 @@ + + + + + + + + + + + Basic Python Datatypes — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Basic Python Datatypes

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

Basic Python Datatypes#

+

Python is a dynamically typed language – this means that you don’t +need to specify ahead of time what kind of data you are going to store +in a variable. Nevertheless, there are some core datatypes that we +need to become familiar with as we use the language.

+

The first set of datatypes are similar to those found in other +languages (like C/C++ and Fortran): floating point numbers, integers, +and strings.

+
+

Tip

+

Floating point is essential for computational science. A great +introduction to floating point and its limitations is: What every +computer scientist should know about floating-point +arithmetic by +D. Goldberg.

+
+

The next set of datatypes are containers. In python, unlike some +languages, these are built into the language and make it very easy to +do complex operations. We’ll look at these later.

+

Some examples come from the python tutorial: +http://docs.python.org/3/tutorial/

+
+

integers#

+

Integers are numbers without a decimal point. They can be positive or negative. Most programming languages use a finite-amount of memory to store a single integer, but in python will expand the amount of memory as necessary to store large integers.

+

The basic operators, +, -, *, and / work with integers

+
+
+
2+2+3
+
+
+
+
+
7
+
+
+
+
+
+
+
2*-4
+
+
+
+
+
-8
+
+
+
+
+
+

Note

+

Integer division is one place where python and other programming languages differ. +In python, dividing two integers results in a float. In C/C++/Fortran, dividing two integers results in an integer, so 1/2 = 0.

+
+
+
+
1/2
+
+
+
+
+
0.5
+
+
+
+
+

To get an integer result, we can use the // operator.

+
+
+
1//2
+
+
+
+
+
0
+
+
+
+
+

Python is a dynamically-typed language—this means that we do not need to declare the datatype of a variable before initializing it.

+

Here we’ll create a variable (think of it as a descriptive label that can refer to some piece of data). The = operator assigns a value to a variable.

+
+
+
a = 1
+b = 2
+
+
+
+
+

Functions operate on variables and return a result. Here, print() will output to the screen.

+
+
+
a + b
+
+
+
+
+
3
+
+
+
+
+
+
+
a * b
+
+
+
+
+
2
+
+
+
+
+

Note that variable names are case sensitive, so a and A are different

+
+
+
A = 2048
+
+
+
+
+
+
+
print(a, A)
+
+
+
+
+
1 2048
+
+
+
+
+

Here we initialize 3 variable all to 0, but these are still distinct variables, so we can change one without affecting the others.

+
+
+
x = y = z = 0
+
+
+
+
+
+
+
print(x, y, z)
+
+
+
+
+
0 0 0
+
+
+
+
+
+
+
z = 1
+
+
+
+
+
+
+
z
+
+
+
+
+
1
+
+
+
+
+

Python has some built in help (and Jupyter/ipython has even more)

+

try doing:

+
help(x)
+
+
+

alternatively, try:

+
x?
+
+
+

(this only works in Jupyter)

+

Another function, type() returns the data type of a variable

+
+
+
type(x)
+
+
+
+
+
int
+
+
+
+
+
+

Note

+

In languages like Fortran and C, you specify the amount of memory an integer can take (usually 2 or 4 bytes). This puts a restriction on the largest size integer that can be represented. Python will adapt the size of the integer so you don’t overflow

+
+
+
+
a = 12345678901234567890123456789012345123456789012345678901234567890
+print(a)
+print(a.bit_length())
+print(type(a))
+
+
+
+
+
12345678901234567890123456789012345123456789012345678901234567890
+213
+<class 'int'>
+
+
+
+
+
+
+

floating point#

+

when operating with both floating point and integers, the result is promoted to a float.

+
+
+
1. + 2
+
+
+
+
+
3.0
+
+
+
+
+

but note the special integer division operator

+
+
+
1.//2
+
+
+
+
+
0.0
+
+
+
+
+
+

Important

+

Not every number can be represented in floating point. Since there are infinitely many real numbers between any two bounds but we are using a finite amount of memory, on a computer we have to approximate numbers. There is an IEEE standard for floating point that pretty much all languages and processors follow.

+

The means two things

+
    +
  • not every real number will have an exact representation in floating point

  • +
  • there is a finite precision to numbers – below this we lose track of differences (this is usually called roundoff error)

  • +
+
+

Consider the following expression, for example:

+
+
+
0.3/0.1 - 3
+
+
+
+
+
-4.440892098500626e-16
+
+
+
+
+

Here’s another example: The number 0.1 cannot be exactly represented on a computer. In our print, we use a format specifier (the stuff inside of the {}) to ask for more precision to be shown:

+
+
+
a = 0.1
+print("{:30.20}".format(a))
+
+
+
+
+
        0.10000000000000000555
+
+
+
+
+

we can ask python to report the limits on floating point

+
+
+
import sys
+sys.float_info
+
+
+
+
+
sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)
+
+
+
+
+

Note that this says that we can only store numbers between 2.2250738585072014e-308 and 1.7976931348623157e+308

+

We also see that the precision is 2.220446049250313e-16 (this is commonly called machine epsilon). To see this, consider adding a small number to 1.0. We’ll use the equality operator (==) to test if two numbers are equal:

+
+

Quick Exercise

+

Define two variables, \(a = 1\), and \(e = 10^{-16}\).

+

Now define a third variable, b = a + e

+

We can use the python == operator to test for equality. What do you expect b == a to return? run it an see if it agrees with your guess.

+
+
+
+

modules#

+

The core python language is extended by a standard library that provides additional functionality. These added pieces are in the form of modules that we can import into our python session (or program).

+

The math module provides functions that do the basic mathematical operations as well as provide constants (note there is a separate cmath module for complex numbers).

+

In python, you import a module. The functions are then defined in a separate namespace—this is a separate region that defines names and variables, etc. A variable in one namespace can have the same name as a variable in a different namespace, and they don’t clash. You use the “.” operator to access a member of a namespace.

+

By default, when you type stuff into the python interpreter or here in the Jupyter notebook, or in a script, it is in its own default namespace, and you don’t need to prefix any of the variables with a namespace indicator.

+
+
+
import math
+
+
+
+
+

math provides the value of pi

+
+
+
math.pi
+
+
+
+
+
3.141592653589793
+
+
+
+
+

This is distinct from any variable pi we might define here

+
+
+
pi = 3
+
+
+
+
+
+
+
print(pi, math.pi)
+
+
+
+
+
3 3.141592653589793
+
+
+
+
+

Note here that pi and math.pi are distinct from one another—they are in different namespaces.

+
+

floating point operations#

+

The same operators, +, -, *, / work are usual for floating point numbers. To raise an number to a power, we use the ** operator (this is the same as Fortran)

+
+
+
R = 2.0
+
+
+
+
+
+
+
math.pi * R**2
+
+
+
+
+
12.566370614359172
+
+
+
+
+

operator precedence follows that of most languages. See

+

https://docs.python.org/3/reference/expressions.html#operator-precedence

+

in order of precedence:

+
    +
  • quantities in ()

  • +
  • slicing, calls, subscripts

  • +
  • exponentiation (**)

  • +
  • +x, -x, ~x

  • +
  • *, @, /, //, %

  • +
  • +, -

  • +
+

(after this are bitwise operations and comparisons)

+

Parentheses can be used to override the precedence.

+
+

Quick Exercise

+

Consider the following expressions. Using the ideas of precedence, think about what value will result, then try it out in the cell below to see if you were right.

+
    +
  • 1 + 3*2**2

  • +
  • 1 + (3*2)**2

  • +
  • 2**3**2

  • +
+
+

The math module provides a lot of the standard math functions we might want to use.

+

For the trig functions, the expectation is that the argument to the function is in radians—you can use math.radians() to convert from degrees to radians, ex:

+
+
+
math.cos(math.radians(45))
+
+
+
+
+
0.7071067811865476
+
+
+
+
+

Notice that in that statement we are feeding the output of one function (math.radians()) into a second function, math.cos()

+

When in doubt, as for help to discover all of the things a module provides:

+
+
+
help(math.sin)
+
+
+
+
+
Help on built-in function sin in module math:
+
+sin(x, /)
+    Return the sine of x (measured in radians).
+
+
+
+
+
+
+
+

strings#

+

python doesn’t care if you use single or double quotes for strings:

+
+
+
a = "this is my string"
+b = 'another string'
+
+
+
+
+
+
+
print(a)
+print(b)
+
+
+
+
+
this is my string
+another string
+
+
+
+
+

Many of the usual mathematical operators are defined for strings as well. For example to concatenate or duplicate:

+
+
+
a + b
+
+
+
+
+
'this is my stringanother string'
+
+
+
+
+
+
+
a + ". " + b
+
+
+
+
+
'this is my string. another string'
+
+
+
+
+
+
+
a * 2
+
+
+
+
+
'this is my stringthis is my string'
+
+
+
+
+

There are several escape codes that are interpreted in strings. These start with a backwards-slash, \. E.g., you can use \n for new line

+
+
+
a = a + "\n"
+print(a)
+
+
+
+
+
this is my string
+
+
+
+
+
+

Quick Exercise

+

The input() function can be used to ask the user for input.

+
    +
  • Use help(input) to see how it works.

  • +
  • Write code to ask for input and store the result in a variable. input() will return a string.

  • +
  • Use the float() function to convert a number entered as input to a floating point variable.

  • +
  • Check to see if the conversion worked using the type() function.

  • +
+
+

“”” can enclose multiline strings. This is useful for docstrings at the start of functions (more on that later…)

+
+
+
c = """
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor 
+incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis 
+nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. 
+Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore 
+eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt 
+in culpa qui officia deserunt mollit anim id est laborum."""
+
+
+
+
+
+
+
print(c)
+
+
+
+
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor 
+incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis 
+nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. 
+Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore 
+eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt 
+in culpa qui officia deserunt mollit anim id est laborum.
+
+
+
+
+

a raw string does not replace escape sequences (like \n). Just put a r before the first quote:

+
+
+
d = r"this is a raw string\n"
+d
+
+
+
+
+
'this is a raw string\\n'
+
+
+
+
+

slicing is used to access a portion of a string.

+

slicing a string can seem a bit counterintuitive if you are coming from Fortran. The trick is to think of the index as representing the left edge of a character in the string. When we do arrays later, the same will apply.

+

Also note that python (like C) uses 0-based indexing

+

Negative indices count from the right.

+
+
+
a = "this is my string"
+print(a)
+print(a[5:7])
+print(a[0])
+print(d)
+print(d[-2])
+
+
+
+
+
this is my string
+is
+t
+this is a raw string\n
+\
+
+
+
+
+
+

Quick Exercise:

+

Strings have a lot of methods (functions that know how to work with a particular datatype, in this case strings). A useful method is .find(). For a string a, +a.find(s) will return the index of the first occurrence of s.

+

For our string c above, find the first . (identifying the first full sentence), and print out just the first sentence in c using this result

+
+

there are also a number of methods and functions that work with strings. Here are some examples:

+
+
+
print(a.replace("this", "that"))
+print(len(a))
+print(a.strip())    # Also notice that strip removes the \n
+print(a.strip()[-1])
+
+
+
+
+
that is my string
+17
+this is my string
+g
+
+
+
+
+

Note that our original string, a, has not changed. In python, strings are immutable. Operations on strings return a new string.

+
+
+
a
+
+
+
+
+
'this is my string'
+
+
+
+
+
+
+
type(a)
+
+
+
+
+
str
+
+
+
+
+

We can format strings when we are printing to insert quantities in particular places in the string. A {} serves as a placeholder for a quantity and is replaced using the .format() method:

+
+
+
a = 1
+b = 2.0
+c = "test"
+print("a = {}; b = {}; c = {}".format(a, b, c))
+
+
+
+
+
a = 1; b = 2.0; c = test
+
+
+
+
+

But the more modern way to do this is to use f-strings

+
+
+
print(f"a = {a}; b = {b}; c = {c}")
+
+
+
+
+
a = 1; b = 2.0; c = test
+
+
+
+
+

Note the f preceding the starting "

+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/01-python/w2-python-advanced-datatypes.html b/01-python/w2-python-advanced-datatypes.html new file mode 100644 index 00000000..c17fb688 --- /dev/null +++ b/01-python/w2-python-advanced-datatypes.html @@ -0,0 +1,1130 @@ + + + + + + + + + + + Advanced Datatypes — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Advanced Datatypes

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

Advanced Datatypes#

+

These notes follow the official python tutorial pretty closely: http://docs.python.org/3/tutorial/

+
+

Lists#

+

Lists group together data. Many languages have arrays (we’ll look at those in a bit in python). But unlike arrays in most languages, lists can hold data of all different types – they don’t need to be homogeneos. The data can be a mix of integers, floating point or complex #s, strings, or other objects (including other lists).

+

A list is defined using square brackets:

+
+
+
a = [1, 2.0, "my list", 4]
+
+
+
+
+
+
+
a
+
+
+
+
+
[1, 2.0, 'my list', 4]
+
+
+
+
+

We can index a list to get a single element – remember that python starts counting at 0:

+
+
+
a[2]
+
+
+
+
+
'my list'
+
+
+
+
+

Like with strings, mathematical operators are defined on lists:

+
+
+
a*2
+
+
+
+
+
[1, 2.0, 'my list', 4, 1, 2.0, 'my list', 4]
+
+
+
+
+

The len() function returns the length of a list

+
+
+
len(a)
+
+
+
+
+
4
+
+
+
+
+

Unlike strings, lists are mutable – you can change elements in a list easily

+
+
+
a[1] = -2.0
+a
+
+
+
+
+
[1, -2.0, 'my list', 4]
+
+
+
+
+

Just like everything else in python, a list is an object that is the instance of a class. Classes have methods (functions) that know how to operate on an object of that class.

+

There are lots of methods that work on lists. Two of the most useful are append, to add to the end of a list, and pop, to remove the last element:

+
+
+
a.append(6)
+a
+
+
+
+
+
[1, -2.0, 'my list', 4, 6]
+
+
+
+
+
+
+
a.pop()
+
+
+
+
+
6
+
+
+
+
+
+
+
a
+
+
+
+
+
[1, -2.0, 'my list', 4]
+
+
+
+
+
+

Quick Exercise

+

An operation we’ll see a lot is to begin with an empty list and add elements to it. An empty list is created as:

+

+ a = []
+ 
+
+
+
    +
  • Create an empty list

  • +
  • Append the integers 1 through 5 to it.

  • +
  • Now pop them out of the list one by one.

  • +
+
+
+

copying lists#

+

copying may seem a little counterintuitive at first. The best way to think about this is that your list lives in memory somewhere and when you do

+
a = [1, 2, 3, 4]
+
+
+

then the variable a is set to point to that location in memory, so it refers to the list.

+

If we then do

+
b = a
+
+
+

then b will also point to that same location in memory – the exact same list object.

+

Since these are both pointing to the same location in memory, if we change the list through a, the change is reflected in b as well:

+
+
+
a = [1, 2, 3, 4]
+b = a  # both a and b refer to the same list object in memory
+print(a)
+a[0] = "changed"
+print(b)
+
+
+
+
+
[1, 2, 3, 4]
+['changed', 2, 3, 4]
+
+
+
+
+

if you want to create a new object in memory that is a copy of another, then you can either index the list, using : to get all the elements, or use the list() function:

+
+
+
c = list(a)   # you can also do c = a[:], which basically slices the entire list
+a[1] = "two"
+print(a)
+print(c)
+
+
+
+
+
['changed', 'two', 3, 4]
+['changed', 2, 3, 4]
+
+
+
+
+

Things get a little complicated when a list contains another mutable object, like another list. Then the copy we looked at above is only a shallow copy.

+

When in doubt, use the id() function to figure out where in memory an object lies (you shouldn’t worry about the what value of the numbers you get from id mean, but just whether they are the same as those for another object)

+
+
+
print(id(a), id(b), id(c))
+
+
+
+
+
140465577151744 140465577151744 140465577151040
+
+
+
+
+

Or use the is operator

+
+
+
a is b
+
+
+
+
+
True
+
+
+
+
+
+
+
a is c
+
+
+
+
+
False
+
+
+
+
+

There are lots of other methods that work on lists (remember, ask for help)

+
+
+
my_list = [10, -1, 5, 24, 2, -1, 9]
+my_list.sort()
+my_list
+
+
+
+
+
[-1, -1, 2, 5, 9, 10, 24]
+
+
+
+
+
+
+
my_list.count(-1)
+
+
+
+
+
2
+
+
+
+
+

We can also insert elements

+
+
+
a.insert(3, "my inserted element")
+a
+
+
+
+
+
['changed', 'two', 3, 'my inserted element', 4]
+
+
+
+
+

joining two lists is simple. Like with strings, the + operator concatenates:

+
+
+
b = [1, 2, 3]
+c = [4, 5, 6]
+d = b + c
+d
+
+
+
+
+
[1, 2, 3, 4, 5, 6]
+
+
+
+
+
+
+
+

Dictionaries#

+

A dictionary stores data as a key:value pair. Unlike a list where you have a particular order, the keys in a dictionary allow you to access information anywhere easily:

+
+
+
my_dict = {"key1":1, "key2":2, "key3":3}
+
+
+
+
+
+
+
my_dict["key1"]
+
+
+
+
+
1
+
+
+
+
+

you can add a new key:pair easily, and it can be of any type

+
+
+
my_dict["newkey"] = "new"
+my_dict
+
+
+
+
+
{'key1': 1, 'key2': 2, 'key3': 3, 'newkey': 'new'}
+
+
+
+
+

You can also easily get the list of keys that are defined in a dictionary

+
+
+
keys = list(my_dict.keys())
+keys
+
+
+
+
+
['key1', 'key2', 'key3', 'newkey']
+
+
+
+
+

and check easily whether a key exists in the dictionary using the in operator

+
+
+
print("key1" in my_dict)
+print("invalidKey" in my_dict)
+
+
+
+
+
True
+False
+
+
+
+
+
+

Quick Exercise

+

Create a dictionary where the keys are the string names of the numbers zero to nine and the values are their numeric representation (0, 1, … , 5)

+
+
+
+

Tuples#

+

tuples are immutable – they cannot be changed, but they are useful for organizing data in some situations. We use () to indicate a tuple:

+
+
+
a = (1, 2, 3, 4)
+a
+
+
+
+
+
(1, 2, 3, 4)
+
+
+
+
+

We can unpack a tuple:

+
+
+
w, x, y, z = a
+
+
+
+
+
+
+
w
+
+
+
+
+
1
+
+
+
+
+

Since a tuple is immutable, we cannot change an element:

+
+
+
a[0] = 2
+
+
+
+
+
---------------------------------------------------------------------------
+TypeError                                 Traceback (most recent call last)
+Cell In[27], line 1
+----> 1 a[0] = 2
+
+TypeError: 'tuple' object does not support item assignment
+
+
+
+
+

But we can turn it into a list, and then we can change it

+
+
+
z = list(a)
+
+
+
+
+
+
+
z[0] = "new"
+
+
+
+
+
+
+
z
+
+
+
+
+
['new', 2, 3, 4]
+
+
+
+
+

It is often not clear how tuples differ from lists. The most obvious way is that they are immutable. Often we’ll see tuples used to store related data that should all be interpreted together. A good example is a Cartesian point, (x, y). Here is a list of points:

+
+
+
points = []
+points.append((1,2))
+points.append((2,3))
+points.append((3,4))
+points
+
+
+
+
+
[(1, 2), (2, 3), (3, 4)]
+
+
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/01-python/w2-python-control-flow.html b/01-python/w2-python-control-flow.html new file mode 100644 index 00000000..144c9dee --- /dev/null +++ b/01-python/w2-python-control-flow.html @@ -0,0 +1,933 @@ + + + + + + + + + + + Control Flow — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Control Flow

+ +
+ +
+
+ + + + +
+ +
+

Control Flow#

+

These notes follow the official python tutorial pretty closely: http://docs.python.org/3/tutorial/

+

To write a program, we need the ability to iterate and take action based on the values of a variable. This includes if-tests and loops.

+

Python uses whitespace to denote a block of code.

+
+

While loop#

+

A simple while loop—notice the indentation to denote the block that is part of the loop.

+

Here we also use the compact += operator: n += 1 is the same as n = n + 1

+
+
+
n = 0
+while n < 10:
+    print(n)
+    n += 1
+
+
+
+
+
0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+
+
+
+
+

This was a very simple example. But often we’ll use the range() function in this situation. Note that range() can take a stride.

+
+
+
for n in range(2, 10, 2):
+    print(n)
+
+
+
+
+
2
+4
+6
+8
+
+
+
+
+
+
+

if statements#

+

if allows for branching. python does not have a select/case statement like some other languages, but if, elif, and else can reproduce any branching functionality you might need.

+
+
+
x = 0
+
+if x < 0:
+    print("negative")
+elif x == 0:
+    print("zero")
+else:
+    print("positive")
+
+
+
+
+
zero
+
+
+
+
+
+
+

Iterating over elements#

+

it’s easy to loop over items in a list or any iterable object. The in operator is the key here.

+
+
+
alist = [1, 2.0, "three", 4]
+for a in alist:
+    print(a)
+
+
+
+
+
1
+2.0
+three
+4
+
+
+
+
+
+
+
for c in "this is a string":
+    print(c)
+
+
+
+
+
t
+h
+i
+s
+ 
+i
+s
+ 
+a
+ 
+s
+t
+r
+i
+n
+g
+
+
+
+
+

We can combine loops and if-tests to do more complex logic, like break out of the loop when you find what you’re looking for

+
+
+
n = 0
+for a in alist:
+    if a == "three":
+        break
+    else:
+        n += 1
+
+print(n)
+
+
+
+
+
2
+
+
+
+
+

(for that example, however, there is a simpler way)

+
+
+
alist.index("three")
+
+
+
+
+
2
+
+
+
+
+

for dictionaries, you can also loop over the elements

+
+
+
my_dict = {"key1":1, "key2":2, "key3":3}
+
+for k, v in my_dict.items():
+    print(f"key = {k}, value = {v}")
+
+
+
+
+
key = key1, value = 1
+key = key2, value = 2
+key = key3, value = 3
+
+
+
+
+
+
+
for k in sorted(my_dict):
+    print(k, my_dict[k])
+
+
+
+
+
key1 1
+key2 2
+key3 3
+
+
+
+
+

sometimes we want to loop over a list element and know its index – enumerate() helps here:

+
+
+
for n, a in enumerate(alist):
+    print(n, a)
+
+
+
+
+
0 1
+1 2.0
+2 three
+3 4
+
+
+
+
+
+

Quick Exercise

+

zip() allows us to loop over two iterables at the same time. Consider the following two +lists:

+

+ a = [1, 2, 3, 4, 5, 6, 7, 8]
+ b = ["a", "b", "c", "d", "e", "f", "g", "h"]
+ 
+
+
+

zip(a, b) will act like a list with each element a tuple with one item from a and the corresponding element from b.

+

Try looping over these lists together (using zip()) and print the corresponding elements from each list together on a single line.

+
+
+

Quick Exercise

+

The .split() function on a string can split it into words (separating on spaces).

+

Using .split(), loop over the words in the string

+

a = "The quick brown fox jumped over the lazy dog"

+

and print one word per line

+
+
+
+

List Comprehensions#

+

list comprehensions provide a compact way to initialize lists. Some examples from the tutorial

+
+
+
squares = [x**2 for x in range(10)]
+
+
+
+
+
+
+
squares
+
+
+
+
+
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
+
+
+
+
+
+

Quick Exercise

+

Use a list comprehension to create a new list from squares containing only the even numbers. It might be helpful to use the modulus operator, %

+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/01-python/w2-python-exercises.html b/01-python/w2-python-exercises.html new file mode 100644 index 00000000..0985f65c --- /dev/null +++ b/01-python/w2-python-exercises.html @@ -0,0 +1,1507 @@ + + + + + + + + + + + Exercises — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Exercises

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

Exercises#

+
+

Q 1#

+

When talking about floating point, we discussed machine epsilon, \(\epsilon\)—this is the smallest number that when added to 1 is still different from 1.

+

We’ll compute \(\epsilon\) here:

+
    +
  • Pick an initial guess for \(\epsilon\) of eps = 1.

  • +
  • Create a loop that checks whether 1 + eps is different from 1

  • +
  • Each loop iteration, cut the value of eps in half

  • +
+

What value of \(\epsilon\) do you find?

+
+
+

Q 2#

+

To iterate over the tuples, where the i-th tuple contains the i-th elements of certain sequences, we can use zip(*sequences) function.

+

We will iterate over two lists, names and age, and print out the resulting tuples.

+
    +
  • Start by initializing lists names = ["Mary", "John", "Sarah"] and age = [21, 56, 98].

  • +
  • Iterate over the tuples containing a name and an age, the zip(list1, list2) function might be useful here.

  • +
  • Print out formatted strings of the type “NAME is AGE years old”.

  • +
+
+
+

Q 3#

+

The function enumerate(sequence) returns tuples containing indices of objects in the sequence, and the objects.

+

The random module provides tools for working with the random numbers. In particular, random.randint(start, end) generates a random number not smaller than start, and not bigger than end.

+
    +
  • Generate a list of 10 random numbers from 0 to 9.

  • +
  • Using the enumerate(random_list) function, iterate over the tuples of random numbers and their indices, and print out “Match: NUMBER and INDEX” if the random number and its index in the list match.

  • +
+
+
+
import random
+
+random_number = random.randint(0,9)
+print(random_number)
+
+
+
+
+
1
+
+
+
+
+
+
+

Q 4#

+

The Fibbonacci sequence is a numerical sequence where each number is the sum of the 2 preceding numbers, e.g., 1, 1, 2, 3, 5, 8, 13, …

+

Create a list where the elements are the terms in the Fibbonacci sequence:

+
    +
  • Start with the list fib = [1, 1]

  • +
  • Loop 25 times, compute the next term as the sum of the previous 2 terms and append to the list

  • +
  • After the loop is complete, print out the terms

  • +
+

You may find it useful to use fib[-1] and fib[-2] to access the last to items in the list

+
+
+

Q 5#

+

We can use the input() function to ask for input from the prompt (note: in python 2 the function was called raw_input()).

+

Create an empty list and use a while loop to ask the user for input and append their input to the list. Keep looping until 10 items are added to the list

+
+
+

Q 6#

+

Here is a list of book titles (from http://thegreatestbooks.org). Loop through the list and capitalize each word in each title. You might find the .capitalize() method that works on strings useful.

+
+
+
titles = ["don quixote", 
+          "in search of lost time", 
+          "ulysses", 
+          "the odyssey", 
+          "war and piece", 
+          "moby dick", 
+          "the divine comedy", 
+          "hamlet", 
+          "the adventures of huckleberry finn", 
+          "the great gatsby"]
+
+
+
+
+
+
+

Q 7#

+

Here’s some text (the Gettysburg Address). Our goal is to count how many times each word repeats. We’ll do a brute force method first, and then we’ll look a ways to do it more efficiently (and compactly).

+
+
+
gettysburg_address = """
+Four score and seven years ago our fathers brought forth on this continent, 
+a new nation, conceived in Liberty, and dedicated to the proposition that 
+all men are created equal.
+
+Now we are engaged in a great civil war, testing whether that nation, or 
+any nation so conceived and so dedicated, can long endure. We are met on
+a great battle-field of that war. We have come to dedicate a portion of
+that field, as a final resting place for those who here gave their lives
+that that nation might live. It is altogether fitting and proper that we
+should do this.
+
+But, in a larger sense, we can not dedicate -- we can not consecrate -- we
+can not hallow -- this ground. The brave men, living and dead, who struggled
+here, have consecrated it, far above our poor power to add or detract.  The
+world will little note, nor long remember what we say here, but it can never
+forget what they did here. It is for us the living, rather, to be dedicated
+here to the unfinished work which they who fought here have thus far so nobly
+advanced. It is rather for us to be here dedicated to the great task remaining
+before us -- that from these honored dead we take increased devotion to that
+cause for which they gave the last full measure of devotion -- that we here
+highly resolve that these dead shall not have died in vain -- that this
+nation, under God, shall have a new birth of freedom -- and that government
+of the people, by the people, for the people, shall not perish from the earth.
+"""
+
+
+
+
+

We’ve already seen the .split() method will, by default, split by spaces, so it will split this into words, producing a list:

+
+
+
ga = gettysburg_address.split()
+
+
+
+
+
+
+
ga
+
+
+
+
+
['Four',
+ 'score',
+ 'and',
+ 'seven',
+ 'years',
+ 'ago',
+ 'our',
+ 'fathers',
+ 'brought',
+ 'forth',
+ 'on',
+ 'this',
+ 'continent,',
+ 'a',
+ 'new',
+ 'nation,',
+ 'conceived',
+ 'in',
+ 'Liberty,',
+ 'and',
+ 'dedicated',
+ 'to',
+ 'the',
+ 'proposition',
+ 'that',
+ 'all',
+ 'men',
+ 'are',
+ 'created',
+ 'equal.',
+ 'Now',
+ 'we',
+ 'are',
+ 'engaged',
+ 'in',
+ 'a',
+ 'great',
+ 'civil',
+ 'war,',
+ 'testing',
+ 'whether',
+ 'that',
+ 'nation,',
+ 'or',
+ 'any',
+ 'nation',
+ 'so',
+ 'conceived',
+ 'and',
+ 'so',
+ 'dedicated,',
+ 'can',
+ 'long',
+ 'endure.',
+ 'We',
+ 'are',
+ 'met',
+ 'on',
+ 'a',
+ 'great',
+ 'battle-field',
+ 'of',
+ 'that',
+ 'war.',
+ 'We',
+ 'have',
+ 'come',
+ 'to',
+ 'dedicate',
+ 'a',
+ 'portion',
+ 'of',
+ 'that',
+ 'field,',
+ 'as',
+ 'a',
+ 'final',
+ 'resting',
+ 'place',
+ 'for',
+ 'those',
+ 'who',
+ 'here',
+ 'gave',
+ 'their',
+ 'lives',
+ 'that',
+ 'that',
+ 'nation',
+ 'might',
+ 'live.',
+ 'It',
+ 'is',
+ 'altogether',
+ 'fitting',
+ 'and',
+ 'proper',
+ 'that',
+ 'we',
+ 'should',
+ 'do',
+ 'this.',
+ 'But,',
+ 'in',
+ 'a',
+ 'larger',
+ 'sense,',
+ 'we',
+ 'can',
+ 'not',
+ 'dedicate',
+ '--',
+ 'we',
+ 'can',
+ 'not',
+ 'consecrate',
+ '--',
+ 'we',
+ 'can',
+ 'not',
+ 'hallow',
+ '--',
+ 'this',
+ 'ground.',
+ 'The',
+ 'brave',
+ 'men,',
+ 'living',
+ 'and',
+ 'dead,',
+ 'who',
+ 'struggled',
+ 'here,',
+ 'have',
+ 'consecrated',
+ 'it,',
+ 'far',
+ 'above',
+ 'our',
+ 'poor',
+ 'power',
+ 'to',
+ 'add',
+ 'or',
+ 'detract.',
+ 'The',
+ 'world',
+ 'will',
+ 'little',
+ 'note,',
+ 'nor',
+ 'long',
+ 'remember',
+ 'what',
+ 'we',
+ 'say',
+ 'here,',
+ 'but',
+ 'it',
+ 'can',
+ 'never',
+ 'forget',
+ 'what',
+ 'they',
+ 'did',
+ 'here.',
+ 'It',
+ 'is',
+ 'for',
+ 'us',
+ 'the',
+ 'living,',
+ 'rather,',
+ 'to',
+ 'be',
+ 'dedicated',
+ 'here',
+ 'to',
+ 'the',
+ 'unfinished',
+ 'work',
+ 'which',
+ 'they',
+ 'who',
+ 'fought',
+ 'here',
+ 'have',
+ 'thus',
+ 'far',
+ 'so',
+ 'nobly',
+ 'advanced.',
+ 'It',
+ 'is',
+ 'rather',
+ 'for',
+ 'us',
+ 'to',
+ 'be',
+ 'here',
+ 'dedicated',
+ 'to',
+ 'the',
+ 'great',
+ 'task',
+ 'remaining',
+ 'before',
+ 'us',
+ '--',
+ 'that',
+ 'from',
+ 'these',
+ 'honored',
+ 'dead',
+ 'we',
+ 'take',
+ 'increased',
+ 'devotion',
+ 'to',
+ 'that',
+ 'cause',
+ 'for',
+ 'which',
+ 'they',
+ 'gave',
+ 'the',
+ 'last',
+ 'full',
+ 'measure',
+ 'of',
+ 'devotion',
+ '--',
+ 'that',
+ 'we',
+ 'here',
+ 'highly',
+ 'resolve',
+ 'that',
+ 'these',
+ 'dead',
+ 'shall',
+ 'not',
+ 'have',
+ 'died',
+ 'in',
+ 'vain',
+ '--',
+ 'that',
+ 'this',
+ 'nation,',
+ 'under',
+ 'God,',
+ 'shall',
+ 'have',
+ 'a',
+ 'new',
+ 'birth',
+ 'of',
+ 'freedom',
+ '--',
+ 'and',
+ 'that',
+ 'government',
+ 'of',
+ 'the',
+ 'people,',
+ 'by',
+ 'the',
+ 'people,',
+ 'for',
+ 'the',
+ 'people,',
+ 'shall',
+ 'not',
+ 'perish',
+ 'from',
+ 'the',
+ 'earth.']
+
+
+
+
+

Now, the next problem is that some of these still have punctuation. In particular, we see “.”, “,”, and “--“.

+

When considering a word, we can get rid of these by using the replace() method:

+
+
+
a = "end.,"
+b = a.replace(".", "").replace(",", "")
+b
+
+
+
+
+
'end'
+
+
+
+
+

Another problem is case—we want to count “but” and “But” as the same. Strings have a lower() method that can be used to convert a string:

+
+
+
a = "But"
+b = "but"
+a == b
+
+
+
+
+
False
+
+
+
+
+
+
+
a.lower() == b.lower()
+
+
+
+
+
True
+
+
+
+
+

Recall that strings are immutable, so replace() produces a new string on output.

+
+
+

your task#

+

Create a dictionary that uses the unique words as keys and has as a value the number of times that word appears.

+

Write a loop over the words in the string (using our split version) and do the following:

+
    +
  • remove any punctuation

  • +
  • convert to lowercase

  • +
  • test if the word is already a key in the dictionary (using the in operator)

    +
      +
    • if the key exists, increment the word count for that key

    • +
    • otherwise, add it to the dictionary with the appropriate count of 1.

    • +
    +
  • +
+

At the end, print out the words and a count of how many times they appear

+
+
+
# your code here
+
+
+
+
+
+
+

More compact way#

+

We can actually do this a lot more compactly by using another list comprehensions and another python datatype called a set. A set is a group of items, where each item is unique (e.g., no repetitions).

+

Here’s a list comprehension that removes all the punctuation and converts to lower case:

+
+
+
words = [q.lower().replace(".", "").replace(",", "") for q in ga]
+
+
+
+
+

and by using the set() function, we turn the list into a set, removing any duplicates:

+
+
+
unique_words = set(words)
+
+
+
+
+

now we can loop over the unique words and use the count method of a list to find how many there are

+
+
+
count = {}
+for uw in unique_words:
+    count[uw] = words.count(uw)
+    
+count
+
+
+
+
+
{'those': 1,
+ 'by': 1,
+ 'resting': 1,
+ 'hallow': 1,
+ 'larger': 1,
+ 'portion': 1,
+ 'for': 5,
+ 'remaining': 1,
+ 'increased': 1,
+ 'died': 1,
+ 'live': 1,
+ 'from': 2,
+ 'battle-field': 1,
+ 'have': 5,
+ 'proper': 1,
+ 'detract': 1,
+ 'it': 5,
+ 'now': 1,
+ 'equal': 1,
+ 'might': 1,
+ 'consecrate': 1,
+ 'full': 1,
+ 'say': 1,
+ 'their': 1,
+ 'thus': 1,
+ 'government': 1,
+ 'whether': 1,
+ 'will': 1,
+ 'liberty': 1,
+ 'world': 1,
+ 'who': 3,
+ 'this': 4,
+ 'far': 2,
+ 'poor': 1,
+ 'remember': 1,
+ 'brought': 1,
+ 'so': 3,
+ 'dedicated': 4,
+ 'the': 11,
+ 'a': 7,
+ 'resolve': 1,
+ 'and': 6,
+ 'new': 2,
+ 'but': 2,
+ 'ground': 1,
+ 'rather': 2,
+ 'under': 1,
+ 'freedom': 1,
+ 'advanced': 1,
+ 'birth': 1,
+ 'cause': 1,
+ 'engaged': 1,
+ 'struggled': 1,
+ 'proposition': 1,
+ 'that': 13,
+ 'should': 1,
+ 'dedicate': 2,
+ 'nation': 5,
+ 'or': 2,
+ 'unfinished': 1,
+ 'score': 1,
+ 'be': 2,
+ 'task': 1,
+ 'what': 2,
+ 'all': 1,
+ 'measure': 1,
+ 'fought': 1,
+ 'did': 1,
+ 'are': 3,
+ 'men': 2,
+ 'dead': 3,
+ 'earth': 1,
+ 'which': 2,
+ 'last': 1,
+ 'they': 3,
+ 'lives': 1,
+ 'field': 1,
+ 'work': 1,
+ 'any': 1,
+ 'highly': 1,
+ 'civil': 1,
+ 'not': 5,
+ 'us': 3,
+ 'sense': 1,
+ 'fitting': 1,
+ 'continent': 1,
+ 'testing': 1,
+ 'honored': 1,
+ 'devotion': 2,
+ 'four': 1,
+ 'do': 1,
+ 'here': 8,
+ 'note': 1,
+ 'seven': 1,
+ 'final': 1,
+ 'is': 3,
+ 'on': 2,
+ 'shall': 3,
+ 'living': 2,
+ 'add': 1,
+ 'as': 1,
+ 'gave': 2,
+ 'can': 5,
+ 'ago': 1,
+ 'little': 1,
+ 'in': 4,
+ 'created': 1,
+ 'people': 3,
+ 'fathers': 1,
+ 'above': 1,
+ 'conceived': 2,
+ 'come': 1,
+ 'these': 2,
+ '--': 7,
+ 'long': 2,
+ 'great': 3,
+ 'vain': 1,
+ 'place': 1,
+ 'nor': 1,
+ 'never': 1,
+ 'our': 2,
+ 'altogether': 1,
+ 'god': 1,
+ 'of': 5,
+ 'brave': 1,
+ 'met': 1,
+ 'consecrated': 1,
+ 'forget': 1,
+ 'we': 10,
+ 'forth': 1,
+ 'years': 1,
+ 'perish': 1,
+ 'war': 2,
+ 'to': 8,
+ 'nobly': 1,
+ 'power': 1,
+ 'before': 1,
+ 'endure': 1,
+ 'take': 1}
+
+
+
+
+

Even shorter – we can use a dictionary comprehension, like a list comprehension

+
+
+
c = {uw: count[uw] for uw in unique_words}
+
+
+
+
+
+
+
c
+
+
+
+
+
{'those': 1,
+ 'by': 1,
+ 'resting': 1,
+ 'hallow': 1,
+ 'larger': 1,
+ 'portion': 1,
+ 'for': 5,
+ 'remaining': 1,
+ 'increased': 1,
+ 'died': 1,
+ 'live': 1,
+ 'from': 2,
+ 'battle-field': 1,
+ 'have': 5,
+ 'proper': 1,
+ 'detract': 1,
+ 'it': 5,
+ 'now': 1,
+ 'equal': 1,
+ 'might': 1,
+ 'consecrate': 1,
+ 'full': 1,
+ 'say': 1,
+ 'their': 1,
+ 'thus': 1,
+ 'government': 1,
+ 'whether': 1,
+ 'will': 1,
+ 'liberty': 1,
+ 'world': 1,
+ 'who': 3,
+ 'this': 4,
+ 'far': 2,
+ 'poor': 1,
+ 'remember': 1,
+ 'brought': 1,
+ 'so': 3,
+ 'dedicated': 4,
+ 'the': 11,
+ 'a': 7,
+ 'resolve': 1,
+ 'and': 6,
+ 'new': 2,
+ 'but': 2,
+ 'ground': 1,
+ 'rather': 2,
+ 'under': 1,
+ 'freedom': 1,
+ 'advanced': 1,
+ 'birth': 1,
+ 'cause': 1,
+ 'engaged': 1,
+ 'struggled': 1,
+ 'proposition': 1,
+ 'that': 13,
+ 'should': 1,
+ 'dedicate': 2,
+ 'nation': 5,
+ 'or': 2,
+ 'unfinished': 1,
+ 'score': 1,
+ 'be': 2,
+ 'task': 1,
+ 'what': 2,
+ 'all': 1,
+ 'measure': 1,
+ 'fought': 1,
+ 'did': 1,
+ 'are': 3,
+ 'men': 2,
+ 'dead': 3,
+ 'earth': 1,
+ 'which': 2,
+ 'last': 1,
+ 'they': 3,
+ 'lives': 1,
+ 'field': 1,
+ 'work': 1,
+ 'any': 1,
+ 'highly': 1,
+ 'civil': 1,
+ 'not': 5,
+ 'us': 3,
+ 'sense': 1,
+ 'fitting': 1,
+ 'continent': 1,
+ 'testing': 1,
+ 'honored': 1,
+ 'devotion': 2,
+ 'four': 1,
+ 'do': 1,
+ 'here': 8,
+ 'note': 1,
+ 'seven': 1,
+ 'final': 1,
+ 'is': 3,
+ 'on': 2,
+ 'shall': 3,
+ 'living': 2,
+ 'add': 1,
+ 'as': 1,
+ 'gave': 2,
+ 'can': 5,
+ 'ago': 1,
+ 'little': 1,
+ 'in': 4,
+ 'created': 1,
+ 'people': 3,
+ 'fathers': 1,
+ 'above': 1,
+ 'conceived': 2,
+ 'come': 1,
+ 'these': 2,
+ '--': 7,
+ 'long': 2,
+ 'great': 3,
+ 'vain': 1,
+ 'place': 1,
+ 'nor': 1,
+ 'never': 1,
+ 'our': 2,
+ 'altogether': 1,
+ 'god': 1,
+ 'of': 5,
+ 'brave': 1,
+ 'met': 1,
+ 'consecrated': 1,
+ 'forget': 1,
+ 'we': 10,
+ 'forth': 1,
+ 'years': 1,
+ 'perish': 1,
+ 'war': 2,
+ 'to': 8,
+ 'nobly': 1,
+ 'power': 1,
+ 'before': 1,
+ 'endure': 1,
+ 'take': 1}
+
+
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/01-python/w3-python-exceptions.html b/01-python/w3-python-exceptions.html new file mode 100644 index 00000000..3aea9dc6 --- /dev/null +++ b/01-python/w3-python-exceptions.html @@ -0,0 +1,744 @@ + + + + + + + + + + + Exceptions — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Exceptions

+ +
+
+ +
+
+
+ + + + +
+ +
+

Exceptions#

+

Python raises exceptions when it encounters an error. The idea is that you can trap these exceptions and take an appropriate action instead of causing the code to crash. The mechanism for this is try / except. Here’s an example that causes an exception, ZeroDivisionError:

+
+
+
a = 1/0
+
+
+
+
+
---------------------------------------------------------------------------
+ZeroDivisionError                         Traceback (most recent call last)
+Cell In[1], line 1
+----> 1 a = 1/0
+
+ZeroDivisionError: division by zero
+
+
+
+
+

and here we handle this

+
+
+
try:
+    a = 1/0
+except ZeroDivisionError:
+    print("warning: you divided by zero")
+    a = 1
+
+a
+
+
+
+
+
warning: you divided by zero
+
+
+
1
+
+
+
+
+

another example—trying to access a key that doesn’t exist in a dictionary:

+
+
+
dict = {"a":1, "b":2, "c":3}
+print(dict["d"])
+
+
+
+
+
---------------------------------------------------------------------------
+KeyError                                  Traceback (most recent call last)
+Cell In[3], line 2
+      1 dict = {"a":1, "b":2, "c":3}
+----> 2 print(dict["d"])
+
+KeyError: 'd'
+
+
+
+
+

KeyError is the exception that was raised. We can check for this and take the appropriate action instead

+
+
+
try:
+    val = dict["d"]
+except KeyError:
+    val = None
+
+print(val)
+
+
+
+
+
None
+
+
+
+
+

We haven’t talked about I/O yet, but as you might expect, when you open a file, you get an object which you can then interact with. Here we try to open a file for reading that does not already exist.

+
+
+
try:
+    f = open("file.txt", "r")
+except IOError:
+    print("error: that file does not exist")
+
+
+
+
+
error: that file does not exist
+
+
+
+
+

There are a lot of different types of exceptions that you can catch, and you can catch multiple ones per except clause or have multiple except clauses. You probably won’t be able to anticipate every failure mode in advance. In that case, when you run and your code crashes because of an exception, the python interpreter will print out the name of the exception and you can then modify your code to take the appropriate action.

+
+ + + + +
+ + + + + + + + +
+ + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/01-python/w3-python-exercises.html b/01-python/w3-python-exercises.html new file mode 100644 index 00000000..0477e892 --- /dev/null +++ b/01-python/w3-python-exercises.html @@ -0,0 +1,983 @@ + + + + + + + + + + + Exercises — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

Exercises#

+
+

Q 1 (function practice)#

+

Let’s practice functions. Here’s a simple function that takes a string and returns a list of all the 4 letter words:

+
+
+
def four_letter_words(message):
+    words = message.split()
+    four_letters = [w for w in words if len(w) == 4]
+    return four_letters
+
+
+
+
+
+
+
message = "The quick brown fox jumps over the lazy dog"
+print(four_letter_words(message))
+
+
+
+
+
['over', 'lazy']
+
+
+
+
+

Write a version of this function that takes a second argument, n, that is the word length we want to search for

+
+
+

Q 2 (primes)#

+

A prime number is divisible only by 1 and itself. We want to write a function that takes a positive integer, n, and finds all of the primes up to that number.

+

A simple (although not very fast) way to find the primes is to start at 1, and build a list of primes by checking if the current number is divisible by any of the previously found primes. If it is not divisible by any earlier primes, then it is a prime.

+

The modulus operator, % could be helpful here.

+
+
+

Q 3 (exceptions for error handling)#

+

We want to safely convert a string into a float, int, or leave it as a string, depending on its contents. As we’ve already seen, python provides float() and int() functions for this:

+
+
+
a = "2.0"
+b = float(a)
+print(b, type(b))
+
+
+
+
+
2.0 <class 'float'>
+
+
+
+
+

But these throw exceptions if the conversion is not possible

+
+
+
a = "this is a string"
+b = float(a)
+
+
+
+
+
---------------------------------------------------------------------------
+ValueError                                Traceback (most recent call last)
+Cell In[4], line 2
+      1 a = "this is a string"
+----> 2 b = float(a)
+
+ValueError: could not convert string to float: 'this is a string'
+
+
+
+
+
+
+
a = "1.2345"
+b = int(a)
+print(b, type(b))
+
+
+
+
+
---------------------------------------------------------------------------
+ValueError                                Traceback (most recent call last)
+Cell In[5], line 2
+      1 a = "1.2345"
+----> 2 b = int(a)
+      3 print(b, type(b))
+
+ValueError: invalid literal for int() with base 10: '1.2345'
+
+
+
+
+
+
+
b = float(a)
+print(b, type(b))
+
+
+
+
+
1.2345 <class 'float'>
+
+
+
+
+

Notice that an int can be converted to a float, but if you convert a float to an int, you rise losing significant digits. A string cannot be converted to either.

+
+

your task#

+

Write a function, convert_type(a) that takes a string a, and converts it to a float if it is a number with a decimal point, an int if it is an integer, or leaves it as a string otherwise, and returns the result. You’ll want to use exceptions to prevent the code from aborting.

+
+
+
+

Q 4 (tic-tac-toe)#

+

Here we’ll write a simple tic-tac-toe game that 2 players can play. First we’ll create a string that represents our game board:

+
+
+
board = """
+ {s1:^3} | {s2:^3} | {s3:^3}
+-----+-----+-----
+ {s4:^3} | {s5:^3} | {s6:^3}
+-----+-----+-----      123
+ {s7:^3} | {s8:^3} | {s9:^3}       456
+                       789  
+"""
+
+
+
+
+

This board will look a little funny if we just print it—the spacing is set to look right when we replace the {} with x or o

+
+
+
print(board)
+
+
+
+
+
 {s1:^3} | {s2:^3} | {s3:^3}
+-----+-----+-----
+ {s4:^3} | {s5:^3} | {s6:^3}
+-----+-----+-----      123
+ {s7:^3} | {s8:^3} | {s9:^3}       456
+                       789  
+
+
+
+
+

and well use a dictionary to denote the status of each square, “x”, “o”, or empty, “”

+
+
+
play = {}
+
+def initialize_board(play):
+    for n in range(9):
+        play[f"s{n+1}"] = ""
+
+initialize_board(play)
+play
+
+
+
+
+
{'s1': '',
+ 's2': '',
+ 's3': '',
+ 's4': '',
+ 's5': '',
+ 's6': '',
+ 's7': '',
+ 's8': '',
+ 's9': ''}
+
+
+
+
+

Note that our {} placeholders in the board string have identifiers (the numbers in the {}). We can use these to match the variables we want to print to the placeholder in the string, regardless of the order in the format()

+
+
+
a = "{s1:} {s2:}".format(s2=1, s1=2)
+a
+
+
+
+
+
'2 1'
+
+
+
+
+

Here’s an easy way to add the values of our dictionary to the appropriate squares in our game board. First note that each of the {} is labeled with a number that matches the keys in our dictionary. Python provides a way to unpack a dictionary into labeled arguments, using **

+

This lets us to write a function to show the tic-tac-toe board.

+
+
+
def show_board(play):
+    """ display the playing board.  We take a dictionary with the current state of the board
+    We rely on the board string to be a global variable"""
+    print(board.format(**play))
+    
+show_board(play)
+
+
+
+
+
     |     |    
+-----+-----+-----
+     |     |    
+-----+-----+-----      123
+     |     |           456
+                       789  
+
+
+
+
+

Now we need a function that asks a player for a move:

+
+
+
def get_move(n, xo, play):
+    """ ask the current player, n, to make a move -- make sure the square was not 
+        already played.  xo is a string of the character (x or o) we will place in
+        the desired square """
+    valid_move = False
+    while not valid_move:
+        idx = input(f"player {n}, enter your move (1-9)")
+        if play[f"s{idx}"] == "":
+            valid_move = True
+        else:
+            print("invalid move")
+            
+    play[f"s{idx}"] = xo
+
+
+
+
+
+
+
help(get_move)
+
+
+
+
+
Help on function get_move in module __main__:
+
+get_move(n, xo, play)
+    ask the current player, n, to make a move -- make sure the square was not
+    already played.  xo is a string of the character (x or o) we will place in
+    the desired square
+
+
+
+
+
+

your task#

+

Using the functions defined above,

+
    +
  • initialize_board()

  • +
  • show_board()

  • +
  • get_move()

  • +
+

fill in the function play_game() below to complete the game, asking for the moves one at a time, alternating between player 1 and 2

+
+
+
def play_game():
+    """ play a game of tic-tac-toe """
+    
+    play ={}
+    initialize_board(play)
+    show_board(play)
+
+
+
+
+
+
+
play_game()
+
+
+
+
+
     |     |    
+-----+-----+-----
+     |     |    
+-----+-----+-----      123
+     |     |           456
+                       789  
+
+
+
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/01-python/w3-python-functions.html b/01-python/w3-python-functions.html new file mode 100644 index 00000000..926be547 --- /dev/null +++ b/01-python/w3-python-functions.html @@ -0,0 +1,990 @@ + + + + + + + + + + + Functions — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Functions

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

Functions#

+

Functions are used to organize program flow, especially to allow us to easily do commonly needed tasks over and over again. We’ve already used a lot of functions, such as those that work on lists (append() and pop()) or strings (like replace()). Here we see how to write our own functions

+

A function takes arguments, listed in the () and returns a value. Even if you don’t explicitly give a return value, one will be return (e.g., None).

+

Here’s a simple example of a function that takes a single argument, i

+
+
+
def my_fun(i):
+    print(f"in the function, i = {i}")
+
+
+
+
+
+
+
my_fun(10)
+my_fun(5)
+
+
+
+
+
in the function, i = 10
+in the function, i = 5
+
+
+
+
+
+

Note

+

Functions are one place where scope comes into play. A function has its own namespace. If a variable is not defined in that function, then it will look to the namespace from where it was called to see if that variable exists there.

+

However, you should avoid this as much as possible (variables that persist across namespaces are called global variables).

+
+

Functions always return a value—if one is not explicitly given, then they return None, otherwise, they can return values (even multiple values) of any type

+
+
+
a = my_fun(10)
+a
+
+
+
+
+
in the function, i = 10
+
+
+
+
+

Here’s a simple function that takes two numbers and returns their product.

+
+
+
def multiply(a, b):
+    return a*b
+
+c = multiply(3, 4)
+c
+
+
+
+
+
12
+
+
+
+
+
+

Quick Exercise

+

Write a simple function that takes a sentence (as a string) and returns an integer equal to the length of the longest word in the sentence. The len() function and the .split() methods will be useful here.

+
+

None is a special quantity in python (analogous to null in some other languages). We can test on None—the preferred manner is to use is:

+
+
+
def do_nothing():
+    pass
+
+
+
+
+
+
+
a = do_nothing()
+if a is None:
+    print("we didn't do anything")
+
+
+
+
+
we didn't do anything
+
+
+
+
+
+
+
a is None
+
+
+
+
+
True
+
+
+
+
+
+

Keyword arguments#

+

You can have optional arguments which provide defaults. Here’s a simple function that computes \(\sin(\theta)\) where \(\theta\) can optionally be in degrees.

+
+
+
import math
+
+
+
+
+
+
+
def my_sin(theta, in_degrees=False):
+    if in_degrees:
+        return math.sin(math.radians(theta))
+    return math.sin(theta)
+
+
+
+
+
+
+
my_sin(math.pi/4), my_sin(45, in_degrees=True)                    
+
+
+
+
+
(0.7071067811865475, 0.7071067811865475)
+
+
+
+
+
+

Important

+

It is important to note that python evaluates the optional arguments once—when the function is defined. This means that if you make the default an empty object, for instance, it will persist across all call.

+

This leads to one of the most common errors for beginners

+
+

Here’s an example of trying to initialize to an empty list:

+
+
+
def f(a, L=[]):
+    L.append(a)
+    return L
+
+
+
+
+
+
+
print(f(1))
+print(f(2))
+print(f(3))
+
+
+
+
+
[1]
+[1, 2]
+[1, 2, 3]
+
+
+
+
+

Notice that each call does not create its own separate list. Instead a single empty list was created when the function was first processed, and this list persists in memory as the default value for the optional argument L.

+

If we want a unique list created each time (e.g., a separate place in memory), we instead initialize the argument’s value to None and then check its actual value and create an empty list in the function body itself if the default value was unchanged.

+
+
+
def fnew(a, L=None):
+    if L is None:
+        L = []
+    L.append(a)
+    return L
+
+
+
+
+
+
+
print(fnew(1))
+print(fnew(2))
+print(fnew(3))
+
+
+
+
+
[1]
+[2]
+[3]
+
+
+
+
+
+
+
L = fnew(1)
+print(fnew(2, L=L))
+
+
+
+
+
[1, 2]
+
+
+
+
+

Notice that the same None that we saw previously comes into play here.

+
+
+
L
+
+
+
+
+
[1, 2]
+
+
+
+
+
+
+

Lambdas#

+

Lambdas are “disposible” functions. These are small, nameless functions that are often used as arguments in other functions.

+

Ex, from the official tutorial: we have a list of tuples. We want to sort the list based on the second item in the tuple. The sort method can take a key optional argument that tells us how to interpret the list item for sorting

+
+
+
pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
+pairs.sort(key=lambda p: p[1])
+
+
+
+
+
+
+
pairs
+
+
+
+
+
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
+
+
+
+
+

Here we use a lambda in an extract from a list (with the filter function)

+
+
+
squares = [x**2 for x in range(100)]
+sq = list(filter(lambda x : x%2 == 0 and x%3 == 0, squares))
+sq
+
+
+
+
+
[0,
+ 36,
+ 144,
+ 324,
+ 576,
+ 900,
+ 1296,
+ 1764,
+ 2304,
+ 2916,
+ 3600,
+ 4356,
+ 5184,
+ 6084,
+ 7056,
+ 8100,
+ 9216]
+
+
+
+
+
+
+
help(filter)
+
+
+
+
+
Help on class filter in module builtins:
+
+class filter(object)
+ |  filter(function, iterable, /)
+ |
+ |  Return an iterator yielding those items of iterable for which function(item)
+ |  is true. If function is None, return the items that are true.
+ |
+ |  Methods defined here:
+ |
+ |  __iter__(self, /)
+ |      Implement iter(self).
+ |
+ |  __next__(self, /)
+ |      Implement next(self).
+ |
+ |  __reduce__(self, /)
+ |      Return state information for pickling.
+ |
+ |  ----------------------------------------------------------------------
+ |  Static methods defined here:
+ |
+ |  __new__(*args, **kwargs)
+ |      Create and return a new object.  See help(type) for accurate signature.
+
+
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/01-python/w4-python-classes.html b/01-python/w4-python-classes.html new file mode 100644 index 00000000..1667981a --- /dev/null +++ b/01-python/w4-python-classes.html @@ -0,0 +1,1305 @@ + + + + + + + + + + + Classes — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Classes

+ +
+ +
+
+ + + + +
+ +
+

Classes#

+

Classes are the fundamental concept for object oriented programming. A class defines a data type with both data and functions that can operate on the data. An object is an instance of a class. Each object will have its own namespace (separate from other instances of the class and other functions, etc. in your program).

+

We use the dot operator, ., to access members of the class (data or functions). We’ve already been doing this a lot, strings, ints, lists, … are all objects in python.

+
+

Naming conventions#

+

The python community has some naming convections, defined in PEP-8:

+

https://www.python.org/dev/peps/pep-0008/

+

The widely adopted ones are:

+
    +
  • class names start with an uppercase, and use “camelcase” for multiword names, e.g. ShoppingCart

  • +
  • variable names (including objects which are instances of a class) are lowercase and use underscores to separate words, e.g., shopping_cart

  • +
  • module names should be lowercase with underscores

  • +
+
+
+

A simple class#

+

Here’s a class that holds some student info

+
+
+
class Student:
+    def __init__(self, name, grade=None):
+        self.name = name
+        self.grade = grade
+
+
+
+
+

This has a function, __init__() which is called automatically when we create an instance of the class.

+

The argument self refers to the object that we will create, and points to the memory that they object will use to store the class’s contents.

+
+
+
a = Student("Mike")
+print(a.name)
+print(a.grade)
+
+
+
+
+
Mike
+None
+
+
+
+
+

Let’s create a bunch of them, stored in a list

+
+
+
students = []
+students.append(Student("fry", "F-"))
+students.append(Student("leela", "A"))
+students.append(Student("zoidberg", "F"))
+students.append(Student("hubert", "C+"))
+students.append(Student("bender", "B"))
+students.append(Student("calculon", "C"))
+students.append(Student("amy", "A"))
+students.append(Student("hermes", "A"))
+students.append(Student("scruffy", "D"))
+students.append(Student("flexo", "F"))
+students.append(Student("morbo", "D"))
+students.append(Student("hypnotoad", "A+"))
+students.append(Student("zapp", "Q"))
+
+
+
+
+
+

Quick Exercise

+

Loop over the students in the students list and print out the name and grade of each student, one per line.

+
+

We can use list comprehensions with our list of objects. For example, let’s find all the students who have A’s

+
+
+
As = [q.name for q in students if q.grade.startswith("A")]
+As
+
+
+
+
+
['leela', 'amy', 'hermes', 'hypnotoad']
+
+
+
+
+
+
+

Playing Cards#

+

Here’s a more complicated class that represents a playing card. Notice that we are using unicode to represent the suits.

+
+
+
class Card:
+    
+    def __init__(self, suit=1, rank=2):
+        if suit < 1 or suit > 4:
+            print("invalid suit, setting to 1")
+            suit = 1
+            
+        self.suit = suit
+        self.rank = rank
+        
+    def value(self):
+        """ we want things order primarily by rank then suit """
+        return self.suit + (self.rank-1)*14
+    
+    # we include this to allow for comparisons with < and > between cards 
+    def __lt__(self, other):
+        return self.value() < other.value()
+
+    def __eq__(self, other):
+        return self.rank == other.rank and self.suit == other.suit
+    
+    def __repr__(self):
+        return self.__str__()
+    
+    def __str__(self):
+        suits = [u"\u2660",  # spade
+                 u"\u2665",  # heart
+                 u"\u2666",  # diamond
+                 u"\u2663"]  # club
+        
+        r = str(self.rank)
+        if self.rank == 11:
+            r = "J"
+        elif self.rank == 12:
+            r = "Q"
+        elif self.rank == 13:
+            r = "K"
+        elif self.rank == 14:
+            r = "A"
+                
+        return r +':'+suits[self.suit-1]
+
+
+
+
+

we can create a card easily.

+
+
+
c1 = Card()
+
+
+
+
+

We can pass arguments to __init__ in when we setup the class:

+
+
+
c2 = Card(suit=2, rank=2)
+
+
+
+
+

Once we have our object, we can access any of the functions in the class using the dot operator

+
+
+
c2.value()
+
+
+
+
+
16
+
+
+
+
+
+
+
c3 = Card(suit=0, rank=4)
+
+
+
+
+
invalid suit, setting to 1
+
+
+
+
+

The __str__ method converts the object into a string that can be printed.

+
+
+
print(c1)
+print(c2)
+
+
+
+
+
2:♠
+2:♥
+
+
+
+
+

the value method assigns a value to the object that can be used in comparisons, and the __lt__ method is what does the actual comparing

+
+
+
print(c1 > c2)
+print(c1 < c2)
+
+
+
+
+
False
+True
+
+
+
+
+

Note that not every operator is defined for our class, so, for instance, we cannot add two cards together:

+
+
+
c1 + c2
+
+
+
+
+
---------------------------------------------------------------------------
+TypeError                                 Traceback (most recent call last)
+Cell In[12], line 1
+----> 1 c1 + c2
+
+TypeError: unsupported operand type(s) for +: 'Card' and 'Card'
+
+
+
+
+
+

Quick Exercise

+
    +
  • Create a “hand” corresponding to a straight (5 cards of any suite, but in sequence of rank)

  • +
  • Create another hand corresponding to a flush (5 cards all of the same suit, of any rank)

  • +
  • Finally create a hand with one of the cards duplicated—this should not be allowed in a standard deck of cards. How would you check for this?

  • +
+
+
+
+

Operators#

+

We can define operations like + and - that work on our objects. Here’s a simple example of currency—we keep track of the country and the amount

+
+
+
class Currency:
+    """ a simple class to hold foreign currency """
+    
+    def __init__(self, amount, country="US"):
+        self.amount = amount
+        self.country = country
+        
+    def __add__(self, other):
+        return Currency(self.amount + other.amount, country=self.country)
+
+    def __sub__(self, other):
+        return Currency(self.amount - other.amount, country=self.country)
+
+    def __str__(self):
+        return f"{self.amount} {self.country}"
+
+
+
+
+

We can now create some monetary amounts for different countries

+
+
+
d1 = Currency(10, "US")
+d2 = Currency(15, "US")
+print(d2 - d1)
+
+
+
+
+
5 US
+
+
+
+
+
+

Quick Exercise

+

As written, our Currency class has a bug—it does not check whether the amounts are in the same country before adding. Modify the __add__ method to first check if the countries are the same. If they are, return the new Currency object with the sum, otherwise, return None.

+
+
+
+

Vectors Example#

+

Here we write a class to represent 2-d vectors. Vectors have a direction and a magnitude. We can represent them as a pair of numbers, representing the x and y lengths. We’ll use a tuple internally for this

+

We want our class to do all the basic operations we do with vectors: add them, multiply by a scalar, cross product, dot product, return the magnitude, etc.

+

We’ll use the math module to provide some basic functions we might need (like sqrt)

+

This example will show us how to overload the standard operations in python. Here’s a list of the builtin methods:

+

https://docs.python.org/3/reference/datamodel.html

+

To make it really clear what’s being called when, I’ve added prints in each of the functions

+
+
+
import math
+
+
+
+
+
+
+
class Vector:
+    """ a general two-dimensional vector """
+    
+    def __init__(self, x, y):
+        print("in __init__")
+        self.x = x
+        self.y = y
+        
+    def __str__(self):
+        print("in __str__")        
+        return f"({self.x} î + {self.y} ĵ)"
+    
+    def __repr__(self):
+        print("in __repr__")        
+        return f"Vector({self.x}, {self.y})"
+
+    def __add__(self, other):
+        print("in __add__")        
+        if isinstance(other, Vector):
+            return Vector(self.x + other.x, self.y + other.y)
+        else:
+            # it doesn't make sense to add anything but two vectors
+            print(f"we don't know how to add a {type(other)} to a Vector")
+            raise NotImplementedError
+
+    def __sub__(self, other):
+        print("in __sub__")        
+        if isinstance(other, Vector):
+            return Vector(self.x - other.x, self.y - other.y)
+        else:
+            # it doesn't make sense to add anything but two vectors
+            print(f"we don't know how to add a {type(other)} to a Vector")
+            raise NotImplementedError
+
+    def __mul__(self, other):
+        print("in __mul__")        
+        if isinstance(other, int) or isinstance(other, float):
+            # scalar multiplication changes the magnitude
+            return Vector(other*self.x, other*self.y)
+        else:
+            print("we don't know how to multiply two Vectors")
+            raise NotImplementedError
+
+    def __matmul__(self, other):
+        print("in __matmul__")
+        # a dot product
+        if isinstance(other, Vector):
+            return self.x*other.x + self.y*other.y
+        else:
+            print("matrix multiplication not defined")
+            raise NotImplementedError
+
+    def __rmul__(self, other):
+        print("in __rmul__")        
+        return self.__mul__(other)
+
+    def __truediv__(self, other):
+        print("in __truediv__")        
+        # we only know how to multiply by a scalar
+        if isinstance(other, int) or isinstance(other, float):
+            return Vector(self.x/other, self.y/other)
+
+    def __abs__(self):
+        print("in __abs__")        
+        return math.sqrt(self.x**2 + self.y**2)
+
+    def __neg__(self):
+        print("in __neg__")        
+        return Vector(-self.x, -self.y)
+
+    def cross(self, other):
+        # a vector cross product -- we return the magnitude, since it will
+        # be in the z-direction, but we are only 2-d 
+        return abs(self.x*other.y - self.y*other.x)
+
+
+
+
+

This is a basic class that provides two methods __str__ and __repr__ to show a representation of it. There was some discussion of this on slack. These two functions provide a readable version of our object.

+

The convection is what __str__ is human readable while __repr__ should be a form that can be used to recreate the object (e.g., via eval()). See:

+

http://stackoverflow.com/questions/1436703/difference-between-str-and-repr-in-python

+
+
+
v = Vector(1,2)
+v
+
+
+
+
+
in __init__
+in __repr__
+
+
+
Vector(1, 2)
+
+
+
+
+
+
+
print(v)
+
+
+
+
+
in __str__
+(1 î + 2 ĵ)
+
+
+
+
+

Vectors have a length, and we’ll use the abs() builtin to provide the magnitude. For a vector:

+
+\[\vec{v} = \alpha \hat{i} + \beta \hat{j}\]
+

we have

+
+\[|\vec{v}| = \sqrt{\alpha^2 + \beta^2}\]
+
+
+
abs(v)
+
+
+
+
+
in __abs__
+
+
+
2.23606797749979
+
+
+
+
+

Let’s look at mathematical operations on vectors now. We want to be able to add and subtract two vectors as well as multiply and divide by a scalar.

+
+
+
u = Vector(3,5)
+
+
+
+
+
in __init__
+
+
+
+
+
+
+
w = u + v
+print(w)
+
+
+
+
+
in __add__
+in __init__
+in __str__
+(4 î + 7 ĵ)
+
+
+
+
+
+
+
u - v
+
+
+
+
+
in __sub__
+in __init__
+in __repr__
+
+
+
Vector(2, 3)
+
+
+
+
+

It doesn’t make sense to add a scalar to a vector, so we didn’t implement this – what happens?

+
+
+
u + 2.0
+
+
+
+
+
in __add__
+we don't know how to add a <class 'float'> to a Vector
+
+
+
---------------------------------------------------------------------------
+NotImplementedError                       Traceback (most recent call last)
+Cell In[23], line 1
+----> 1 u + 2.0
+
+Cell In[16], line 24, in Vector.__add__(self, other)
+     21 else:
+     22     # it doesn't make sense to add anything but two vectors
+     23     print(f"we don't know how to add a {type(other)} to a Vector")
+---> 24     raise NotImplementedError
+
+NotImplementedError: 
+
+
+
+
+

Now multiplication. It makes sense to multiply by a scalar, but there are multiple ways to define multiplication of two vectors.

+

Note that python provides both a __mul__ and a __rmul__ function to define what happens when we multiply a vector by a quantity and what happens when we multiply something else by a vector.

+
+
+
u*2.0
+
+
+
+
+
in __mul__
+in __init__
+in __repr__
+
+
+
Vector(6.0, 10.0)
+
+
+
+
+
+
+
2.0*u
+
+
+
+
+
in __rmul__
+in __mul__
+in __init__
+in __repr__
+
+
+
Vector(6.0, 10.0)
+
+
+
+
+

and division: __truediv__ is the python 3 way of division /, while __floordiv__ is the old python 2 way, also enabled via //.

+

Dividing a scalar by a vector doesn’t make sense:

+
+
+
u/5.0
+
+
+
+
+
in __truediv__
+in __init__
+in __repr__
+
+
+
Vector(0.6, 1.0)
+
+
+
+
+
+
+
5.0/u
+
+
+
+
+
---------------------------------------------------------------------------
+TypeError                                 Traceback (most recent call last)
+Cell In[27], line 1
+----> 1 5.0/u
+
+TypeError: unsupported operand type(s) for /: 'float' and 'Vector'
+
+
+
+
+

Python 3.5 introduced a new matrix multiplication operator, @ – we’ll use this to implement a dot product between two vectors:

+
+
+
u @ v
+
+
+
+
+
in __matmul__
+
+
+
13
+
+
+
+
+

For a cross product, we don’t have an obvious operator, so we’ll use a function. For 2-d vectors, this will result in a scalar

+
+
+
u.cross(v)
+
+
+
+
+
1
+
+
+
+
+

Finally, negation is a separate operation:

+
+
+
-u
+
+
+
+
+
in __neg__
+in __init__
+in __repr__
+
+
+
Vector(-3, -5)
+
+
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/01-python/w4-python-exercises.html b/01-python/w4-python-exercises.html new file mode 100644 index 00000000..44ecdc37 --- /dev/null +++ b/01-python/w4-python-exercises.html @@ -0,0 +1,1035 @@ + + + + + + + + + + + Practicing Classes — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Practicing Classes

+ +
+ +
+
+ + + + +
+ +
+

Practicing Classes#

+
+

Exercise 1 (shopping cart)#

+

Let’s write a simple shopping cart class – this will hold items that you intend to purchase as well as the amount, etc. And allow you to add / remove items, get a subtotal, etc.

+

We’ll use two classes: Item will be a single item and ShoppingCart will be the collection of items you wish to purchase.

+

First, our store needs an inventory – here’s what we have for sale:

+
+
+
INVENTORY_TEXT = """
+apple, 0.60
+banana, 0.20
+grapefruit, 0.75
+grapes, 1.99
+kiwi, 0.50
+lemon, 0.20
+lime, 0.25
+mango, 1.50
+papaya, 2.95
+pineapple, 3.50
+blueberries, 1.99
+blackberries, 2.50
+peach, 0.50
+plum, 0.33
+clementine, 0.25
+cantaloupe, 3.25
+pear, 1.25
+quince, 0.45
+orange, 0.60
+"""
+
+# this will be a global -- convention is all caps
+INVENTORY = {}
+for line in INVENTORY_TEXT.splitlines():
+    if line.strip() == "":
+        continue
+    item, price = line.split(",")
+    INVENTORY[item] = float(price)
+
+
+
+
+
+
+
INVENTORY
+
+
+
+
+
{'apple': 0.6,
+ 'banana': 0.2,
+ 'grapefruit': 0.75,
+ 'grapes': 1.99,
+ 'kiwi': 0.5,
+ 'lemon': 0.2,
+ 'lime': 0.25,
+ 'mango': 1.5,
+ 'papaya': 2.95,
+ 'pineapple': 3.5,
+ 'blueberries': 1.99,
+ 'blackberries': 2.5,
+ 'peach': 0.5,
+ 'plum': 0.33,
+ 'clementine': 0.25,
+ 'cantaloupe': 3.25,
+ 'pear': 1.25,
+ 'quince': 0.45,
+ 'orange': 0.6}
+
+
+
+
+
+

Item#

+

Here’s the start of an item class – we want it to hold the name and quantity.

+

You should have the following features:

+
    +
  • the name should be something in our inventory

  • +
  • Our shopping cart will include a list of all the items we want to buy, so we want to be able to check for duplicates. Implement the equal test, ==, using __eq__

  • +
  • we’ll want to consolidate dupes, so implement the + operator, using __add__ so we can add items together in our shopping cart. Note, add should raise a ValueError if you try to add two Items that don’t have the same name.

  • +
+

Here’s a start:

+
+
+
class Item:
+    """ an item to buy """
+    
+    def __init__(self, name, quantity=1):
+        """keep track of an item that is in our inventory"""
+        if name not in INVENTORY:
+            raise ValueError("invalid item name")
+        self.name = name
+        self.quantity = quantity
+        
+    def __repr__(self):
+        return f"{self.name}: {self.quantity}"
+        
+    def __eq__(self, other):
+        """check if the items have the same name"""
+        return self.name == other.name
+    
+    def __add__(self, other):
+        """add two items together if they are the same type"""
+        if self.name == other.name:
+            return Item(self.name, self.quantity + other.quantity)
+        else:
+            raise ValueError("names don't match")
+
+
+
+
+

Here are some tests your code should pass:

+
+
+
a = Item("apple", 10)
+b = Item("banana", 20)
+
+
+
+
+
+
+
c = Item("apple", 20)
+
+
+
+
+
+
+
# won't work
+a + b
+
+
+
+
+
---------------------------------------------------------------------------
+ValueError                                Traceback (most recent call last)
+Cell In[6], line 2
+      1 # won't work
+----> 2 a + b
+
+Cell In[3], line 23, in Item.__add__(self, other)
+     21     return Item(self.name, self.quantity + other.quantity)
+     22 else:
+---> 23     raise ValueError("names don't match")
+
+ValueError: names don't match
+
+
+
+
+
+
+
# will work
+a += c
+print(a)
+
+
+
+
+
apple: 30
+
+
+
+
+
+
+
d = Item("dog")
+
+
+
+
+
---------------------------------------------------------------------------
+ValueError                                Traceback (most recent call last)
+Cell In[8], line 1
+----> 1 d = Item("dog")
+
+Cell In[3], line 7, in Item.__init__(self, name, quantity)
+      5 """keep track of an item that is in our inventory"""
+      6 if name not in INVENTORY:
+----> 7     raise ValueError("invalid item name")
+      8 self.name = name
+      9 self.quantity = quantity
+
+ValueError: invalid item name
+
+
+
+
+
+
+
# should be False
+a == b
+
+
+
+
+
False
+
+
+
+
+
+
+
# should be True -- they have the same name
+a == c
+
+
+
+
+
True
+
+
+
+
+

How do they behave in a list?

+
+
+
items = []
+items.append(a)
+items.append(b)
+items
+
+
+
+
+
[apple: 30, banana: 20]
+
+
+
+
+
+
+
# should be True -- they have the same name
+c in items
+
+
+
+
+
True
+
+
+
+
+
+
+

ShoppingCart#

+

Now we want to create a shopping cart. The main thing it will do is hold a list of items.

+
+
+
class ShoppingCart:
+    
+    def __init__(self):
+        # the list of items we control
+        self.items = []
+        
+    def subtotal(self):
+        """ return a subtotal of our items """
+        pass
+
+    def add(self, name, quantity):
+        """ add an item to our cart -- the an item of the same name already
+        exists, then increment the quantity.  Otherwise, add a new item
+        to the cart with the desired quantity."""
+        pass
+        
+    def remove(self, name):
+        """ remove all of item name from the cart """
+        pass
+        
+    def report(self):
+        """ print a summary of the cart """
+        for item in self.items:
+            print(f"{item.name} : {item.quantity}")
+
+
+
+
+

Here are some tests

+
+
+
sc = ShoppingCart()
+sc.add("orange", 19)
+
+
+
+
+
+
+
sc.add("apple", 2)
+
+
+
+
+
+
+
sc.report()
+
+
+
+
+
+
+
sc.add("apple", 9)
+
+
+
+
+
+
+
# apple should only be listed once in the report, with a quantity of 11
+sc.report()
+
+
+
+
+
+
+
sc.subtotal()
+
+
+
+
+
+
+
sc.remove("apple")
+
+
+
+
+
+
+
# apple should no longer be listed
+sc.report()
+
+
+
+
+
+
+
+

Exercise 2: Poker Odds#

+

Use the deck of cards class from the notebook we worked through outside of class to write a Monte Carlo code that plays a lot of hands of straight poker (like 100,000). Count how many of these hands has a particular poker hand (like 3-of-a-kind). The ratio of # of hands with 3-of-a-kind to total hands is an approximation to the odds of getting a 3-of-a-kind in poker.

+

You’ll want to copy-paste those classes into a .py file to allow you to import and reuse them here

+
+
+

Exercise 3: Tic-Tac-Toe#

+

Revisit the tic-tac-toe game you developed in the functions exercises but now write it as a class with methods to do each of the main steps.

+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/01-python/w4-python-modules.html b/01-python/w4-python-modules.html new file mode 100644 index 00000000..d6b962d2 --- /dev/null +++ b/01-python/w4-python-modules.html @@ -0,0 +1,790 @@ + + + + + + + + + + + Modules — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Modules

+ +
+
+ +
+
+
+ + + + +
+ +
+

Modules#

+

Here we import our own module called myprofile (we have it in the same directory as this notebook)

+
+
+
import myprofile
+
+
+
+
+

We have a docstring at the top – the comments there are what appear when we ask for help

+
+
+
help(myprofile)
+
+
+
+
+
Help on module myprofile:
+
+NAME
+    myprofile
+
+DESCRIPTION
+    A very simple profiling class.  Define some timers and methods
+    to start and stop them.  Nesting of timers is tracked so we can
+    pretty print the profiling information.
+
+    # define a timer object, labeled 'my timer'
+    a = timer('my timer')
+
+    This will add 'my timer' to the list of keys in the 'my timer'
+    dictionary.  Subsequent calls to the timer class constructor
+    will have no effect.
+
+    # start timing the 'my timer' block of code
+    a.begin()
+
+    ... do stuff here ...
+
+    # end the timing of the 'my timer' block of code
+    a.end()
+
+    for best results, the block of code timed should be large
+    enough to offset the overhead of the timer class method
+    calls.
+
+    Multiple timers can be instantiated and nested.  The stackCount
+    global parameter keeps count of the level of nesting, and the
+    timerNesting data structure stores the nesting level for each
+    defined timer.
+
+    timeReport() is called at the end to print out a summary of the
+    timing.
+
+    At present, no enforcement is done to ensure proper nesting.
+
+CLASSES
+    builtins.object
+        Timer
+
+    class Timer(builtins.object)
+     |  Timer(name)
+     |
+     |  Methods defined here:
+     |
+     |  __init__(self, name)
+     |      Initialize self.  See help(type(self)) for accurate signature.
+     |
+     |  begin(self)
+     |
+     |  end(self)
+     |
+     |  ----------------------------------------------------------------------
+     |  Data descriptors defined here:
+     |
+     |  __dict__
+     |      dictionary for instance variables
+     |
+     |  __weakref__
+     |      list of weak references to the object
+
+FUNCTIONS
+    time_report()
+
+DATA
+    stack_count = 0
+    timer_nesting = {}
+    timer_order = []
+    timers = {}
+
+FILE
+    /home/runner/work/python-science/python-science/content/01-python/myprofile.py
+
+
+
+
+

This module simply provides a way to time routines (python and ipython have built-in methods for this too)

+
+
+
t = myprofile.Timer("main loop")
+t.begin()
+
+sum = 0.0
+for n in range(1000):
+    sum += n**2
+
+
+t.end()
+myprofile.time_report()
+
+print(sum)
+
+
+
+
+
main loop:  0.0001976490020751953
+332833500.0
+
+
+
+
+

In the file myprofile.py, you will see a block of code under

+
if __name__ == "__main__":
+
+
+

That code is executed if the file is run directly, either from the commandline as:

+

python myprofile.py

+

for through the %run magic

+
+
+
%run myprofile
+
+
+
+
+
1:  10.000112056732178
+2:  25.00027108192444
+   3:  20.00014853477478
+
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/01-python/w5-python-more-examples.html b/01-python/w5-python-more-examples.html new file mode 100644 index 00000000..c67dbf71 --- /dev/null +++ b/01-python/w5-python-more-examples.html @@ -0,0 +1,762 @@ + + + + + + + + + + + Exercises — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

Exercises#

+
+

Exercise 1: Rock-Paper-Scissors#

+

Implement a set of games of rock-paper-scissors against the computer.

+
    +
  • Ask for input from the user (“rock”, “paper”, or “scissors”) and the randomly select one of these for the computer’s play.

  • +
  • Announce who won.

  • +
  • Keep playing until a player says that they no longer want to play.

  • +
  • When all games are done, print out how many games were won by the player and by the computer

  • +
+
+
+

Exercise 2: Pascal’s triangle#

+

Pascal’s triangle is created such that each layer has 1 more element than the previous, with 1s on the side and in the interior, the numbers are the sum of the two above it, e.g.,:

+
            1
+          1   1
+        1   2   1
+      1   3   3   1
+    1   4   6   4   1
+  1   5   10  10  5   1
+
+
+

Write a function to return the first n rows of Pascal’s triangle. The return should be a list of length n, with each element itself a list containing the numbers for that row.

+

If you want to add complexity, write a function to print out Pascal’s triangle with proper formatting, so the numbers in each row are centered between the ones in the row above

+
+
+

Exercise 3: Panagrams#

+

A panagram is a sentence that includes all 26 letters of the alphabet, e.g., “The quick brown fox jumps over the lazy dog.”

+

Write a function that takes as an argument a sentence and returns True or False, indicating whether the sentence is a panagram.

+
+
+

Exercise 4: Math practice#

+

We want to make a simple table of trigonometric functions for different angles. Write a code that outputs in columns, the following data:

+
angle (degrees)    angle (radians)     sin(angle)     cos(angle)    sin(angle)**2 + cos(angle)**2
+
+
+

For all angles spaced 30 degrees apart in the range 0 to 360 degrees.

+

Keep in mind that the trig functions expect the input in radians.

+
+
+

Exercise 5: Calendar events#

+

We want to keep a schedule of events. We will do this by creating a class called Day. It is sketched out below. A Day holds a list of events and has methods that allow you to add an delete events. Our events will be instances of a class Event, which holds the time, location, and description of the event.

+

Finally, we can keep track of a list of all the Days for which we have events to make our schedule.

+

Fill in these classes and write some code to demonstrate their use:

+
    +
  • Create a full week of days in your calendar

  • +
  • Add an event every day at noon called “lunch”

  • +
  • Randomly add some other events to fill out your calendar

  • +
  • Write some code that tells you the start time of your first meeting and the end time of your last meeting (this is the length of your work day)

  • +
+
+
+
class Day:
+    """a single day keeping track of the events scheduled"""
+    def __init__(month, day, year):
+        # store the month, day, and year as data in the class
+        
+        # keep track of the events
+        self.events = []
+    
+    def add_event(name, time=None, location=None):
+        pass
+    
+    def delete_event(name):
+        pass
+    
+    
+class Event:
+    """a single event in our calendar"""
+    def __init__(name, time=9, location=None, duration=1):
+        self.name = name
+        self.time = time
+        self.location = location
+        self.duration = duration
+
+
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/02-numpy/numpy-advanced.html b/02-numpy/numpy-advanced.html new file mode 100644 index 00000000..230efcf4 --- /dev/null +++ b/02-numpy/numpy-advanced.html @@ -0,0 +1,1226 @@ + + + + + + + + + + + NumPy Advanced Operations — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

NumPy Advanced Operations

+ +
+ +
+
+ + + + +
+ +
+

NumPy Advanced Operations#

+
+
+
import numpy as np
+
+
+
+
+
+

Copying Arrays#

+
+

Important

+

Simply using = does not make a copy, but much like with lists, you will just have multiple names pointing to the same ndarray object

+

Therefore, we need to understand if two arrays, A and B point to:

+
    +
  • the same array, including shape and data/memory space

  • +
  • the same data/memory space, but perhaps different shapes (a view)

  • +
  • a separate copy of the data (i.e. stored completely separately in memory)

  • +
+

All of these are possible.

+
+

All of these are possible:

+
    +
  • B = A

    +

    this is assignment. No copy is made. A and B point to the same data in memory and share the same shape, etc. They are just two different labels for the same object in memory

    +
  • +
  • B = A[:]

    +

    this is a view or shallow copy. The shape info for A and B are stored independently, but both point to the same memory location for data

    +
  • +
  • B = A.copy()

    +

    this is a deep copy. A completely separate object will be created in memory, with a completely separate location in memory.

    +
  • +
+

Let’s look at examples

+
+
+
a = np.arange(10)
+a
+
+
+
+
+
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
+
+
+
+
+

Here is assignment—we can just use the is operator to test for equality

+
+
+
b = a
+b is a
+
+
+
+
+
True
+
+
+
+
+

Since b and a are the same, changes to the shape of one are reflected in the other—no copy is made.

+
+
+
b.shape = (2, 5)
+print(b)
+a.shape
+
+
+
+
+
[[0 1 2 3 4]
+ [5 6 7 8 9]]
+
+
+
(2, 5)
+
+
+
+
+
+
+
b is a
+
+
+
+
+
True
+
+
+
+
+
+
+
a
+
+
+
+
+
array([[0, 1, 2, 3, 4],
+       [5, 6, 7, 8, 9]])
+
+
+
+
+

a shallow copy creates a new view into the array—the data is the same, but the array properties can be different

+
+
+
a = np.arange(12)
+c = a[:]
+a.shape = (3,4)
+
+print(a)
+print(c)
+
+
+
+
+
[[ 0  1  2  3]
+ [ 4  5  6  7]
+ [ 8  9 10 11]]
+[ 0  1  2  3  4  5  6  7  8  9 10 11]
+
+
+
+
+

since the underlying data is the same memory, changing an element of one is reflected in the other

+
+
+
c[1] = -1
+a
+
+
+
+
+
array([[ 0, -1,  2,  3],
+       [ 4,  5,  6,  7],
+       [ 8,  9, 10, 11]])
+
+
+
+
+

Even slices into an array are just views, still pointing to the same memory

+
+
+
d = c[3:8]
+d
+
+
+
+
+
array([3, 4, 5, 6, 7])
+
+
+
+
+
+
+
d[:] = 0 
+
+
+
+
+
+
+
print(a)
+print(c)
+print(d)
+
+
+
+
+
[[ 0 -1  2  0]
+ [ 0  0  0  0]
+ [ 8  9 10 11]]
+[ 0 -1  2  0  0  0  0  0  8  9 10 11]
+[0 0 0 0 0]
+
+
+
+
+

There are lots of ways to inquire if two arrays are the same, views, own their own data, etc

+
+
+
print(c is a)
+print(c.base is a)
+print(c.flags.owndata)
+print(a.flags.owndata)
+
+
+
+
+
False
+True
+False
+True
+
+
+
+
+

to make a copy of the data of the array that you can deal with independently of the original, you need a deep copy

+
+
+
d = a.copy()
+d[:,:] = 0.0
+
+print(a)
+print(d)
+
+
+
+
+
[[ 0 -1  2  0]
+ [ 0  0  0  0]
+ [ 8  9 10 11]]
+[[0 0 0 0]
+ [0 0 0 0]
+ [0 0 0 0]]
+
+
+
+
+
+
+

Boolean Indexing#

+

There are lots of fun ways to index arrays to access only those elements that meet a certain condition

+
+
+
a = np.arange(12).reshape(3,4)
+a
+
+
+
+
+
array([[ 0,  1,  2,  3],
+       [ 4,  5,  6,  7],
+       [ 8,  9, 10, 11]])
+
+
+
+
+

Here we set all the elements in the array that are > 4 to zero

+
+
+
a[a > 4] = 0
+a
+
+
+
+
+
array([[0, 1, 2, 3],
+       [4, 0, 0, 0],
+       [0, 0, 0, 0]])
+
+
+
+
+

and now, all the zeros to -1

+
+
+
a[a == 0] = -1
+a
+
+
+
+
+
array([[-1,  1,  2,  3],
+       [ 4, -1, -1, -1],
+       [-1, -1, -1, -1]])
+
+
+
+
+
+
+
a == -1
+
+
+
+
+
array([[ True, False, False, False],
+       [False,  True,  True,  True],
+       [ True,  True,  True,  True]])
+
+
+
+
+

if we have 2 tests, we need to use logical_and() or logical_or()

+
+
+
a = np.arange(12).reshape(3,4)
+a
+
+
+
+
+
array([[ 0,  1,  2,  3],
+       [ 4,  5,  6,  7],
+       [ 8,  9, 10, 11]])
+
+
+
+
+
+
+
a[np.logical_or(a == 0, np.logical_and(a > 3, a <= 9))] = 100.0
+a
+
+
+
+
+
array([[100,   1,   2,   3],
+       [100, 100, 100, 100],
+       [100, 100,  10,  11]])
+
+
+
+
+

Our test that we index the array with returns a boolean array of the same shape:

+
+
+
a > 4
+
+
+
+
+
array([[ True, False, False, False],
+       [ True,  True,  True,  True],
+       [ True,  True,  True,  True]])
+
+
+
+
+
+
+

Avoiding Loops#

+

In general, you want to avoid loops over elements on an array.

+

Here, let’s create 1-d x and y coordinates and then try to fill some larger array

+
+
+
M = 128
+N = 256
+xmin = ymin = 0.0
+xmax = ymax = 1.0
+
+x = np.linspace(xmin, xmax, M, endpoint=False)
+y = np.linspace(ymin, ymax, N, endpoint=False)
+
+print(x.shape)
+print(y.shape)
+
+
+
+
+
(128,)
+(256,)
+
+
+
+
+

we’ll time out code

+
+
+
import time
+
+
+
+
+
+
+
t0 = time.time()
+
+g = np.zeros((M, N))
+
+for j in range(N):
+    for i in range(M):
+
+        g[i,j] = np.sin(2.0*np.pi*x[i]*y[j])
+        
+t1 = time.time()
+print(f"time elapsed: {t1-t0} s")
+
+
+
+
+
time elapsed: 0.024106740951538086 s
+
+
+
+
+

Now let’s instead do this using all array syntax.

+
+

Tip

+

First will extend our 1-d coordinate arrays to be 2-d.
+NumPy has a function for this (meshgrid())

+
+

Let’s look at how meshgrid() works first

+
+
+
x2d, y2d = np.meshgrid([0, 1, 2, 3], [10, 20, 30], indexing="ij")
+x2d
+
+
+
+
+
array([[0, 0, 0],
+       [1, 1, 1],
+       [2, 2, 2],
+       [3, 3, 3]])
+
+
+
+
+
+
+
y2d
+
+
+
+
+
array([[10, 20, 30],
+       [10, 20, 30],
+       [10, 20, 30],
+       [10, 20, 30]])
+
+
+
+
+

We see that this creates 2 two-dimensional arrays, one with the x-values changing across rows and one with y-values changing across columns. This means we can index all points +(x[i], y[j]) through the pair x2d, y2d

+

Now let’s do our same example this method.

+
+
+
t0 = time.time()
+x2d, y2d = np.meshgrid(x, y, indexing="ij")
+g2 = np.sin(2.0*np.pi*x2d*y2d)
+t1 = time.time()
+print(f"time elapsed: {t1-t0} s")
+
+
+
+
+
time elapsed: 0.023400306701660156 s
+
+
+
+
+

A final check to make sure they give the same answer

+
+
+
print(np.max(np.abs(g2-g)))
+
+
+
+
+
0.0
+
+
+
+
+
+
+

Numerical differencing on NumPy arrays#

+

Now we want to construct a derivative,

+
+\[ +\frac{d f}{dx} +\]
+
+
+
x = np.linspace(0, 2*np.pi, 25)
+f = np.sin(x)
+
+
+
+
+

We want to do this without loops—we’ll use views into arrays offset from one another. Recall from calculus that a derivative is approximately:

+
+\[ +\frac{df}{dx} = \frac{f(x+h) - f(x)}{h} +\]
+

Here, we’ll take \(h\) to be a single adjacent element

+
+
+
dx = x[1] - x[0]
+dfdx = (f[1:] - f[:-1])/dx
+
+
+
+
+
+
+
dfdx
+
+
+
+
+
array([ 0.98861593,  0.92124339,  0.79108963,  0.60702442,  0.38159151,
+        0.13015376, -0.13015376, -0.38159151, -0.60702442, -0.79108963,
+       -0.92124339, -0.98861593, -0.98861593, -0.92124339, -0.79108963,
+       -0.60702442, -0.38159151, -0.13015376,  0.13015376,  0.38159151,
+        0.60702442,  0.79108963,  0.92124339,  0.98861593])
+
+
+
+
+
+
+
for i in range(len(x)-1):
+    print((f[i+1] - f[i]) / dx)
+
+
+
+
+
0.9886159294653692
+0.9212433876373748
+0.7910896313685742
+0.6070244240594342
+0.38159150540593506
+0.13015375626880096
+-0.13015375626880055
+-0.38159150540593506
+-0.6070244240594342
+-0.7910896313685731
+-0.9212433876373752
+-0.9886159294653699
+-0.9886159294653681
+-0.9212433876373755
+-0.7910896313685738
+-0.6070244240594346
+-0.38159150540593545
+-0.1301537562688014
+0.13015375626880013
+0.3815915054059342
+0.607024424059435
+0.7910896313685731
+0.9212433876373736
+0.9886159294653716
+
+
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/02-numpy/numpy-basics.html b/02-numpy/numpy-basics.html new file mode 100644 index 00000000..53a0a2f7 --- /dev/null +++ b/02-numpy/numpy-basics.html @@ -0,0 +1,1295 @@ + + + + + + + + + + + NumPy — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

NumPy#

+

This notebook is based on the SciPy NumPy tutorial

+
+

Note

+

Note that the traditional way to import numpy is to rename it np. This saves on typing and makes your code a little more compact.

+
+
+
+
import numpy as np
+
+
+
+
+

NumPy provides a multidimensional array class as well as a large number of functions that operate on arrays.

+

NumPy arrays allow you to write fast (optimized) code that works on arrays of data. To do this, there are some restrictions on arrays:

+
    +
  • all elements are of the same data type (e.g. float)

  • +
  • the size of the array is fixed in memory, and specified when you create the array (e.g., you cannot grow the array like you do with lists)

  • +
+

The nice part is that arithmetic operations work on entire arrays—this means that you can avoid writing loops in python (which tend to be slow). Instead the “looping” is done in the underlying compiled code

+
+

Array Creation and Properties#

+

There are a lot of ways to create arrays. Let’s look at a few

+

Here we create an array using arange and then change its shape to be 3 rows and 5 columns. Note the row-major ordering—you’ll see that the rows are together in the inner [] (more on this in a bit)

+
+
+
a = np.arange(15)
+
+
+
+
+
+
+
a
+
+
+
+
+
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])
+
+
+
+
+
+
+
a = np.arange(15).reshape(3,5)
+
+print(a)
+
+
+
+
+
[[ 0  1  2  3  4]
+ [ 5  6  7  8  9]
+ [10 11 12 13 14]]
+
+
+
+
+

an array is an object of the ndarray class, and it has methods that know how to work on the array data. Here, .reshape() is one of the many methods.

+

A NumPy array has a lot of meta-data associated with it describing its shape, datatype, etc.

+
+
+
print(a.ndim)
+print(a.shape)
+print(a.size)
+print(a.dtype)
+print(a.itemsize)
+print(type(a))
+
+
+
+
+
2
+(3, 5)
+15
+int64
+8
+<class 'numpy.ndarray'>
+
+
+
+
+
+
+
#help(a)
+
+
+
+
+

we can also create an array from a list

+
+
+
b = np.array( [1.0, 2.0, 3.0, 4.0] )
+print(b)
+print(b.dtype)
+print(type(b))
+
+
+
+
+
[1. 2. 3. 4.]
+float64
+<class 'numpy.ndarray'>
+
+
+
+
+

we can create a multi-dimensional array of a specified size initialized all to 0 easily, using the zeros() function.

+
+
+
b = np.zeros((10,8))
+b
+
+
+
+
+
array([[0., 0., 0., 0., 0., 0., 0., 0.],
+       [0., 0., 0., 0., 0., 0., 0., 0.],
+       [0., 0., 0., 0., 0., 0., 0., 0.],
+       [0., 0., 0., 0., 0., 0., 0., 0.],
+       [0., 0., 0., 0., 0., 0., 0., 0.],
+       [0., 0., 0., 0., 0., 0., 0., 0.],
+       [0., 0., 0., 0., 0., 0., 0., 0.],
+       [0., 0., 0., 0., 0., 0., 0., 0.],
+       [0., 0., 0., 0., 0., 0., 0., 0.],
+       [0., 0., 0., 0., 0., 0., 0., 0.]])
+
+
+
+
+

There is also an analogous ones() and empty() array routine. Note that here we can explicitly set the datatype for the array in this function if we wish.

+

Unlike lists in python, all of the elements of a numpy array are of the same datatype

+
+
+
c = np.eye(10, dtype=np.float64)
+c
+
+
+
+
+
array([[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
+       [0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
+       [0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
+       [0., 0., 0., 1., 0., 0., 0., 0., 0., 0.],
+       [0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],
+       [0., 0., 0., 0., 0., 1., 0., 0., 0., 0.],
+       [0., 0., 0., 0., 0., 0., 1., 0., 0., 0.],
+       [0., 0., 0., 0., 0., 0., 0., 1., 0., 0.],
+       [0., 0., 0., 0., 0., 0., 0., 0., 1., 0.],
+       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]])
+
+
+
+
+

linspace creates an array with evenly space numbers. The endpoint optional argument specifies whether the upper range is in the array or not

+
+
+
d = np.linspace(0, 1, 10, endpoint=False)
+print(d)
+
+
+
+
+
[0.  0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9]
+
+
+
+
+
+

Quick Exercise

+

Analogous to linspace(), there is a logspace() function that creates an array with elements equally spaced in log. Use help(np.logspace) to see the arguments, and create an array with 10 elements from \(10^{-6}\) to \(10^3\).

+
+
+
+

Array Operations#

+

most operations (+, -, *, /) will work on an entire array at once, element-by-element.

+

Note that that the multiplication operator is not a matrix multiply, but @ will do matrix multiplication.

+

Let’s create a simply array to start with

+
+
+
a = np.arange(12).reshape(3,4)
+print(a)
+
+
+
+
+
[[ 0  1  2  3]
+ [ 4  5  6  7]
+ [ 8  9 10 11]]
+
+
+
+
+

Multiplication by a scalar multiplies every element

+
+
+
a*2
+
+
+
+
+
array([[ 0,  2,  4,  6],
+       [ 8, 10, 12, 14],
+       [16, 18, 20, 22]])
+
+
+
+
+

adding two arrays adds element-by-element

+
+
+
a + a
+
+
+
+
+
array([[ 0,  2,  4,  6],
+       [ 8, 10, 12, 14],
+       [16, 18, 20, 22]])
+
+
+
+
+

multiplying two arrays multiplies element-by-element

+
+
+
a*a
+
+
+
+
+
array([[  0,   1,   4,   9],
+       [ 16,  25,  36,  49],
+       [ 64,  81, 100, 121]])
+
+
+
+
+
+

Quick Exercise

+

What do you think 1./a will do? Try it and see

+
+

We can think of our 2-d array as a 3 x 4 matrix (3 rows, 4 columns). We can take the transpose to geta 4 x 3 matrix, and then we can do a matrix multiplication

+

We can do some basic linear algebra operations on arrays. The operator @ is a matrix multiplication / dot-product operator

+
+
+
b = a.transpose()
+b
+
+
+
+
+
array([[ 0,  4,  8],
+       [ 1,  5,  9],
+       [ 2,  6, 10],
+       [ 3,  7, 11]])
+
+
+
+
+
+
+
a @ b
+
+
+
+
+
array([[ 14,  38,  62],
+       [ 38, 126, 214],
+       [ 62, 214, 366]])
+
+
+
+
+

We can sum the elements of an array:

+
+
+
a.sum()
+
+
+
+
+
np.int64(66)
+
+
+
+
+
+

Quick Exercise

+

sum() takes an optional argument, axis=N, where N is the axis to sum over. Sum the elements of a across rows to create an array with just the sum along each column of a.

+
+

We can also easily get the extrema

+
+
+
print(a.min(), a.max())
+
+
+
+
+
0 11
+
+
+
+
+
+
+

universal functions#

+

universal functions work element-by-element. Let’s create a new array scaled by pi / 12

+
+
+
b = a*np.pi/12.0
+print(b)
+
+
+
+
+
[[0.         0.26179939 0.52359878 0.78539816]
+ [1.04719755 1.30899694 1.57079633 1.83259571]
+ [2.0943951  2.35619449 2.61799388 2.87979327]]
+
+
+
+
+
+
+
c = np.cos(b)
+print(c)
+
+
+
+
+
[[ 1.00000000e+00  9.65925826e-01  8.66025404e-01  7.07106781e-01]
+ [ 5.00000000e-01  2.58819045e-01  6.12323400e-17 -2.58819045e-01]
+ [-5.00000000e-01 -7.07106781e-01 -8.66025404e-01 -9.65925826e-01]]
+
+
+
+
+
+
+
d = b + c 
+
+
+
+
+
+
+
print(d)
+
+
+
+
+
[[1.         1.22772521 1.38962418 1.49250494]
+ [1.54719755 1.56781598 1.57079633 1.57377667]
+ [1.5943951  1.64908771 1.75196847 1.91386744]]
+
+
+
+
+
+

Quick Exercise

+

We will often want to write our own function that operates on an array and returns a new array. We can do this just like we did with functions previously—the key is to use the methods from the np module to do any operations, since they work on, and return, arrays.

+

Write a simple function that returns \(\sin(2\pi x)\) for an input array x. Then test it +by passing in an array x that you create via linspace()

+
+
+
+

Slicing#

+

slicing works very similarly to how we saw with strings. Remember, python uses 0-based indexing

+

Consider the following array:

+
+
+
a = np.arange(9)
+a
+
+
+
+
+
array([0, 1, 2, 3, 4, 5, 6, 7, 8])
+
+
+
+
+

Now look at accessing a single element vs. a range (using slicing)

+

Giving a single (0-based) index just references a single value

+
+
+
a[2]
+
+
+
+
+
np.int64(2)
+
+
+
+
+

When we do a range, like a[2:5], then we get the elements starting at index 2 and up to, but not including index 5.

+

That is, slicing uses the interval: [begin, end)

+
+
+
a[2:5]
+
+
+
+
+
array([2, 3, 4])
+
+
+
+
+
+
+
a[2:4]
+
+
+
+
+
array([2, 3])
+
+
+
+
+

The : can be used to specify all of the elements in that dimension

+
+
+
a[:]
+
+
+
+
+
array([0, 1, 2, 3, 4, 5, 6, 7, 8])
+
+
+
+
+
+
+

Multidimensional Arrays#

+

Multidimensional arrays are stored in a contiguous space in memory – this means that the columns / rows need to be unraveled (flattened) so that it can be thought of as a single one-dimensional array. Different programming languages do this via different conventions:

+

multidimensional array formatting, row-major vs. column-major

+

Storage order:

+
    +
  • Python/C use row-major storage: rows are stored one after the other

  • +
  • Fortran/matlab use column-major storage: columns are stored one after another

  • +
+

The ordering matters when

+
    +
  • passing arrays between languages (we’ll talk about this later this semester)

  • +
  • looping over arrays – you want to access elements that are next to one-another in memory

    +
      +
    • e.g, in Fortran:

      +
      double precision :: A(M,N)
      +do j = 1, N
      +   do i = 1, M
      +      A(i,j) = …
      +   enddo
      +enddo
      +
      +
      +
    • +
    • in C

      +
      double A[M][N];
      +for (i = 0; i < M; i++) {
      +   for (j = 0; j < N; j++) {
      +      A[i][j] = …
      +   }
      +}  
      +
      +
      +
    • +
    +
  • +
+

In python, using NumPy, we’ll try to avoid explicit loops over elements as much as possible

+

Let’s look at multidimensional arrays:

+
+
+
a = np.arange(15).reshape(3,5)
+a
+
+
+
+
+
array([[ 0,  1,  2,  3,  4],
+       [ 5,  6,  7,  8,  9],
+       [10, 11, 12, 13, 14]])
+
+
+
+
+

Notice that the output of a shows the row-major storage. The rows are grouped together in the inner [...]

+

Giving a single index (0-based) for each dimension just references a single value in the array

+
+
+
a[1,1]
+
+
+
+
+
np.int64(6)
+
+
+
+
+

Doing slices will access a range of elements. Think of the start and stop in the slice as referencing the left-edge of the slots in the array.

+
+
+
a[0:2,0:2]
+
+
+
+
+
array([[0, 1],
+       [5, 6]])
+
+
+
+
+

Access a specific column

+
+
+
a[:,1]
+
+
+
+
+
array([ 1,  6, 11])
+
+
+
+
+

Sometimes we want a one-dimensional view into the array – here we see the memory layout (row-major) more explicitly

+
+
+
a.flatten()
+
+
+
+
+
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])
+
+
+
+
+

we can also iterate – this is done over the first axis (rows)

+
+
+
for row in a:
+    print(row)
+
+
+
+
+
[0 1 2 3 4]
+[5 6 7 8 9]
+[10 11 12 13 14]
+
+
+
+
+

or element by element

+
+
+
for e in a.flat:
+    print(e)
+
+
+
+
+
0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
+
+
+
+

Generally speaking, we want to avoid looping over the elements of an array in python—this is slow. Instead we want to write and use functions that operate on the entire array at once.

+
+

Quick Exercise

+

Consider the array defined as:

+

+ q = np.array([[1, 2, 3, 2, 1],
+               [2, 4, 4, 4, 2],
+               [3, 4, 4, 4, 3],
+               [2, 4, 4, 4, 2],
+               [1, 2, 3, 2, 1]])
+ 
+
+
+
    +
  • using slice notation, create an array that consists of only the 4’s in q (this will be called a view, as we’ll see shortly)

  • +
  • zero out all of the elements in your view

  • +
  • how does q change?

  • +
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/02-numpy/numpy-exercises.html b/02-numpy/numpy-exercises.html new file mode 100644 index 00000000..c6a0e9c3 --- /dev/null +++ b/02-numpy/numpy-exercises.html @@ -0,0 +1,776 @@ + + + + + + + + + + + NumPy exercises — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

NumPy exercises

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

NumPy exercises#

+

Some of these come from / are inspired from rougier/numpy-100 and http://www.scipy-lectures.org/intro/numpy/exercises.html

+

You might want to look over these lists as well.

+
+
+
import numpy as np
+
+
+
+
+
+

Q1#

+

We can use np.random.random_sample() to create an array with random values. By default, these will be in the range [0.0, 1.0). You can +multiple the output and add a scalar to it to get it to be in a different range.

+

Create a 10 x 10 array initialized with random numbers that lie between 0 and 10.

+

Then compute the average of the array (there is a numpy function for this, np.mean()).

+
+
+

Q2#

+

Create the array:

+
[[1,  6, 11],
+ [2,  7, 12],
+ [3,  8, 13],
+ [4,  9, 14],
+ [5, 10, 15]]
+
+
+

with out explicitly typing it in.

+

Now create a new array containing only its 2nd and 4th rows.

+
+
+

Q3#

+

Create a 2d array with 1 on the border and 0 on the inside, e.g., like:

+
1 1 1 1 1
+1 0 0 0 1
+1 0 0 0 1
+1 1 1 1 1
+
+
+

Do this using array slice notation to let it work for an arbitrary-sized array

+
+
+

Q4#

+
    +
  • Create an array with angles in degrees 0, 15, 30, … 90 (i.e., every 15 degrees up to 90).

  • +
  • Now create 3 new arrays with the sine, cosine, and tangent of the elements of the first array

  • +
  • Finally, calculate the inverse sine, inverse cosine, and inverse tangent the arrays above and compare to the original angles

  • +
+
+
+

Q5#

+

Given the array:

+
x = np.array([1, -1, 2, 5, 8, 4, 10, 12, 3])
+
+
+

calculate the difference of each element with its neighbor.

+
+
+

Q6#

+

Here we will read in columns of numbers from a file and create a histogram, using NumPy routines. Make sure you have the data file +“sample.txt” in the same directory as this notebook (you can download it from https://raw.githubusercontent.com/sbu-python-summer/python-tutorial/master/day-3/sample.txt

+
    +
  • Use np.loadtxt() to read this file in.

  • +
  • Next, use np.histogram() to create a histogram array. The output returns both the count and an array of edges.

  • +
  • Finally, loop over the bins and print out the bin center (averaging the left and right edges of the bin) and the count for that bin.

  • +
+
+
+

Q7#

+

NumPy has a standard deviation function, np.std(), but here we’ll write our own that works on a 1-d array (vector). The standard +deviation is a measure of the “width” of the distribution of numbers +in the vector.

+

Given an array, \(a\), and an average \(\bar{a}\), the standard deviation +is:

+
+\[ +\sigma = \left [ \frac{1}{N} \sum_{i=1}^N (a_i - \bar{a})^2 \right ]^{1/2} +\]
+

Write a function to calculate the standard deviation for an input array, a:

+
    +
  • First compute the average of the elements in a to define \(\bar{a}\)

  • +
  • Next compute the sum over the squares of \(a - \bar{a}\)

  • +
  • Then divide the sum by the number of elements in the array

  • +
  • Finally take the square root (you can use np.sqrt())

  • +
+

Test your function on a random array, and compare to the built-in np.std()

+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/02-numpy/numpy.html b/02-numpy/numpy.html new file mode 100644 index 00000000..f6d4c82e --- /dev/null +++ b/02-numpy/numpy.html @@ -0,0 +1,605 @@ + + + + + + + + + + + NumPy — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

NumPy

+ +
+
+ +
+
+
+ + + + +
+ +
+

NumPy#

+

The NumPy library provides a class for n-dimensional arrays of data.

+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/03-practices/git-single.html b/03-practices/git-single.html new file mode 100644 index 00000000..02eea323 --- /dev/null +++ b/03-practices/git-single.html @@ -0,0 +1,730 @@ + + + + + + + + + + + <no title> — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +

A single-user interaction with git:

+
    +
  • Make your project

    +

    We’ll start by making our project directory and moving into it:

    +
    $ mkdir myproject
    +$ cd myproject/
    +
    +
    +
  • +
  • Now have git track this

    +

    Now we do git init . – this tells git to initialize this +directory under version control

    +
    $ git init .
    +
    +
    +

    If we do ls at this point, we see nothing. However, there is a +“hidden” directory called .git/ which we can see by passing -a +to ls:

    +
    $ ls -al
    +$ ls -l .git
    +
    +
    +

    In that directory are the files that git uses to keep track of +changes and the history.

    +
  • +
  • Create a file

    +

    We’ll create a file called README (use whatever editor you like, +I’ll use emacs):

    +
    $ emacs -nw README
    +
    +
    +

    Add some descriptive text to the file and save it. At the moment, +git doesn’t know about this file – you can see this via git status:

    +
    $ git status
    +
    +
    +
  • +
  • Tell git about the file

    +

    Now we need to tell git to start tracking the file. We use +git add for this. Then we need to tell git to store the current +state of the file – we use git commit for this:

    +
    $ git add README
    +$ git commit README
    +
    +
    +

    Notice that an editor window pops up – take the time to give a +descriptive message about the changes.

    +

    If you make more changes to the file, git won’t store them until +you commit them. So you’ll commit the same file over and over as +it changes, but only add it once.

    +
  • +
  • Create another file

    +

    Let’s create a python program, awesome.py with the line:

    +
    print("hello")
    +
    +
    +

    Now add that file and commit it too

    +
    $ git add awesome.py
    +$ git commit .
    +
    +
    +
  • +
  • Look at your log

    +

    git log will show you all the commits, the message you entered +when you made the commit (that helps you understand what was done). +It will also have a “hash” next to the commit (like +dbc2916bb609759d54ca7668558bc639bab9b60b)

    +
    $ git log
    +
    +
    +
  • +
  • Add to you code

    +

    Edit awesome.py and make it a function and have your program call +the function. Now we need to commit this again:

    +
    $ git commit awesome.py 
    +
    +
    +

    And git log will show this commit as separate from the one we made +when we created the file.

    +
  • +
  • Go back in time

    +

    Suppose our code is not working anymore, and we know it was in the +past. We can go back to any previous version of the code by using +checkout and the hash next to that commit (note: your hashes will +be different than mine).

    +
    $ git log
    +$ git checkout dbc2916bb609759d54ca7668558bc639bab9b60b
    +
    +
    +

    Look at the file, and you’ll see it is different.

    +

    If you want to go back to the latest version, you can checkout master +– that’s the name of the main “branch” git recognizes.

    +
    git checkout master
    +
    +
    +
  • +
  • Working with branches

    +

    Suppose we want to do some development that might be invasive and we +don’t want to break the working code on “master”. We can use a +branch for this – thing of this as a parallel development that can +track the master branch and merge back and forth with it. We can +work on this new branch until we are happy, and then incorporate our +changes back to master.

    +

    Here we’ll create a new branch called development:

    +
    $ git checkout -b development
    +
    +
    +

    Now make some changes to awesome.py and commit them.

    +
    $ emacs -nw awesome.py
    +$ git commit awesome.py
    +
    +
    +

    If you go back to master, you won’t see these changes:

    +
    $ git checkout master
    +$ more awesome.py
    +
    +
    +

    If you are happy with the changes, you can do a merge. While on +master, merge development into master with:

    +
    $ git merge development
    +
    +
    +
  • +
+ + + + +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/03-practices/python-style.html b/03-practices/python-style.html new file mode 100644 index 00000000..b18299a1 --- /dev/null +++ b/03-practices/python-style.html @@ -0,0 +1,683 @@ + + + + + + + + + + + Python Style (from the PEP-0008) — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Python Style (from the PEP-0008)

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

Python Style (from the PEP-0008)#

+

This notebook shows the various code snippets from the PEP-0008 style guide: http://legacy.python.org/dev/peps/pep-0008/

+
+

code lay-out#

+
+

indentation#

+

Indentation should use 4 spaces per indentation level (and NOT tabs). Python 3 does not allow for a mixture of tabs and spaces. Note that a lot of editors will map the tab key into a sequence of spaces

+

Continuation lines should align wrapped elements either vertically inside parentheses, brackets, or braces, or using a hanging indent (with no arguments on the first line)

+

note: to prevent ipython from throwing errors that some things are not defined, not all the code is in a code cell.

+
+
+

line length#

+

Lines should be a maximum of 79 characters.

+

The 80-column terminal goes back to the early terminals and page-widths for printers and is pretty much a good standard for any language.

+

Comments and docstrings should be limited to 72-characters

+

Implied line continuation is automatic inside parenthesis, brackets

+
+
+
+ + + + +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/04-matplotlib/ipyvolume-example.html b/04-matplotlib/ipyvolume-example.html new file mode 100644 index 00000000..d3493817 --- /dev/null +++ b/04-matplotlib/ipyvolume-example.html @@ -0,0 +1,758 @@ + + + + + + + + + + + <no title> — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+
+
import numpy as np
+import ipyvolume
+
+
+
+
+
---------------------------------------------------------------------------
+ModuleNotFoundError                       Traceback (most recent call last)
+Cell In[1], line 2
+      1 import numpy as np
+----> 2 import ipyvolume
+
+ModuleNotFoundError: No module named 'ipyvolume'
+
+
+
+
+
+
+
N = 64
+x = np.linspace(0.0, 1.0, N)
+y = np.linspace(0.0, 1.0, N)
+z = np.linspace(0.0, 1.0, N)
+
+x3d, y3d, z3d = np.meshgrid(x, y, z, indexing="ij")
+
+r = np.sqrt((x3d - 0.5)**2 + (y3d - 0.5)**2 + (z3d - 0.5)**2)
+
+
+
+
+
+
+
f = 1.0 + np.sin(x3d*np.pi*5)*np.sin(y3d*np.pi*7)*np.cos(z3d*np.pi*2) * np.exp(-r**2/0.25**2)
+
+
+
+
+
+
+
a = ipyvolume.volshow(f)
+
+
+
+
+
---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+Cell In[4], line 1
+----> 1 a = ipyvolume.volshow(f)
+
+NameError: name 'ipyvolume' is not defined
+
+
+
+
+
+
+
a
+
+
+
+
+
---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+Cell In[5], line 1
+----> 1 a
+
+NameError: name 'a' is not defined
+
+
+
+
+
+
+
import numpy as np
+import ipyvolume as ipv
+V = np.zeros((128,128,128)) # our 3d array
+# outer box
+V[30:-30,30:-30,30:-30] = 0.75
+V[35:-35,35:-35,35:-35] = 0.0
+# inner box
+V[50:-50,50:-50,50:-50] = 0.25
+V[55:-55,55:-55,55:-55] = 0.0
+ipv.quickvolshow(V, level=[0.25, 0.75], opacity=0.03, level_width=0.1, data_min=0, data_max=1)
+
+
+
+
+
---------------------------------------------------------------------------
+ModuleNotFoundError                       Traceback (most recent call last)
+Cell In[6], line 2
+      1 import numpy as np
+----> 2 import ipyvolume as ipv
+      3 V = np.zeros((128,128,128)) # our 3d array
+      4 # outer box
+
+ModuleNotFoundError: No module named 'ipyvolume'
+
+
+
+
+ + + + +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/04-matplotlib/matplotlib-basics.html b/04-matplotlib/matplotlib-basics.html new file mode 100644 index 00000000..bede13e5 --- /dev/null +++ b/04-matplotlib/matplotlib-basics.html @@ -0,0 +1,1222 @@ + + + + + + + + + + + matplotlib — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

matplotlib#

+

Matplotlib is the core plotting package in scientific python. There are others to explore as well (which we’ll chat about on slack).

+
+

Note

+

There are different interfaces for interacting with matplotlib, an interactive, function-driven (state machine) command-set and an object-oriented version. We’ll focus on the OO interface.

+
+
+

Tip

+

To enable interactivity in the plots, install the ipympl package and then +in a cell, run:

+
%matplotlib widget
+
+
+
+
+
+
import numpy as np
+import matplotlib.pyplot as plt
+
+
+
+
+
+

Matplotlib concepts#

+

Matplotlib was designed with the following goals (from mpl docs):

+
    +
  • Plots should look great—publication quality (e.g. antialiased)

  • +
  • Postscript/PDF output for inclusion with TeX documents

  • +
  • Embeddable in a graphical user interface for application development

  • +
  • Code should be easy to understand it and extend

  • +
  • Making plots should be easy

  • +
+

Matplotlib is mostly for 2-d data, but there are some basic 3-d (surface) interfaces.

+

Volumetric data requires a different approach

+ +
+

Importing#

+

There are several different interfaces for matplotlib (see https://matplotlib.org/3.1.1/faq/index.html)

+

Basic ideas:

+
    +
  • matplotlib is the entire package

  • +
  • matplotlib.pyplot is a module within matplotlib that provides easy access to the core plotting routines

  • +
  • pylab combines pyplot and numpy into a single namespace to give a MatLab like interface. You should avoid this—it might be removed in the future.

  • +
+

There are a number of modules that extend its behavior, e.g. basemap for plotting on a sphere, mplot3d for 3-d surfaces

+
+
+

Anatomy of a figure#

+

Figures are the highest level object and can include multiple axes +anatomy of a matplotlib figure showing the different components

+

(figure from: http://matplotlib.org/faq/usage_faq.html#parts-of-a-figure )

+
+
+

Backends#

+

Interactive backends: pygtk, wxpython, tkinter, …

+

Hardcopy backends: PNG, PDF, PS, SVG, …

+
+
+
+
+

Basic plotting#

+
+
+
x = np.linspace(0.0, 2.0*np.pi, 50)
+y = np.cos(x)
+
+
+
+
+

plot() is the most basic command.

+

We’ll use plt.subplots() to create a Figure and Axis object

+
+
+
fig, ax = plt.subplots()
+
+ax.plot(x, y)
+ax.set_xlabel(r"$x$")
+ax.set_ylabel(r"$\cos(x)$")
+ax.set_xlim(0, 2*np.pi)
+
+
+
+
+
(0.0, 6.283185307179586)
+
+
+a plot of a cosine function +
+
+

Here we also see that we can use LaTeX notation for the axes.

+
+

Quick Exercise

+

We can plot 2 lines on a plot simply by calling plot twice. Make a plot with both sin(x) and cos(x) drawn

+
+

we can use symbols instead of lines pretty easily too—and label them

+
+
+
fig, ax = plt.subplots()
+
+ax.plot(x, np.sin(x), marker="o", label="sine")
+ax.plot(x, np.cos(x), marker="x", label="cosine")
+ax.set_xlim(0.0, 2.0*np.pi)
+ax.legend()
+
+
+
+
+
<matplotlib.legend.Legend at 0x7fc0ec335010>
+
+
+a plot of sine and cosine functions +
+
+
+

Tip

+

We can also specify basic style using a “format string” (see https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.plot.html)

+

This has the form '[marker][line][color]'

+
+

Here we can change the linestyle and thickness

+
+
+
fig, ax = plt.subplots()
+ax.plot(x, np.sin(x), linestyle="--", linewidth=3.0)
+ax.plot(x, np.cos(x), linestyle="-")
+
+
+
+
+
[<matplotlib.lines.Line2D at 0x7fc0ec39b770>]
+
+
+sine and cosine functions now with the sine using a dotted line +
+
+

There are predefined styles that can be used too. Generally you need to start from the figure creation for these to take effect

+
+
+
plt.style.available
+
+
+
+
+
['Solarize_Light2',
+ '_classic_test_patch',
+ '_mpl-gallery',
+ '_mpl-gallery-nogrid',
+ 'bmh',
+ 'classic',
+ 'dark_background',
+ 'fast',
+ 'fivethirtyeight',
+ 'ggplot',
+ 'grayscale',
+ 'petroff10',
+ 'seaborn-v0_8',
+ 'seaborn-v0_8-bright',
+ 'seaborn-v0_8-colorblind',
+ 'seaborn-v0_8-dark',
+ 'seaborn-v0_8-dark-palette',
+ 'seaborn-v0_8-darkgrid',
+ 'seaborn-v0_8-deep',
+ 'seaborn-v0_8-muted',
+ 'seaborn-v0_8-notebook',
+ 'seaborn-v0_8-paper',
+ 'seaborn-v0_8-pastel',
+ 'seaborn-v0_8-poster',
+ 'seaborn-v0_8-talk',
+ 'seaborn-v0_8-ticks',
+ 'seaborn-v0_8-white',
+ 'seaborn-v0_8-whitegrid',
+ 'tableau-colorblind10']
+
+
+
+
+
+
+
plt.style.use("fivethirtyeight")
+
+fig = plt.figure()
+ax = fig.add_subplot(111)
+ax.plot(x, np.sin(x), linestyle="--", linewidth=3.0)
+ax.plot(x, np.cos(x), linestyle="-")
+ax.set_xlim(0.0, 2.0*np.pi)
+
+
+
+
+
(0.0, 6.283185307179586)
+
+
+a different theme -- now the figure background is gray and the font and colors are different +
+
+
+
+
plt.style.use("default")
+
+
+
+
+
+
+

Multiple axes#

+

There are a wide range of methods for putting multiple axes on a grid. We’ll look at the simplest method.

+

The add_subplot() method we’ve been using can take 3 numbers: the number of rows, number of columns, and current index

+
+
+
fig = plt.figure()
+
+ax1 = fig.add_subplot(211)
+
+x = np.linspace(0,5, 100)
+ax1.plot(x, x**3 - 4*x)
+ax1.set_xlabel("x")
+ax1.set_ylabel(r"$x^3 - 4x$", fontsize="large")
+
+ax2 = fig.add_subplot(212)
+
+ax2.plot(x, np.exp(-x**2))
+ax2.set_xlabel("x")
+ax2.set_ylabel("Gaussian")
+
+# log scale
+ax2.set_yscale("log")
+
+# tight_layout() makes sure things don't overlap
+fig.tight_layout()
+
+
+
+
+two axes stacked vertically, with a cubic function drawn on the top and a Gaussian (log-scale) on the bottom +
+
+
+
+

Visualizing 2-d array data#

+

2-d datasets consist of (x, y) pairs and a value associated with that point. Here we create a 2-d Gaussian, using the meshgrid() function to define a rectangular set of points.

+
+
+
def g(x, y):
+    return np.exp(-((x-0.5)**2)/0.1**2 - ((y-0.5)**2)/0.2**2)
+
+N = 100
+
+x = np.linspace(0.0, 1.0, N)
+y = x.copy()
+
+xv, yv = np.meshgrid(x, y)
+
+
+
+
+

A “heatmap” style plot assigns colors to the data values. A lot of work has gone into the latest matplotlib to define a colormap that works good for colorblindness and black-white printing.

+
+
+
fig, ax = plt.subplots()
+
+im = ax.imshow(g(xv, yv), origin="lower")
+fig.colorbar(im, ax=ax)
+
+
+
+
+
<matplotlib.colorbar.Colorbar at 0x7fc0e5e530e0>
+
+
+a heat-map of a 2D Gaussian +
+
+

Sometimes we want to show just contour lines—like on a topographic map. The contour() function does this for us.

+
+
+
fig, ax = plt.subplots()
+
+contours = ax.contour(g(xv, yv))
+ax.axis("equal")   # this adjusts the size of image to make x and y lengths equal
+
+
+
+
+
(np.float64(0.0), np.float64(99.0), np.float64(0.0), np.float64(99.0))
+
+
+contour plot of a 2D Gaussian +
+
+
+

Quick Exercise

+

Contour plots can label the contours, using the ax.clabel() function. +Try adding labels to this contour plot.

+
+
+
+

Error bars#

+

For experiments, we often have errors associated with the \(y\) values. Here we create some data and add some noise to it, then plot it with errors.

+
+
+
def y_experiment(a1, a2, sigma, x):
+    """ return the experimental data in a linear + random fashion a1
+        is the intercept, a2 is the slope, and sigma is the error """
+
+    N = len(x)
+
+    # standard_normal gives samples from the "standard normal" distribution
+    rng = np.random.default_rng()
+    r = rng.standard_normal(N)
+    y = a1 + a2*x + sigma*r
+    return y
+
+N = 40
+x = np.linspace(0.0, 100.0, N)
+sigma = 25.0*np.ones(N)
+y = y_experiment(10.0, 3.0, sigma, x)
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+ax.errorbar(x, y, yerr=sigma, fmt="o")
+
+
+
+
+
<ErrorbarContainer object of 3 artists>
+
+
+a plot showing data points with vertical error bars +
+
+
+
+

Annotations#

+

adding text and annotations is easy

+
+
+
xx = np.linspace(0, 2.0*np.pi, 1000)
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+ax.plot(xx, np.sin(xx))
+ax.text(np.pi/2, np.sin(np.pi/2), r"maximum")
+
+
+
+
+
Text(1.5707963267948966, 1.0, 'maximum')
+
+
+a sine wave with the peak labeled "maximum" +
+
+

we can also turn off the top and right “splines”

+
+
+
ax.spines['right'].set_visible(False)
+ax.spines['top'].set_visible(False)
+ax.xaxis.set_ticks_position('bottom')                                           
+ax.yaxis.set_ticks_position('left') 
+fig
+
+
+
+
+a sine wave with the peak labeled "maximum" and only the left and lower axes drawn +
+
+

Annotations with an arrow are also possible

+
+
+
#example from http://matplotlib.org/examples/pylab_examples/annotation_demo.html
+fig = plt.figure()
+ax = fig.add_subplot(111, projection='polar')
+r = np.arange(0, 1, 0.001)
+theta = 2*2*np.pi*r
+line, = ax.plot(theta, r, color='#ee8d18', lw=3)
+
+ind = 800
+thisr, thistheta = r[ind], theta[ind]
+ax.plot([thistheta], [thisr], 'o')
+ax.annotate('a polar annotation',
+            xy=(thistheta, thisr),  # theta, radius
+            xytext=(0.05, 0.05),    # fraction, fraction
+            textcoords='figure fraction',
+            arrowprops=dict(facecolor='black', shrink=0.05),
+            horizontalalignment='left',
+            verticalalignment='bottom',
+            )
+
+
+
+
+
Text(0.05, 0.05, 'a polar annotation')
+
+
+a polar plot of a spiral with a point labeled +
+
+
+
+

Surface plots#

+

matplotlib can’t deal with true 3-d data (i.e., x,y,z + a value), but it can plot 2-d surfaces and lines in 3-d.

+
+
+
from mpl_toolkits.mplot3d import Axes3D
+fig = plt.figure()
+ax = plt.axes(projection="3d")
+
+# parametric curves
+N = 100
+theta = np.linspace(-4*np.pi, 4*np.pi, N)
+z = np.linspace(-2, 2, N)
+r = z**2 + 1
+
+x = r*np.sin(theta)
+y = r*np.cos(theta)
+
+ax.plot(x,y,z)
+
+
+
+
+
[<mpl_toolkits.mplot3d.art3d.Line3D at 0x7fc0e3906660>]
+
+
+a 3D plot of a curve that spirals around the vertical axis +
+
+
+
+
fig = plt.figure()
+ax = plt.axes(projection="3d")
+
+X = np.arange(-5,5, 0.25)
+Y = np.arange(-5,5, 0.25)
+X, Y = np.meshgrid(X, Y)
+R = np.sqrt(X**2 + Y**2)
+Z = np.sin(R)
+
+surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap="coolwarm")
+
+# and the view (note: most interactive backends will allow you to rotate this freely)
+ax.azim = 90
+ax.elev = 40
+
+
+
+
+a surface plot of a function colored by z value +
+
+
+
+

Histograms#

+

here we generate a bunch of gaussian-normalized random numbers and make a histogram. The probability distribution should match +$\(y(x) = \frac{1}{\sigma \sqrt{2\pi}} e^{-x^2/(2\sigma^2)}\)$

+
+
+
N = 10000
+rng = np.random.default_rng()
+r = rng.standard_normal(N)
+
+fig, ax = plt.subplots()
+ax.hist(r, density=True, bins=20)
+
+x = np.linspace(-5,5,200)
+sigma = 1.0
+ax.plot(x, np.exp(-x**2/(2*sigma**2)) / (sigma*np.sqrt(2.0*np.pi)),
+        c="C1", lw=2)
+ax.set_xlabel("x")
+
+
+
+
+
Text(0.5, 0, 'x')
+
+
+a histogram of Gaussian random numbers, with a Gaussian function plotted matching it well +
+
+
+
+

Final fun#

+

if you want to make things look hand-drawn in the style of xkcd, rerun these examples after doing +plt.xkcd()

+
+
+
plt.xkcd()
+
+
+
+
+
<contextlib.ExitStack at 0x7fc0ede37700>
+
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/04-matplotlib/matplotlib-basics.html.backup b/04-matplotlib/matplotlib-basics.html.backup new file mode 100644 index 00000000..5f1b11e9 --- /dev/null +++ b/04-matplotlib/matplotlib-basics.html.backup @@ -0,0 +1,1236 @@ + + + + + + + + + + + matplotlib — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

matplotlib#

+

Matplotlib is the core plotting package in scientific python. There are others to explore as well (which we’ll chat about on slack).

+
+

Note

+

There are different interfaces for interacting with matplotlib, an interactive, function-driven (state machine) command-set and an object-oriented version. We’ll focus on the OO interface.

+
+
+

Tip

+

To enable interactivity in the plots, install the ipympl package and then +in a cell, run:

+
%matplotlib widget
+
+
+
+
+
+
import numpy as np
+import matplotlib.pyplot as plt
+
+
+
+
+
+

Matplotlib concepts#

+

Matplotlib was designed with the following goals (from mpl docs):

+
    +
  • Plots should look great—publication quality (e.g. antialiased)

  • +
  • Postscript/PDF output for inclusion with TeX documents

  • +
  • Embeddable in a graphical user interface for application development

  • +
  • Code should be easy to understand it and extend

  • +
  • Making plots should be easy

  • +
+

Matplotlib is mostly for 2-d data, but there are some basic 3-d (surface) interfaces.

+

Volumetric data requires a different approach

+ +
+

Importing#

+

There are several different interfaces for matplotlib (see https://matplotlib.org/3.1.1/faq/index.html)

+

Basic ideas:

+
    +
  • matplotlib is the entire package

  • +
  • matplotlib.pyplot is a module within matplotlib that provides easy access to the core plotting routines

  • +
  • pylab combines pyplot and numpy into a single namespace to give a MatLab like interface. You should avoid this—it might be removed in the future.

  • +
+

There are a number of modules that extend its behavior, e.g. basemap for plotting on a sphere, mplot3d for 3-d surfaces

+
+
+

Anatomy of a figure#

+

Figures are the highest level object and can include multiple axes +anatomy of a matplotlib figure showing the different components

+

(figure from: http://matplotlib.org/faq/usage_faq.html#parts-of-a-figure )

+
+
+

Backends#

+

Interactive backends: pygtk, wxpython, tkinter, …

+

Hardcopy backends: PNG, PDF, PS, SVG, …

+
+
+
+
+

Basic plotting#

+
+
+
x = np.linspace(0.0, 2.0*np.pi, 50)
+y = np.cos(x)
+
+
+
+
+

plot() is the most basic command.

+

We’ll use plt.subplots() to create a Figure and Axis object

+
+
+
fig, ax = plt.subplots()
+
+ax.plot(x, y)
+ax.set_xlabel(r"$x$")
+ax.set_ylabel(r"$\cos(x)$")
+ax.set_xlim(0, 2*np.pi)
+# alt-text: a plot of a cosine function
+
+
+
+
+
(0.0, 6.283185307179586)
+
+
+../_images/9231241e6bd766784356e576c64f4a902c6f34847590b61274f8a9b8380916b1.png +
+
+

Here we also see that we can use LaTeX notation for the axes.

+
+

Quick Exercise

+

We can plot 2 lines on a plot simply by calling plot twice. Make a plot with both sin(x) and cos(x) drawn

+
+

we can use symbols instead of lines pretty easily too—and label them

+
+
+
fig, ax = plt.subplots()
+
+ax.plot(x, np.sin(x), marker="o", label="sine")
+ax.plot(x, np.cos(x), marker="x", label="cosine")
+ax.set_xlim(0.0, 2.0*np.pi)
+ax.legend()
+# alt-text: a plot of sine and cosine functions
+
+
+
+
+
<matplotlib.legend.Legend at 0x7fc0ec335010>
+
+
+../_images/ea36916fe87eb56d146e30bc3732345060fe752538e655250838ad92c5d3b836.png +
+
+
+

Tip

+

We can also specify basic style using a “format string” (see https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.plot.html)

+

This has the form '[marker][line][color]'

+
+

Here we can change the linestyle and thickness

+
+
+
fig, ax = plt.subplots()
+ax.plot(x, np.sin(x), linestyle="--", linewidth=3.0)
+ax.plot(x, np.cos(x), linestyle="-")
+# alt-text: sine and cosine functions now with the sine using a dotted line
+
+
+
+
+
[<matplotlib.lines.Line2D at 0x7fc0ec39b770>]
+
+
+../_images/c5438daabd7e7caf26009be4c16d43bb4bc5a754ef48f6b7a6a16440848bdb20.png +
+
+

There are predefined styles that can be used too. Generally you need to start from the figure creation for these to take effect

+
+
+
plt.style.available
+
+
+
+
+
['Solarize_Light2',
+ '_classic_test_patch',
+ '_mpl-gallery',
+ '_mpl-gallery-nogrid',
+ 'bmh',
+ 'classic',
+ 'dark_background',
+ 'fast',
+ 'fivethirtyeight',
+ 'ggplot',
+ 'grayscale',
+ 'petroff10',
+ 'seaborn-v0_8',
+ 'seaborn-v0_8-bright',
+ 'seaborn-v0_8-colorblind',
+ 'seaborn-v0_8-dark',
+ 'seaborn-v0_8-dark-palette',
+ 'seaborn-v0_8-darkgrid',
+ 'seaborn-v0_8-deep',
+ 'seaborn-v0_8-muted',
+ 'seaborn-v0_8-notebook',
+ 'seaborn-v0_8-paper',
+ 'seaborn-v0_8-pastel',
+ 'seaborn-v0_8-poster',
+ 'seaborn-v0_8-talk',
+ 'seaborn-v0_8-ticks',
+ 'seaborn-v0_8-white',
+ 'seaborn-v0_8-whitegrid',
+ 'tableau-colorblind10']
+
+
+
+
+
+
+
plt.style.use("fivethirtyeight")
+
+fig = plt.figure()
+ax = fig.add_subplot(111)
+ax.plot(x, np.sin(x), linestyle="--", linewidth=3.0)
+ax.plot(x, np.cos(x), linestyle="-")
+ax.set_xlim(0.0, 2.0*np.pi)
+# alt-text: a different theme -- now the figure background is gray and the font and colors are different
+
+
+
+
+
(0.0, 6.283185307179586)
+
+
+../_images/5617d55160afd4564a01ed069520d3f30915b23d9d997f8675ac844c00e19c29.png +
+
+
+
+
plt.style.use("default")
+
+
+
+
+
+
+

Multiple axes#

+

There are a wide range of methods for putting multiple axes on a grid. We’ll look at the simplest method.

+

The add_subplot() method we’ve been using can take 3 numbers: the number of rows, number of columns, and current index

+
+
+
fig = plt.figure()
+
+ax1 = fig.add_subplot(211)
+
+x = np.linspace(0,5, 100)
+ax1.plot(x, x**3 - 4*x)
+ax1.set_xlabel("x")
+ax1.set_ylabel(r"$x^3 - 4x$", fontsize="large")
+
+ax2 = fig.add_subplot(212)
+
+ax2.plot(x, np.exp(-x**2))
+ax2.set_xlabel("x")
+ax2.set_ylabel("Gaussian")
+
+# log scale
+ax2.set_yscale("log")
+
+# tight_layout() makes sure things don't overlap
+fig.tight_layout()
+# alt-text: two axes stacked vertically, with a cubic function drawn on the top and a Gaussian (log-scale) on the bottom
+
+
+
+
+../_images/05b93724c7fe07ec65ef5c1c21c6785ef2c54d0a8ded3cc3d404a1d49e57ead3.png +
+
+
+
+

Visualizing 2-d array data#

+

2-d datasets consist of (x, y) pairs and a value associated with that point. Here we create a 2-d Gaussian, using the meshgrid() function to define a rectangular set of points.

+
+
+
def g(x, y):
+    return np.exp(-((x-0.5)**2)/0.1**2 - ((y-0.5)**2)/0.2**2)
+
+N = 100
+
+x = np.linspace(0.0, 1.0, N)
+y = x.copy()
+
+xv, yv = np.meshgrid(x, y)
+
+
+
+
+

A “heatmap” style plot assigns colors to the data values. A lot of work has gone into the latest matplotlib to define a colormap that works good for colorblindness and black-white printing.

+
+
+
fig, ax = plt.subplots()
+
+im = ax.imshow(g(xv, yv), origin="lower")
+fig.colorbar(im, ax=ax)
+# alt-text: a heat-map of a 2D Gaussian
+
+
+
+
+
<matplotlib.colorbar.Colorbar at 0x7fc0e5e530e0>
+
+
+../_images/9ad115d2a594e0d189e996c0204cda78b2df12f8e9d61a968cab4d3ecaecd970.png +
+
+

Sometimes we want to show just contour lines—like on a topographic map. The contour() function does this for us.

+
+
+
fig, ax = plt.subplots()
+
+contours = ax.contour(g(xv, yv))
+ax.axis("equal")   # this adjusts the size of image to make x and y lengths equal
+# alt-text: contour plot of a 2D Gaussian
+
+
+
+
+
(np.float64(0.0), np.float64(99.0), np.float64(0.0), np.float64(99.0))
+
+
+../_images/18bb87f4a28b0bb4588355d6203a23546722fd40585a27f99318e66e528bee84.png +
+
+
+

Quick Exercise

+

Contour plots can label the contours, using the ax.clabel() function. +Try adding labels to this contour plot.

+
+
+
+

Error bars#

+

For experiments, we often have errors associated with the \(y\) values. Here we create some data and add some noise to it, then plot it with errors.

+
+
+
def y_experiment(a1, a2, sigma, x):
+    """ return the experimental data in a linear + random fashion a1
+        is the intercept, a2 is the slope, and sigma is the error """
+
+    N = len(x)
+
+    # standard_normal gives samples from the "standard normal" distribution
+    rng = np.random.default_rng()
+    r = rng.standard_normal(N)
+    y = a1 + a2*x + sigma*r
+    return y
+
+N = 40
+x = np.linspace(0.0, 100.0, N)
+sigma = 25.0*np.ones(N)
+y = y_experiment(10.0, 3.0, sigma, x)
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+ax.errorbar(x, y, yerr=sigma, fmt="o")
+# alt-text: a plot showing data points with vertical error bars
+
+
+
+
+
<ErrorbarContainer object of 3 artists>
+
+
+../_images/b830e2cee583d9a13af607d32039bfdb4fd20e74472e1fcbd358ecace9ed6981.png +
+
+
+
+

Annotations#

+

adding text and annotations is easy

+
+
+
xx = np.linspace(0, 2.0*np.pi, 1000)
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+ax.plot(xx, np.sin(xx))
+ax.text(np.pi/2, np.sin(np.pi/2), r"maximum")
+# alt-text: a sine wave with the peak labeled "maximum"
+
+
+
+
+
Text(1.5707963267948966, 1.0, 'maximum')
+
+
+../_images/9117b21318fe3c3165d87c5d4c46d5a1b3c429cc8cf31bebed9c203fda6b40d9.png +
+
+

we can also turn off the top and right “splines”

+
+
+
ax.spines['right'].set_visible(False)
+ax.spines['top'].set_visible(False)
+ax.xaxis.set_ticks_position('bottom')                                           
+ax.yaxis.set_ticks_position('left') 
+fig
+# alt-text: a sine wave with the peak labeled "maximum" and only the left and lower axes drawn
+
+
+
+
+../_images/3ec9e8820552c29bedb8733a09eef410e319daa5f2f917b9366051c4e9f6b13d.png +
+
+

Annotations with an arrow are also possible

+
+
+
#example from http://matplotlib.org/examples/pylab_examples/annotation_demo.html
+fig = plt.figure()
+ax = fig.add_subplot(111, projection='polar')
+r = np.arange(0, 1, 0.001)
+theta = 2*2*np.pi*r
+line, = ax.plot(theta, r, color='#ee8d18', lw=3)
+
+ind = 800
+thisr, thistheta = r[ind], theta[ind]
+ax.plot([thistheta], [thisr], 'o')
+ax.annotate('a polar annotation',
+            xy=(thistheta, thisr),  # theta, radius
+            xytext=(0.05, 0.05),    # fraction, fraction
+            textcoords='figure fraction',
+            arrowprops=dict(facecolor='black', shrink=0.05),
+            horizontalalignment='left',
+            verticalalignment='bottom',
+            )
+# alt-text: a polar plot of a spiral with a point labeled 
+
+
+
+
+
Text(0.05, 0.05, 'a polar annotation')
+
+
+../_images/567340fd5c0f4220f685c50adc59432167e90edc971cdc98c37610d9cc7e7f4c.png +
+
+
+
+

Surface plots#

+

matplotlib can’t deal with true 3-d data (i.e., x,y,z + a value), but it can plot 2-d surfaces and lines in 3-d.

+
+
+
from mpl_toolkits.mplot3d import Axes3D
+fig = plt.figure()
+ax = plt.axes(projection="3d")
+
+# parametric curves
+N = 100
+theta = np.linspace(-4*np.pi, 4*np.pi, N)
+z = np.linspace(-2, 2, N)
+r = z**2 + 1
+
+x = r*np.sin(theta)
+y = r*np.cos(theta)
+
+ax.plot(x,y,z)
+# alt-text: a 3D plot of a curve that spirals around the vertical axis
+
+
+
+
+
[<mpl_toolkits.mplot3d.art3d.Line3D at 0x7fc0e3906660>]
+
+
+../_images/cff06e6db54c63aafc594447299f1f54fa904a8c1ffdcd6337a6f738f0d98336.png +
+
+
+
+
fig = plt.figure()
+ax = plt.axes(projection="3d")
+
+X = np.arange(-5,5, 0.25)
+Y = np.arange(-5,5, 0.25)
+X, Y = np.meshgrid(X, Y)
+R = np.sqrt(X**2 + Y**2)
+Z = np.sin(R)
+
+surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap="coolwarm")
+
+# and the view (note: most interactive backends will allow you to rotate this freely)
+ax.azim = 90
+ax.elev = 40
+# alt-text: a surface plot of a function colored by z value
+
+
+
+
+../_images/fb0a6c80516310178afbce3044be9cff9c3d04e75390440cb7f125480aa50ee5.png +
+
+
+
+

Histograms#

+

here we generate a bunch of gaussian-normalized random numbers and make a histogram. The probability distribution should match +$\(y(x) = \frac{1}{\sigma \sqrt{2\pi}} e^{-x^2/(2\sigma^2)}\)$

+
+
+
N = 10000
+rng = np.random.default_rng()
+r = rng.standard_normal(N)
+
+fig, ax = plt.subplots()
+ax.hist(r, density=True, bins=20)
+
+x = np.linspace(-5,5,200)
+sigma = 1.0
+ax.plot(x, np.exp(-x**2/(2*sigma**2)) / (sigma*np.sqrt(2.0*np.pi)),
+        c="C1", lw=2)
+ax.set_xlabel("x")
+# alt-text: a histogram of Gaussian random numbers, with a Gaussian function plotted matching it well
+
+
+
+
+
Text(0.5, 0, 'x')
+
+
+../_images/6e87cfca18df7ece7614e5650e85111b46d544693ed61be588b1a338177b4788.png +
+
+
+
+

Final fun#

+

if you want to make things look hand-drawn in the style of xkcd, rerun these examples after doing +plt.xkcd()

+
+
+
plt.xkcd()
+
+
+
+
+
<contextlib.ExitStack at 0x7fc0ede37700>
+
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/04-matplotlib/matplotlib-exercises.html b/04-matplotlib/matplotlib-exercises.html new file mode 100644 index 00000000..da03a69e --- /dev/null +++ b/04-matplotlib/matplotlib-exercises.html @@ -0,0 +1,835 @@ + + + + + + + + + + + matplotlib exercises — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

matplotlib exercises#

+
+
+
import matplotlib.pyplot as plt
+import numpy as np
+
+
+
+
+
+

Q1: planetary positions#

+

The distances of the planets from the Sun (technically, their semi-major axes) are:

+
+
+
a = np.array([0.39, 0.72, 1.00, 1.52, 5.20, 9.54, 19.22, 30.06, 39.48])
+
+
+
+
+

These are in units where the Earth-Sun distance is 1 (astronomical units).

+

The corresponding periods of their orbits (how long they take to go once around the Sun) are, in years

+
+
+
P = np.array([0.24, 0.62, 1.00, 1.88, 11.86, 29.46, 84.01, 164.8, 248.09])
+
+
+
+
+

Finally, the names of the planets corresponding to these are:

+
+
+
names = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", 
+         "Uranus", "Neptune", "Pluto"]
+
+
+
+
+

(technically, pluto isn’t a planet anymore, but we still love it :)

+
    +
  • Plot as points, the periods vs. distances for each planet on a log-log plot.

  • +
  • Write the name of the planet next to the point for that planet on the plot

  • +
+
+
+

Q2: drawing a circle#

+

For an angle \(\theta\) in the range \(\theta \in [0, 2\pi]\), the polar equations of a circle of radius \(R\) are:

+
+\[x = R\cos(\theta)\]
+
+\[y = R\sin(\theta)\]
+

We want to draw a circle.

+
    +
  • Create an array to hold the theta values—the more we use, the smoother the circle will be

  • +
  • Create x and y arrays from theta for your choice of \(R\)

  • +
  • Plot y vs. x

  • +
+

Now, look up the matplotlib fill() function, and draw a circle filled in with a solid color.

+
+
+

Q3: Circles, circles, circles…#

+

Generalize your circle drawing commands to produce a function,

+
draw_circle(x0, y0, R, color)
+
+
+

that draws the circle. Here, (x0, y0) is the center of the circle, R is the radius, and color is the color of the circle.

+

Now randomly draw 10 circles at different locations, with random radii, and random colors on the same plot.

+
+
+

Q4: Climate#

+

Download the data file of global surface air temperature averages from here: +https://raw.githubusercontent.com/sbu-python-summer/python-tutorial/master/day-4/nasa-giss.txt

+

(this data comes from: https://data.giss.nasa.gov/gistemp/graphs/)

+

There are 3 columns here: the year, the temperature change, and a smoothed representation of the temperature change.

+
    +
  • Read in this data using np.loadtxt().

  • +
  • Plot as a line the smoothed representation of the temperature changes.

  • +
  • Plot as points the temperature change (no smoothing). Color the points blue if they are < 0 and color them red if they are >= 0

  • +
+

You might find the NumPy where() function useful.

+
+
+

Q5: subplots#

+

matplotlib has a number of ways to create multiple axes in a figure – look at plt.subplots() (http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.subplot)

+

Create an x array using NumPy with a number of points, spanning from \([0, 2\pi]\).

+

Create 3 axes vertically, and do the following:

+
    +
  • Define a new numpy array f initialized to a function of your choice.

  • +
  • Plot f in the top axes

  • +
  • Compute a numerical derivative of f, +$\( f' = \frac{f_{i+1} - f_i}{\Delta x}\)$ +and plot this in the middle axes

  • +
  • Do this again, this time on \(f'\) to compute the second derivative and plot that in the bottom axes

  • +
+
+
+

Q6: Mandelbrot set#

+

The Mandelbrot set is defined such that \(z_{k+1} = z_k^2 + c\) +remains bounded, which is usually taken as \(|z_{k+1}| \le 2\) +where \(c\) is a complex number and we start with \(z_0 = 0\)

+

We want to consider a range of \(c\), as complex numbers \(c = x + iy\), +where \(-2 < x < 2\) and \(-2 < y < 2\).

+

For each \(c\), identify its position on a Cartesian grid as \((x,y)\) and +assign a value \(N\) that is the number of iterations, \(k\), required for \(|z_{k+1}|\) to become greater than \(2\).

+

The plot of this function is called the Mandelbrot set.

+

Here’s a simple implementation that just does a fixed number of iterations and then colors points in Z depending on whether they satisfy \(|z| \le 2\).

+

Your task is to extend this to record the number of iterations it takes for each point in the Z-plane to violate that constraint, +and then plot that data – it will show more structure

+
+
+
N = 256
+x = np.linspace(-2, 2, N)
+y = np.linspace(-2, 2, N)
+
+xv, yv = np.meshgrid(x, y, indexing="ij")
+
+
+
+
+
+
+
c = xv + 1j*y
+
+z = np.zeros((N, N), dtype=np.complex128)
+
+for i in range(10):
+    z = z**2 + c
+    
+m = np.ones((N, N))
+m[np.abs(z) <= 2] = 0.0
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+ax.imshow(m)
+
+
+
+
+
<matplotlib.image.AxesImage at 0x7fdbfb3add30>
+
+
+a plot of the Mandelbrot set +
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/04-matplotlib/matplotlib-exercises.html.backup b/04-matplotlib/matplotlib-exercises.html.backup new file mode 100644 index 00000000..0648a8cf --- /dev/null +++ b/04-matplotlib/matplotlib-exercises.html.backup @@ -0,0 +1,836 @@ + + + + + + + + + + + matplotlib exercises — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

matplotlib exercises#

+
+
+
import matplotlib.pyplot as plt
+import numpy as np
+
+
+
+
+
+

Q1: planetary positions#

+

The distances of the planets from the Sun (technically, their semi-major axes) are:

+
+
+
a = np.array([0.39, 0.72, 1.00, 1.52, 5.20, 9.54, 19.22, 30.06, 39.48])
+
+
+
+
+

These are in units where the Earth-Sun distance is 1 (astronomical units).

+

The corresponding periods of their orbits (how long they take to go once around the Sun) are, in years

+
+
+
P = np.array([0.24, 0.62, 1.00, 1.88, 11.86, 29.46, 84.01, 164.8, 248.09])
+
+
+
+
+

Finally, the names of the planets corresponding to these are:

+
+
+
names = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", 
+         "Uranus", "Neptune", "Pluto"]
+
+
+
+
+

(technically, pluto isn’t a planet anymore, but we still love it :)

+
    +
  • Plot as points, the periods vs. distances for each planet on a log-log plot.

  • +
  • Write the name of the planet next to the point for that planet on the plot

  • +
+
+
+

Q2: drawing a circle#

+

For an angle \(\theta\) in the range \(\theta \in [0, 2\pi]\), the polar equations of a circle of radius \(R\) are:

+
+\[x = R\cos(\theta)\]
+
+\[y = R\sin(\theta)\]
+

We want to draw a circle.

+
    +
  • Create an array to hold the theta values—the more we use, the smoother the circle will be

  • +
  • Create x and y arrays from theta for your choice of \(R\)

  • +
  • Plot y vs. x

  • +
+

Now, look up the matplotlib fill() function, and draw a circle filled in with a solid color.

+
+
+

Q3: Circles, circles, circles…#

+

Generalize your circle drawing commands to produce a function,

+
draw_circle(x0, y0, R, color)
+
+
+

that draws the circle. Here, (x0, y0) is the center of the circle, R is the radius, and color is the color of the circle.

+

Now randomly draw 10 circles at different locations, with random radii, and random colors on the same plot.

+
+
+

Q4: Climate#

+

Download the data file of global surface air temperature averages from here: +https://raw.githubusercontent.com/sbu-python-summer/python-tutorial/master/day-4/nasa-giss.txt

+

(this data comes from: https://data.giss.nasa.gov/gistemp/graphs/)

+

There are 3 columns here: the year, the temperature change, and a smoothed representation of the temperature change.

+
    +
  • Read in this data using np.loadtxt().

  • +
  • Plot as a line the smoothed representation of the temperature changes.

  • +
  • Plot as points the temperature change (no smoothing). Color the points blue if they are < 0 and color them red if they are >= 0

  • +
+

You might find the NumPy where() function useful.

+
+
+

Q5: subplots#

+

matplotlib has a number of ways to create multiple axes in a figure – look at plt.subplots() (http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.subplot)

+

Create an x array using NumPy with a number of points, spanning from \([0, 2\pi]\).

+

Create 3 axes vertically, and do the following:

+
    +
  • Define a new numpy array f initialized to a function of your choice.

  • +
  • Plot f in the top axes

  • +
  • Compute a numerical derivative of f, +$\( f' = \frac{f_{i+1} - f_i}{\Delta x}\)$ +and plot this in the middle axes

  • +
  • Do this again, this time on \(f'\) to compute the second derivative and plot that in the bottom axes

  • +
+
+
+

Q6: Mandelbrot set#

+

The Mandelbrot set is defined such that \(z_{k+1} = z_k^2 + c\) +remains bounded, which is usually taken as \(|z_{k+1}| \le 2\) +where \(c\) is a complex number and we start with \(z_0 = 0\)

+

We want to consider a range of \(c\), as complex numbers \(c = x + iy\), +where \(-2 < x < 2\) and \(-2 < y < 2\).

+

For each \(c\), identify its position on a Cartesian grid as \((x,y)\) and +assign a value \(N\) that is the number of iterations, \(k\), required for \(|z_{k+1}|\) to become greater than \(2\).

+

The plot of this function is called the Mandelbrot set.

+

Here’s a simple implementation that just does a fixed number of iterations and then colors points in Z depending on whether they satisfy \(|z| \le 2\).

+

Your task is to extend this to record the number of iterations it takes for each point in the Z-plane to violate that constraint, +and then plot that data – it will show more structure

+
+
+
N = 256
+x = np.linspace(-2, 2, N)
+y = np.linspace(-2, 2, N)
+
+xv, yv = np.meshgrid(x, y, indexing="ij")
+
+
+
+
+
+
+
c = xv + 1j*y
+
+z = np.zeros((N, N), dtype=np.complex128)
+
+for i in range(10):
+    z = z**2 + c
+    
+m = np.ones((N, N))
+m[np.abs(z) <= 2] = 0.0
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+ax.imshow(m)
+# alt-text: a plot of the Mandelbrot set
+
+
+
+
+
<matplotlib.image.AxesImage at 0x7fdbfb3add30>
+
+
+../_images/6bc78f3fe2a9e65e3d606852a276b997d3deff24f08686a8548303dc6c3d334c.png +
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/04-matplotlib/matplotlib.html b/04-matplotlib/matplotlib.html new file mode 100644 index 00000000..c517a6be --- /dev/null +++ b/04-matplotlib/matplotlib.html @@ -0,0 +1,605 @@ + + + + + + + + + + + matplotlib — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

matplotlib

+ +
+
+ +
+
+
+ + + + +
+ +
+

matplotlib#

+

matplotlib is the core plotting library for python.

+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/05-scipy/scipy-basics-2.html b/05-scipy/scipy-basics-2.html new file mode 100644 index 00000000..685088fa --- /dev/null +++ b/05-scipy/scipy-basics-2.html @@ -0,0 +1,1500 @@ + + + + + + + + + + + More SciPy — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

More SciPy#

+
+
+
import numpy as np
+import matplotlib.pyplot as plt
+
+
+
+
+
+

Fitting#

+

Fitting is used to match a model to experimental data. E.g. we have N points of \((x_i, y_i)\) with associated errors, \(\sigma_i\), and we want to find a simply function that best represents the data.

+

Usually this means that we will need to define a metric, often called the residual, for how well our function matches the data, and then minimize this residual. Least-squares fitting is a popular formulation.

+

We want to fit our data to a function \(Y(x, \{a_j\})\), where \(a_j\) are model parameters we can adjust. We want to find the optimal \(a_j\) to minimize the distance of \(Y\) from our data, measured parallel to the \(y\)-axis:

+
+\[\Delta_i = Y(x_i, \{a_j\}) - y_i\]
+

Least-squares minimizes the distance between the +data points and the model line parallel to the \(y\)-axis. We write this as \(\chi^2\):

+
+\[\chi^2(\{a_j\}) = \sum_{i=1}^N \left ( \frac{\Delta_i}{\sigma_i} \right )^2\]
+
+
+
from scipy import optimize
+
+
+
+
+
+

general linear least squares#

+

First we’ll make some experimental data (a quadratic with random fashion). We use the standard_normal function to provide Gaussian normalized errors.

+
+
+
def y_experiment2(a1, a2, a3, sigma, x):
+    """ return the experimental data in a quadratic + random fashion,                              
+        with a1, a2, a3 the coefficients of the quadratic and sigma is                             
+        the error.  This will be poorly matched to a linear fit for                                
+        a3 != 0 """
+
+    N = len(x)
+
+    # standard_normal gives samples from the "standard normal" distribution
+    rng = np.random.default_rng()
+    r = rng.standard_normal(N)
+
+    y = a1 + a2*x + a3*x*x + sigma*r
+
+    return y
+
+
+
+
+
+
+
N = 40
+sigma = 5.0*np.ones(N)
+
+x = np.linspace(0, 100.0, N)
+y = y_experiment2(2.0, 1.50, -0.02, sigma, x)
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+ax.scatter(x,y)
+ax.errorbar(x, y, yerr=sigma, fmt="o")
+
+
+
+
+
<ErrorbarContainer object of 3 artists>
+
+
+a plot showing data points with vertical error bars +
+
+
+

Note

+

“linear” in general linear least squares means that the fit parameters enter into the fitting function linearly, +the function itself can be nonlinear in \(x\).

+
+

We’ll use the leastsq() function to minimize the square of the residual.

+

First our function to compute the residual.

+
+
+
def resid(avec, x, y, sigma):
+    """ the residual function -- this is what will be minimized by the
+        scipy.optimize.leastsq() routine.  avec is the parameters we
+        are optimizing -- they are packed in here, so we unpack to
+        begin.  (x, y) are the data points 
+
+        scipy.optimize.leastsq() minimizes:
+
+           x = arg min(sum(func(y)**2,axis=0))
+                    y
+
+        so this should just be the distance from a point to the curve,
+        and it will square it and sum over the points
+        """
+
+    a0, a1, a2 = avec
+    
+    return (y - (a0 + a1*x + a2*x**2))/sigma
+
+
+
+
+
+
+
# initial guesses
+a0, a1, a2 = 1, 1, 1
+
+afit, flag = optimize.leastsq(resid, [a0, a1, a2], args=(x, y, sigma))
+
+print(afit)
+
+
+
+
+
[ 4.41036976  1.41057673 -0.01943501]
+
+
+
+
+
+
+
ax.plot(x, afit[0] + afit[1]*x + afit[2]*x*x )
+fig
+
+
+
+
+a plot showing data points with error bars and a quadratic fit to them +
+
+

We see that we represent the data quite well.

+

We can compute the reduced \(\chi^2\)

+
+
+
chisq = sum(np.power(resid(afit, x, y, sigma),2))
+normalization = len(x)-len(afit)
+print(chisq/normalization)
+
+
+
+
+
1.286126531081104
+
+
+
+
+

In general a (reduced) \(\chi^2 < 1\) indicates a good fit.

+
+
+

a nonlinear example#

+

The same interface works with nonlinear data.

+

We’ll create a new set of experimental data—an exponential

+
+
+
a0 = 2.5
+a1 = 2./3.
+sigma = 5.0
+
+a0_orig, a1_orig = a0, a1
+
+rng = np.random.default_rng()
+
+x = np.linspace(0.0, 4.0, 25)
+y = a0*np.exp(a1*x) + sigma * rng.standard_normal(len(x))
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+ax.scatter(x,y)
+ax.errorbar(x, y, yerr=sigma, fmt="o", label="_nolegend_")
+
+
+
+
+
<ErrorbarContainer object of 3 artists>
+
+
+a plot showing a noisy exponential dataset with error bars +
+
+

our function to minimize

+
+
+
def resid(avec, x, y):
+    """ the residual function -- this is what will be minimized by the                             
+        scipy.optimize.leastsq() routine.  avec is the parameters we                               
+        are optimizing -- they are packed in here, so we unpack to                                 
+        begin.  (x, y) are the data points                                                         
+                                                                                                   
+        scipy.optimize.leastsq() minimizes:                                                        
+                                                                                                   
+           x = arg min(sum(func(y)**2,axis=0))                                                     
+                    y                                                                              
+                                                                                                   
+        so this should just be the distance from a point to the curve,                             
+        and it will square it and sum over the points                                              
+        """
+
+    a0, a1 = avec
+
+    # note: if we wanted to deal with error bars, we would weight each                             
+    # residual accordingly                                                                         
+    return y - a0*np.exp(a1*x)
+
+
+
+
+
+
+
a0, a1 = 1, 1
+afit, flag = optimize.leastsq(resid, [a0, a1], args=(x, y))
+
+print(flag)
+print(afit)
+
+
+
+
+
1
+[2.96629275 0.59509582]
+
+
+
+
+
+
+
ax.plot(x, afit[0]*np.exp(afit[1]*x),
+           label=r"$a_0 = $ %f; $a_1 = $ %f" % (afit[0], afit[1]))
+ax.plot(x, a0_orig*np.exp(a1_orig*x), ":", label="original function")
+ax.legend(numpoints=1, frameon=False)
+fig
+
+
+
+
+a plot showing noisy exponential data points, a fit, and the original function +
+
+
+

Note

+

What about uncertainties in both \(x\) and \(y\)? SciPy has an +orthogonal distance regression implementation based on ODRPACK for this.

+
+
+
+
+

FFTs#

+

Fourier transforms convert a physical-space (or time series) representation of a function into frequency space. This provides an equivalent representation of the data with a new view.

+

The FFT and its inverse in NumPy use:

+
+\[F_k = \sum_{n=0}^{N-1} f_n e^{-2\pi i nk/N}\]
+
+\[f_n = \frac{1}{N} \sum_{k=0}^{N-1} F_k + e^{2\pi i n k/N}\]
+

Both NumPy and SciPy have FFT routines that are similar. However, the NumPy version returns the data in a more convenient form.

+
+

Tip

+

It’s always best to start with something you understand—let’s do a simple sine wave. Since our function is real, we can use the rfft routines in NumPy—the understand that we are working with real data and they don’t return the negative frequency components.

+
+
+

Important

+

FFTs assume that you are periodic. If you include both endpoints of the domain in the points that comprise your sample then you will not match this assumption. Here we use endpoint=False with linspace()

+
+
+

Note

+

For real-valued data, we’ll use np.fft.rfft(), which will return N/2 complex values given N real samples.

+

To get the frequencies, we can use np.fft.rfftfreq(), which will return dimensionless frequencies of the form +\(0, 1/N, 2/N, 3/N, ...\). We know that the shortest lowest frequency corresponds to a single wavelength in the domain, so physically, \(1/N\) corresponds to a frequency \(1/L\), where \(L\) is the domain size. This means that +we can convert the frequencies by dividing by \(\Delta x = L/N\).

+
+

To make our life easier, we’ll define a function that plots all the stages of the FFT process

+
+
+
def plot_FFT(xx, f):
+
+    npts = len(xx)
+    dx = xx[1] - xx[0]
+
+    # Forward transform: f(x) -> F(k)
+    fk = np.fft.rfft(f)
+
+    # Normalization -- the '2' here comes from the fact that we are
+    # neglecting the negative portion of the frequency space, since
+    # the FFT of a real function contains redundant information, so
+    # we are only dealing with 1/2 of the frequency space.
+    #
+    # technically, we should only scale the 0 bin by N, since k=0 is
+    # not duplicated -- we won't worry about that for these plots
+    norm = 2.0 / npts
+
+    fk = fk * norm
+
+    fk_r = fk.real
+    fk_i = fk.imag
+
+    # rfftfreq returns the frequencies as 0, 1/N, 2/N, 3/N, ...
+    k = np.fft.rfftfreq(npts)
+
+    # to make these dimensional, we need to divide by dx.
+    kfreq = k / dx
+
+    # Inverse transform: F(k) -> f(x) -- without the normalization
+    fkinv = np.fft.irfft(fk/norm)
+
+    # plots
+    fig, ax = plt.subplots(nrows=4, ncols=1)
+    
+    ax[0].plot(xx, f)
+    ax[0].set_xlabel("x")
+    ax[0].set_ylabel("f(x)")
+
+    ax[1].plot(kfreq, fk_r, label=r"Re($\mathcal{F}$)")
+    ax[1].plot(kfreq, fk_i, ls=":", label=r"Im($\mathcal{F}$)")
+    ax[1].set_xlabel(r"$\nu_k$")
+    ax[1].set_ylabel("F(k)")
+
+    ax[1].legend(fontsize="small", frameon=False)
+
+    ax[2].plot(kfreq, np.abs(fk))
+    ax[2].set_xlabel(r"$\nu_k$")
+    ax[2].set_ylabel(r"|F(k)|")
+
+    ax[3].plot(xx, fkinv.real)
+    ax[3].set_xlabel(r"$\nu_k$")
+    ax[3].set_ylabel(r"inverse F(k)")
+
+    f = plt.gcf()
+    
+    f.set_size_inches(10,8)
+    plt.tight_layout()
+
+
+
+
+

Now we’ll test it on \(f(x) = \sin(2\pi \nu_0 x)\), where \(\nu_0\) is a frequency we set.

+
+
+
def single_freq_sine(npts):
+
+    # a pure sine with no phase shift will result in pure imaginary
+    # signal
+    f_0 = 0.2
+
+    xmax = 10.0/f_0
+    
+    xx = np.linspace(0.0, xmax, npts, endpoint=False)
+
+    f = np.sin(2.0*np.pi*f_0*xx)
+
+    return xx, f
+
+
+
+
+
+
+
npts = 128
+xx, f = single_freq_sine(npts)
+plot_FFT(xx, f)
+
+
+
+
+a plot with 4 vertical panels showing (1) a sine (2) the Fourier transform of the sine (3) the power in Fourier space (4) the data transformed back to real space +
+
+

Notice that all of the power is at our single frequency, and it is all in the imaginary components, which is expected since \(e^{ix} = \cos(x) + i\sin(x)\)

+

Next, we can try \(f(x) = \cos(2\pi\nu_0 x)\), and we know that a cosine is just a sine shifted in phase by \(\pi/2\).

+
+
+
def single_freq_cosine(npts):
+
+    # a pure cosine with no phase shift will result in pure real
+    # signal
+    f_0 = 0.2
+
+    xmax = 10.0/f_0
+
+    xx = np.linspace(0.0, xmax, npts, endpoint=False)
+
+    f = np.cos(2.0*np.pi*f_0*xx)
+
+    return xx, f
+
+
+
+
+
+
+
xx, f = single_freq_cosine(npts)
+plot_FFT(xx, f)
+
+
+
+
+a plot with 4 vertical panes showing a single-mode cosine transformed to Fourier space and back +
+
+

Now, as expected, all of the power is in the real component.

+

Now let’s look at a sine with a \(\pi/4\) phase shift

+
+
+
def single_freq_sine_plus_shift(npts):
+
+    # a pure sine with no phase shift will result in pure imaginary
+    # signal
+    f_0 = 0.2
+
+    xmax = 10.0/f_0
+
+    xx = np.linspace(0.0, xmax, npts, endpoint=False)
+
+    f = np.sin(2.0*np.pi*f_0*xx + np.pi/4)
+
+    return xx, f
+
+
+
+
+
+
+
xx, f = single_freq_sine_plus_shift(npts)
+plot_FFT(xx, f)
+
+
+
+
+a plot with 4 vertical panes showing a single-mode sine with a phase shift transformed to Fourier space and back +
+
+

No surprise—the power is now equally in the real and imaginary parts.

+
+

A frequency filter#

+

we’ll setup a simple two-frequency sine wave and filter a component

+
+
+
def two_freq_sine(npts):
+
+    # a pure sine with no phase shift will result in pure imaginary             
+    # signal                                                                    
+    f_0 = 0.2
+    f_1 = 0.5
+
+    xmax = 10.0/f_0
+
+    # we call with endpoint=False -- if we include the endpoint, then for       
+    # a periodic function, the first and last point are identical -- this       
+    # shows up as a signal in the FFT.                                          
+    xx = np.linspace(0.0, xmax, npts, endpoint=False)
+
+    f = 0.5*(np.sin(2.0*np.pi*f_0*xx) + np.sin(2.0*np.pi*f_1*xx))
+
+    return xx, f
+
+
+
+
+
+
+
npts = 256
+
+xx, f = two_freq_sine(npts)
+
+fig, ax = plt.subplots()
+ax.plot(xx, f)
+
+
+
+
+
[<matplotlib.lines.Line2D at 0x7f69192ca270>]
+
+
+a plot showing a two-mode sine wave +
+
+

we’ll take the transform: \(f(x) \rightarrow F(k)\)

+
+
+
# normalization factor: the 2 here comes from the fact that we neglect          
+# the negative portion of frequency space because our input function            
+# is real                                                                       
+norm = 2.0/npts
+fk = norm*np.fft.rfft(f)
+
+ofk_r = fk.real.copy()
+ofk_i = fk.imag.copy()
+
+# get the frequencies
+k = np.fft.rfftfreq(len(xx))
+
+# since we don't include the endpoint in xx, to normalize things, we need       
+# max(xx) + dx to get the true length of the domain
+#
+# This makes the frequencies essentially multiples of 1/dx
+kfreq = k*npts/(max(xx) + xx[1])
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+ax.plot(kfreq, fk.real, label="real")
+ax.plot(kfreq, fk.imag, ":", label="imaginary")
+ax.legend(frameon=False)
+
+
+
+
+
<matplotlib.legend.Legend at 0x7f691918b4d0>
+
+
+the FFT of our two-mode sine-wave showing two spikes +
+
+

Now we can filter out the higher frequencies—this is done in Fourier space.

+
+
+
fk[kfreq > 0.4] = 0.0
+
+# element 0 of fk is the DC component                                           
+fk_r = fk.real
+fk_i = fk.imag
+
+
+
+
+

Finally, transform back.

+
+
+
# Inverse transform: F(k) -> f(x)                                               
+fkinv = np.fft.irfft(fk/norm)
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+ax.plot(xx, fkinv.real)
+
+
+
+
+
[<matplotlib.lines.Line2D at 0x7f6918d806e0>]
+
+
+a plot of a single-mode sine wave +
+
+
+
+
+

Linear Algebra#

+
+

general manipulations of matrices#

+

You can use regular NumPy arrays or you can use a special matrix class that offers some short cuts.

+
+

Tip

+

Since the introduction of the matrix multiplication operator, @, using the numpy matrix class is no longer recommended.

+
+

Let’s create a simply 2x2 matrix, \({\bf A}\)

+
+
+
a = np.array([[1.0, 2.0],
+              [3.0, 4.0]])
+
+
+
+
+
+
+
print(a)
+print(a.transpose())
+print(a.T)
+
+
+
+
+
[[1. 2.]
+ [3. 4.]]
+[[1. 3.]
+ [2. 4.]]
+[[1. 3.]
+ [2. 4.]]
+
+
+
+
+

We can solve for the inverse

+
+
+
ainv = np.linalg.inv(a)
+print(ainv)
+
+
+
+
+
[[-2.   1. ]
+ [ 1.5 -0.5]]
+
+
+
+
+

And multiply \({\bf A}\) by its inverse, \({\bf A}^{-1}\)

+
+
+
a @ ainv
+
+
+
+
+
array([[1.0000000e+00, 0.0000000e+00],
+       [8.8817842e-16, 1.0000000e+00]])
+
+
+
+
+

the eye() function will generate an identity matrix (as will the identity())

+
+
+
print(np.eye(2))
+print(np.identity(2))
+
+
+
+
+
[[1. 0.]
+ [0. 1.]]
+[[1. 0.]
+ [0. 1.]]
+
+
+
+
+
+
+

Linear systems#

+

we can solve \({\bf A}{\bf x} = {\bf b}\) easily.

+
+

Note

+

Linear system solvers don’t usually compute the inverse first and then multiply +by it. It is much cheaper to solve the system directly (e.g., via Gaussian elimination) +than to first compute the inverse.

+
+
+
+
b = np.array([5, 7])
+x = np.linalg.solve(a, b)
+print(x)
+
+
+
+
+
[-3.  4.]
+
+
+
+
+
+
+

tridiagonal matrix solve#

+

Here we’ll solve the Poisson problem:

+
+\[f^{\prime\prime} = g(x)\]
+

with \(g(x) = \sin(x)\), and the domain \(x \in [0, 2\pi]\), with boundary conditions \(f(0) = f(2\pi) = 0\).

+

The solution is simply \(f(x) = -\sin(x)\).

+

We’ll use a grid of \(N\) points with \(x_0\) on the left boundary and \(x_{N-1}\) on the right boundary.

+

We difference our equation as:

+
+\[f_{i+1} - 2 f_i + f_{i-1} = \Delta x^2 g_i\]
+

We keep the boundary points fixed, so we only need to solve for the \(N-2\) interior points. Near the boundaries, our difference is:

+
+\[f_2 - 2 f_1 = \Delta x^2 g_1\]
+

and

+
+\[-2f_{N-1} + f_{N-2} = \Delta x^2 g_{N-1}\]
+

We can write the system of equations for solving for the \(N-2\) interior points as:

+
+\[{\bf A} = \left ( +\begin{array}{ccccccc} +-2 & 1 & & & & & \newline +1 & -2 & 1 & & & & \newline + & 1 & -2 & 1 & & & \newline + & & \ddots & \ddots & \ddots & & \newline + & & & \ddots & \ddots & \ddots & \newline + & & & & 1 & -2 & 1 \newline + & & & & & 1 & -2 \newline +\end{array} +\right ) +\]
+
+\[\begin{split} +{\bf x} = \left ( +\begin{array}{c} +f_\mathrm{1} \\\ +f_\mathrm{2} \\\ +f_\mathrm{3} \\\ +\vdots \\\ +\vdots \\\ +f_\mathrm{N-2} \\\ +f_\mathrm{N-1} \\\ +\end{array} +\right ) +\end{split}\]
+
+\[\begin{split} +{\bf b} = \Delta x^2 \left ( +\begin{array}{c} +g_\mathrm{1} \\\ +g_\mathrm{2} \\\ +g_\mathrm{3} \\\ +\vdots \\\ +\vdots \\\ +g_\mathrm{N-2} \\\ +g_\mathrm{N-1}\\\ +\end{array} +\right ) +\end{split}\]
+

Then we just solve \({\bf A}{\bf x} = {\bf b}\)

+
+

Note

+

SciPy has banded solvers that work with banded matrices like we have above. They +will be much more efficient than using a solver based on a dense matrix

+
+
+
+
import scipy.linalg as linalg
+
+# our grid -- including endpoints
+N = 100
+x = np.linspace(0.0, 2.0*np.pi, N, endpoint=True)
+dx = x[1] - x[0]
+
+# our source
+g = np.sin(x)
+
+# our matrix will be tridiagonal, with [1, -2, 1] on the diagonals
+# we only solve for the N-2 interior points
+
+# diagonal
+d = -2 * np.ones(N-2)
+
+# upper -- note that the upper diagonal has 1 less element than the
+# main diagonal.  The SciPy banded solver wants the matrix in the 
+# form:
+#
+# *    a01  a12  a23  a34  a45    <- upper diagonal
+# a00  a11  a22  a33  a44  a55    <- diagonal
+# a10  a21  a32  a43  a54   *     <- lower diagonal
+#
+
+u = np.ones(N-2)
+u[0] = 0.0
+
+# lower
+l = np.ones(N-2)
+l[N-3] = 0.0
+
+# put the upper, diagonal, and lower parts together as a banded matrix
+A = np.matrix([u, d, l])
+
+# solve A sol = dx**2 g for the inner N-2 points
+sol = linalg.solve_banded((1,1), A, dx**2*g[1:N-1])
+
+
+
+
+

Let’s plot the solution

+
+
+
fig, ax = plt.subplots()
+ax.plot(x[1:N-1], sol)
+
+
+
+
+
[<matplotlib.lines.Line2D at 0x7f6918bf4c20>]
+
+
+a plot showing a function that looks approximately like -sin(x) +
+
+

This looks like \(-\sin(x)\), as expected.

+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/05-scipy/scipy-basics-2.html.backup b/05-scipy/scipy-basics-2.html.backup new file mode 100644 index 00000000..9e82bcef --- /dev/null +++ b/05-scipy/scipy-basics-2.html.backup @@ -0,0 +1,1511 @@ + + + + + + + + + + + More SciPy — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

More SciPy#

+
+
+
import numpy as np
+import matplotlib.pyplot as plt
+
+
+
+
+
+

Fitting#

+

Fitting is used to match a model to experimental data. E.g. we have N points of \((x_i, y_i)\) with associated errors, \(\sigma_i\), and we want to find a simply function that best represents the data.

+

Usually this means that we will need to define a metric, often called the residual, for how well our function matches the data, and then minimize this residual. Least-squares fitting is a popular formulation.

+

We want to fit our data to a function \(Y(x, \{a_j\})\), where \(a_j\) are model parameters we can adjust. We want to find the optimal \(a_j\) to minimize the distance of \(Y\) from our data, measured parallel to the \(y\)-axis:

+
+\[\Delta_i = Y(x_i, \{a_j\}) - y_i\]
+

Least-squares minimizes the distance between the +data points and the model line parallel to the \(y\)-axis. We write this as \(\chi^2\):

+
+\[\chi^2(\{a_j\}) = \sum_{i=1}^N \left ( \frac{\Delta_i}{\sigma_i} \right )^2\]
+
+
+
from scipy import optimize
+
+
+
+
+
+

general linear least squares#

+

First we’ll make some experimental data (a quadratic with random fashion). We use the standard_normal function to provide Gaussian normalized errors.

+
+
+
def y_experiment2(a1, a2, a3, sigma, x):
+    """ return the experimental data in a quadratic + random fashion,                              
+        with a1, a2, a3 the coefficients of the quadratic and sigma is                             
+        the error.  This will be poorly matched to a linear fit for                                
+        a3 != 0 """
+
+    N = len(x)
+
+    # standard_normal gives samples from the "standard normal" distribution
+    rng = np.random.default_rng()
+    r = rng.standard_normal(N)
+
+    y = a1 + a2*x + a3*x*x + sigma*r
+
+    return y
+
+
+
+
+
+
+
N = 40
+sigma = 5.0*np.ones(N)
+
+x = np.linspace(0, 100.0, N)
+y = y_experiment2(2.0, 1.50, -0.02, sigma, x)
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+ax.scatter(x,y)
+ax.errorbar(x, y, yerr=sigma, fmt="o")
+# alt-text: a plot showing data points with vertical error bars
+
+
+
+
+
<ErrorbarContainer object of 3 artists>
+
+
+../_images/a93bdf927f3ddd98df4c44c2591fd8d5638149b718d17317ac1fbc18d8bf5ed3.png +
+
+
+

Note

+

“linear” in general linear least squares means that the fit parameters enter into the fitting function linearly, +the function itself can be nonlinear in \(x\).

+
+

We’ll use the leastsq() function to minimize the square of the residual.

+

First our function to compute the residual.

+
+
+
def resid(avec, x, y, sigma):
+    """ the residual function -- this is what will be minimized by the
+        scipy.optimize.leastsq() routine.  avec is the parameters we
+        are optimizing -- they are packed in here, so we unpack to
+        begin.  (x, y) are the data points 
+
+        scipy.optimize.leastsq() minimizes:
+
+           x = arg min(sum(func(y)**2,axis=0))
+                    y
+
+        so this should just be the distance from a point to the curve,
+        and it will square it and sum over the points
+        """
+
+    a0, a1, a2 = avec
+    
+    return (y - (a0 + a1*x + a2*x**2))/sigma
+
+
+
+
+
+
+
# initial guesses
+a0, a1, a2 = 1, 1, 1
+
+afit, flag = optimize.leastsq(resid, [a0, a1, a2], args=(x, y, sigma))
+
+print(afit)
+
+
+
+
+
[ 4.41036976  1.41057673 -0.01943501]
+
+
+
+
+
+
+
ax.plot(x, afit[0] + afit[1]*x + afit[2]*x*x )
+fig
+# alt-text: a plot showing data points with error bars and a quadratic fit to them
+
+
+
+
+../_images/e87ab7494f0da848d087afd17caa635ee9bd52cae1bcf328e7c1319748e4c646.png +
+
+

We see that we represent the data quite well.

+

We can compute the reduced \(\chi^2\)

+
+
+
chisq = sum(np.power(resid(afit, x, y, sigma),2))
+normalization = len(x)-len(afit)
+print(chisq/normalization)
+
+
+
+
+
1.286126531081104
+
+
+
+
+

In general a (reduced) \(\chi^2 < 1\) indicates a good fit.

+
+
+

a nonlinear example#

+

The same interface works with nonlinear data.

+

We’ll create a new set of experimental data—an exponential

+
+
+
a0 = 2.5
+a1 = 2./3.
+sigma = 5.0
+
+a0_orig, a1_orig = a0, a1
+
+rng = np.random.default_rng()
+
+x = np.linspace(0.0, 4.0, 25)
+y = a0*np.exp(a1*x) + sigma * rng.standard_normal(len(x))
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+ax.scatter(x,y)
+ax.errorbar(x, y, yerr=sigma, fmt="o", label="_nolegend_")
+# alt-text: a plot showing a noisy exponential dataset with error bars
+
+
+
+
+
<ErrorbarContainer object of 3 artists>
+
+
+../_images/1c1de585c1b5f18074c2dd3773b47600eeb692383554e04d0549a6c1e360293a.png +
+
+

our function to minimize

+
+
+
def resid(avec, x, y):
+    """ the residual function -- this is what will be minimized by the                             
+        scipy.optimize.leastsq() routine.  avec is the parameters we                               
+        are optimizing -- they are packed in here, so we unpack to                                 
+        begin.  (x, y) are the data points                                                         
+                                                                                                   
+        scipy.optimize.leastsq() minimizes:                                                        
+                                                                                                   
+           x = arg min(sum(func(y)**2,axis=0))                                                     
+                    y                                                                              
+                                                                                                   
+        so this should just be the distance from a point to the curve,                             
+        and it will square it and sum over the points                                              
+        """
+
+    a0, a1 = avec
+
+    # note: if we wanted to deal with error bars, we would weight each                             
+    # residual accordingly                                                                         
+    return y - a0*np.exp(a1*x)
+
+
+
+
+
+
+
a0, a1 = 1, 1
+afit, flag = optimize.leastsq(resid, [a0, a1], args=(x, y))
+
+print(flag)
+print(afit)
+
+
+
+
+
1
+[2.96629275 0.59509582]
+
+
+
+
+
+
+
ax.plot(x, afit[0]*np.exp(afit[1]*x),
+           label=r"$a_0 = $ %f; $a_1 = $ %f" % (afit[0], afit[1]))
+ax.plot(x, a0_orig*np.exp(a1_orig*x), ":", label="original function")
+ax.legend(numpoints=1, frameon=False)
+fig
+# alt-text: a plot showing noisy exponential data points, a fit, and the original function
+
+
+
+
+../_images/76208d9bf1fefa3640dd42e087091d507beaad7c146c99e2fc8c3481e7ed1185.png +
+
+
+

Note

+

What about uncertainties in both \(x\) and \(y\)? SciPy has an +orthogonal distance regression implementation based on ODRPACK for this.

+
+
+
+
+

FFTs#

+

Fourier transforms convert a physical-space (or time series) representation of a function into frequency space. This provides an equivalent representation of the data with a new view.

+

The FFT and its inverse in NumPy use:

+
+\[F_k = \sum_{n=0}^{N-1} f_n e^{-2\pi i nk/N}\]
+
+\[f_n = \frac{1}{N} \sum_{k=0}^{N-1} F_k + e^{2\pi i n k/N}\]
+

Both NumPy and SciPy have FFT routines that are similar. However, the NumPy version returns the data in a more convenient form.

+
+

Tip

+

It’s always best to start with something you understand—let’s do a simple sine wave. Since our function is real, we can use the rfft routines in NumPy—the understand that we are working with real data and they don’t return the negative frequency components.

+
+
+

Important

+

FFTs assume that you are periodic. If you include both endpoints of the domain in the points that comprise your sample then you will not match this assumption. Here we use endpoint=False with linspace()

+
+
+

Note

+

For real-valued data, we’ll use np.fft.rfft(), which will return N/2 complex values given N real samples.

+

To get the frequencies, we can use np.fft.rfftfreq(), which will return dimensionless frequencies of the form +\(0, 1/N, 2/N, 3/N, ...\). We know that the shortest lowest frequency corresponds to a single wavelength in the domain, so physically, \(1/N\) corresponds to a frequency \(1/L\), where \(L\) is the domain size. This means that +we can convert the frequencies by dividing by \(\Delta x = L/N\).

+
+

To make our life easier, we’ll define a function that plots all the stages of the FFT process

+
+
+
def plot_FFT(xx, f):
+
+    npts = len(xx)
+    dx = xx[1] - xx[0]
+
+    # Forward transform: f(x) -> F(k)
+    fk = np.fft.rfft(f)
+
+    # Normalization -- the '2' here comes from the fact that we are
+    # neglecting the negative portion of the frequency space, since
+    # the FFT of a real function contains redundant information, so
+    # we are only dealing with 1/2 of the frequency space.
+    #
+    # technically, we should only scale the 0 bin by N, since k=0 is
+    # not duplicated -- we won't worry about that for these plots
+    norm = 2.0 / npts
+
+    fk = fk * norm
+
+    fk_r = fk.real
+    fk_i = fk.imag
+
+    # rfftfreq returns the frequencies as 0, 1/N, 2/N, 3/N, ...
+    k = np.fft.rfftfreq(npts)
+
+    # to make these dimensional, we need to divide by dx.
+    kfreq = k / dx
+
+    # Inverse transform: F(k) -> f(x) -- without the normalization
+    fkinv = np.fft.irfft(fk/norm)
+
+    # plots
+    fig, ax = plt.subplots(nrows=4, ncols=1)
+    
+    ax[0].plot(xx, f)
+    ax[0].set_xlabel("x")
+    ax[0].set_ylabel("f(x)")
+
+    ax[1].plot(kfreq, fk_r, label=r"Re($\mathcal{F}$)")
+    ax[1].plot(kfreq, fk_i, ls=":", label=r"Im($\mathcal{F}$)")
+    ax[1].set_xlabel(r"$\nu_k$")
+    ax[1].set_ylabel("F(k)")
+
+    ax[1].legend(fontsize="small", frameon=False)
+
+    ax[2].plot(kfreq, np.abs(fk))
+    ax[2].set_xlabel(r"$\nu_k$")
+    ax[2].set_ylabel(r"|F(k)|")
+
+    ax[3].plot(xx, fkinv.real)
+    ax[3].set_xlabel(r"$\nu_k$")
+    ax[3].set_ylabel(r"inverse F(k)")
+
+    f = plt.gcf()
+    
+    f.set_size_inches(10,8)
+    plt.tight_layout()
+
+
+
+
+

Now we’ll test it on \(f(x) = \sin(2\pi \nu_0 x)\), where \(\nu_0\) is a frequency we set.

+
+
+
def single_freq_sine(npts):
+
+    # a pure sine with no phase shift will result in pure imaginary
+    # signal
+    f_0 = 0.2
+
+    xmax = 10.0/f_0
+    
+    xx = np.linspace(0.0, xmax, npts, endpoint=False)
+
+    f = np.sin(2.0*np.pi*f_0*xx)
+
+    return xx, f
+
+
+
+
+
+
+
npts = 128
+xx, f = single_freq_sine(npts)
+plot_FFT(xx, f)
+# alt-text: a plot with 4 vertical panels showing (1) a sine (2) the Fourier transform of the sine (3) the power in Fourier space (4) the data transformed back to real space
+
+
+
+
+../_images/8f22444e032fd5b8480eb4e0752cbffe2ed935ab38ea9d30bcfe690d80c847f3.png +
+
+

Notice that all of the power is at our single frequency, and it is all in the imaginary components, which is expected since \(e^{ix} = \cos(x) + i\sin(x)\)

+

Next, we can try \(f(x) = \cos(2\pi\nu_0 x)\), and we know that a cosine is just a sine shifted in phase by \(\pi/2\).

+
+
+
def single_freq_cosine(npts):
+
+    # a pure cosine with no phase shift will result in pure real
+    # signal
+    f_0 = 0.2
+
+    xmax = 10.0/f_0
+
+    xx = np.linspace(0.0, xmax, npts, endpoint=False)
+
+    f = np.cos(2.0*np.pi*f_0*xx)
+
+    return xx, f
+
+
+
+
+
+
+
xx, f = single_freq_cosine(npts)
+plot_FFT(xx, f)
+# alt-text: a plot with 4 vertical panes showing a single-mode cosine transformed to Fourier space and back
+
+
+
+
+../_images/5707a02e8cca32ab41ddc3e507473f33e404efe75b61045d1b311ea520450202.png +
+
+

Now, as expected, all of the power is in the real component.

+

Now let’s look at a sine with a \(\pi/4\) phase shift

+
+
+
def single_freq_sine_plus_shift(npts):
+
+    # a pure sine with no phase shift will result in pure imaginary
+    # signal
+    f_0 = 0.2
+
+    xmax = 10.0/f_0
+
+    xx = np.linspace(0.0, xmax, npts, endpoint=False)
+
+    f = np.sin(2.0*np.pi*f_0*xx + np.pi/4)
+
+    return xx, f
+
+
+
+
+
+
+
xx, f = single_freq_sine_plus_shift(npts)
+plot_FFT(xx, f)
+# alt-text: a plot with 4 vertical panes showing a single-mode sine with a phase shift transformed to Fourier space and back
+
+
+
+
+../_images/c62cb3987d333d4e135d6e54b6380771dd67af5d0377162c755c8a723636c989.png +
+
+

No surprise—the power is now equally in the real and imaginary parts.

+
+

A frequency filter#

+

we’ll setup a simple two-frequency sine wave and filter a component

+
+
+
def two_freq_sine(npts):
+
+    # a pure sine with no phase shift will result in pure imaginary             
+    # signal                                                                    
+    f_0 = 0.2
+    f_1 = 0.5
+
+    xmax = 10.0/f_0
+
+    # we call with endpoint=False -- if we include the endpoint, then for       
+    # a periodic function, the first and last point are identical -- this       
+    # shows up as a signal in the FFT.                                          
+    xx = np.linspace(0.0, xmax, npts, endpoint=False)
+
+    f = 0.5*(np.sin(2.0*np.pi*f_0*xx) + np.sin(2.0*np.pi*f_1*xx))
+
+    return xx, f
+
+
+
+
+
+
+
npts = 256
+
+xx, f = two_freq_sine(npts)
+
+fig, ax = plt.subplots()
+ax.plot(xx, f)
+# alt-text: a plot showing a two-mode sine wave
+
+
+
+
+
[<matplotlib.lines.Line2D at 0x7f69192ca270>]
+
+
+../_images/29b1ce756a8461259f118d9cc3963ee84959a0af7c2bf3aed56d12b9d0dc4fa4.png +
+
+

we’ll take the transform: \(f(x) \rightarrow F(k)\)

+
+
+
# normalization factor: the 2 here comes from the fact that we neglect          
+# the negative portion of frequency space because our input function            
+# is real                                                                       
+norm = 2.0/npts
+fk = norm*np.fft.rfft(f)
+
+ofk_r = fk.real.copy()
+ofk_i = fk.imag.copy()
+
+# get the frequencies
+k = np.fft.rfftfreq(len(xx))
+
+# since we don't include the endpoint in xx, to normalize things, we need       
+# max(xx) + dx to get the true length of the domain
+#
+# This makes the frequencies essentially multiples of 1/dx
+kfreq = k*npts/(max(xx) + xx[1])
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+ax.plot(kfreq, fk.real, label="real")
+ax.plot(kfreq, fk.imag, ":", label="imaginary")
+ax.legend(frameon=False)
+# alt-text: the FFT of our two-mode sine-wave showing two spikes
+
+
+
+
+
<matplotlib.legend.Legend at 0x7f691918b4d0>
+
+
+../_images/ac8d3ce8eecdd5cb24b438db7ea2a2d517d57d84887392d87efab1d8f9d5fd94.png +
+
+

Now we can filter out the higher frequencies—this is done in Fourier space.

+
+
+
fk[kfreq > 0.4] = 0.0
+
+# element 0 of fk is the DC component                                           
+fk_r = fk.real
+fk_i = fk.imag
+
+
+
+
+

Finally, transform back.

+
+
+
# Inverse transform: F(k) -> f(x)                                               
+fkinv = np.fft.irfft(fk/norm)
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+ax.plot(xx, fkinv.real)
+# alt-text: a plot of a single-mode sine wave
+
+
+
+
+
[<matplotlib.lines.Line2D at 0x7f6918d806e0>]
+
+
+../_images/12158ea15c928d43a30e2afd5fbc1c6444e47d37058e3182be60f71a63af8fa5.png +
+
+
+
+
+

Linear Algebra#

+
+

general manipulations of matrices#

+

You can use regular NumPy arrays or you can use a special matrix class that offers some short cuts.

+
+

Tip

+

Since the introduction of the matrix multiplication operator, @, using the numpy matrix class is no longer recommended.

+
+

Let’s create a simply 2x2 matrix, \({\bf A}\)

+
+
+
a = np.array([[1.0, 2.0],
+              [3.0, 4.0]])
+
+
+
+
+
+
+
print(a)
+print(a.transpose())
+print(a.T)
+
+
+
+
+
[[1. 2.]
+ [3. 4.]]
+[[1. 3.]
+ [2. 4.]]
+[[1. 3.]
+ [2. 4.]]
+
+
+
+
+

We can solve for the inverse

+
+
+
ainv = np.linalg.inv(a)
+print(ainv)
+
+
+
+
+
[[-2.   1. ]
+ [ 1.5 -0.5]]
+
+
+
+
+

And multiply \({\bf A}\) by its inverse, \({\bf A}^{-1}\)

+
+
+
a @ ainv
+
+
+
+
+
array([[1.0000000e+00, 0.0000000e+00],
+       [8.8817842e-16, 1.0000000e+00]])
+
+
+
+
+

the eye() function will generate an identity matrix (as will the identity())

+
+
+
print(np.eye(2))
+print(np.identity(2))
+
+
+
+
+
[[1. 0.]
+ [0. 1.]]
+[[1. 0.]
+ [0. 1.]]
+
+
+
+
+
+
+

Linear systems#

+

we can solve \({\bf A}{\bf x} = {\bf b}\) easily.

+
+

Note

+

Linear system solvers don’t usually compute the inverse first and then multiply +by it. It is much cheaper to solve the system directly (e.g., via Gaussian elimination) +than to first compute the inverse.

+
+
+
+
b = np.array([5, 7])
+x = np.linalg.solve(a, b)
+print(x)
+
+
+
+
+
[-3.  4.]
+
+
+
+
+
+
+

tridiagonal matrix solve#

+

Here we’ll solve the Poisson problem:

+
+\[f^{\prime\prime} = g(x)\]
+

with \(g(x) = \sin(x)\), and the domain \(x \in [0, 2\pi]\), with boundary conditions \(f(0) = f(2\pi) = 0\).

+

The solution is simply \(f(x) = -\sin(x)\).

+

We’ll use a grid of \(N\) points with \(x_0\) on the left boundary and \(x_{N-1}\) on the right boundary.

+

We difference our equation as:

+
+\[f_{i+1} - 2 f_i + f_{i-1} = \Delta x^2 g_i\]
+

We keep the boundary points fixed, so we only need to solve for the \(N-2\) interior points. Near the boundaries, our difference is:

+
+\[f_2 - 2 f_1 = \Delta x^2 g_1\]
+

and

+
+\[-2f_{N-1} + f_{N-2} = \Delta x^2 g_{N-1}\]
+

We can write the system of equations for solving for the \(N-2\) interior points as:

+
+\[{\bf A} = \left ( +\begin{array}{ccccccc} +-2 & 1 & & & & & \newline +1 & -2 & 1 & & & & \newline + & 1 & -2 & 1 & & & \newline + & & \ddots & \ddots & \ddots & & \newline + & & & \ddots & \ddots & \ddots & \newline + & & & & 1 & -2 & 1 \newline + & & & & & 1 & -2 \newline +\end{array} +\right ) +\]
+
+\[\begin{split} +{\bf x} = \left ( +\begin{array}{c} +f_\mathrm{1} \\\ +f_\mathrm{2} \\\ +f_\mathrm{3} \\\ +\vdots \\\ +\vdots \\\ +f_\mathrm{N-2} \\\ +f_\mathrm{N-1} \\\ +\end{array} +\right ) +\end{split}\]
+
+\[\begin{split} +{\bf b} = \Delta x^2 \left ( +\begin{array}{c} +g_\mathrm{1} \\\ +g_\mathrm{2} \\\ +g_\mathrm{3} \\\ +\vdots \\\ +\vdots \\\ +g_\mathrm{N-2} \\\ +g_\mathrm{N-1}\\\ +\end{array} +\right ) +\end{split}\]
+

Then we just solve \({\bf A}{\bf x} = {\bf b}\)

+
+

Note

+

SciPy has banded solvers that work with banded matrices like we have above. They +will be much more efficient than using a solver based on a dense matrix

+
+
+
+
import scipy.linalg as linalg
+
+# our grid -- including endpoints
+N = 100
+x = np.linspace(0.0, 2.0*np.pi, N, endpoint=True)
+dx = x[1] - x[0]
+
+# our source
+g = np.sin(x)
+
+# our matrix will be tridiagonal, with [1, -2, 1] on the diagonals
+# we only solve for the N-2 interior points
+
+# diagonal
+d = -2 * np.ones(N-2)
+
+# upper -- note that the upper diagonal has 1 less element than the
+# main diagonal.  The SciPy banded solver wants the matrix in the 
+# form:
+#
+# *    a01  a12  a23  a34  a45    <- upper diagonal
+# a00  a11  a22  a33  a44  a55    <- diagonal
+# a10  a21  a32  a43  a54   *     <- lower diagonal
+#
+
+u = np.ones(N-2)
+u[0] = 0.0
+
+# lower
+l = np.ones(N-2)
+l[N-3] = 0.0
+
+# put the upper, diagonal, and lower parts together as a banded matrix
+A = np.matrix([u, d, l])
+
+# solve A sol = dx**2 g for the inner N-2 points
+sol = linalg.solve_banded((1,1), A, dx**2*g[1:N-1])
+
+
+
+
+

Let’s plot the solution

+
+
+
fig, ax = plt.subplots()
+ax.plot(x[1:N-1], sol)
+# alt-text: a plot showing a function that looks approximately like -sin(x)
+
+
+
+
+
[<matplotlib.lines.Line2D at 0x7f6918bf4c20>]
+
+
+../_images/9464b0c0bcee714b070f959c01ff09c68008dbf76d6bcb101f395150b9d6b153.png +
+
+

This looks like \(-\sin(x)\), as expected.

+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/05-scipy/scipy-basics.html b/05-scipy/scipy-basics.html new file mode 100644 index 00000000..33b63202 --- /dev/null +++ b/05-scipy/scipy-basics.html @@ -0,0 +1,1510 @@ + + + + + + + + + + + SciPy — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

SciPy#

+

SciPy is a collection of numerical algorithms with python interfaces. In many cases, these interfaces are wrappers around standard numerical libraries that have been developed in the community and are used with other languages. Usually detailed references are available to explain the implementation.

+
+

Note

+

There are many subpackages generally, you load the subpackages separately, e.g.

+
from scipy import linalg, optimize
+
+
+

then you have access to the methods in those namespaces

+
+
+

Important

+

One thing to keep in mind—all numerical methods have strengths and weaknesses, and make assumptions. You should always do some research into the method to understand what it is doing.

+
+
+

Tip

+

It is also always a good idea to run a new method on some test where you know the answer, to make sure it is behaving as expected.

+
+
+
+
import numpy as np
+import matplotlib.pyplot as plt
+
+
+
+
+
+

Integration#

+

we’ll do some integrals of the form

+
+\[I = \int_a^b f(x) dx\]
+

We can imagine two situations:

+
    +
  • our function \(f(x)\) is given by an analytic expression. This gives us the freedom to pick our integration points, and in general can allow us to optimize our result and get high accuracy

  • +
  • our function \(f(x)\) is defined on at a set of (possibly regular spaced) points.

  • +
+
+

Note

+

In numerical analysis, the term quadrature is used to describe any integration method that represents the integral as the weighted sum of a discrete number of points.

+
+
+
+
from scipy import integrate
+#help(integrate)
+
+
+
+
+

Let’s consider integrating

+
+\[I = \int_0^{2\pi} \sin^2(x) dx\]
+

quad() is the basic integrator for a general (not sampled) function. It uses a general-interface from the Fortran package QUADPACK (QAGS or QAGI). It will return the integral in an interval and an estimate of the error in the approximation

+
+
+
def f(x):
+    return np.sin(x)**2
+
+
+
+
+
+
+
#help(integrate.quad)
+
+
+
+
+

quad will return the integral and an estimate of the error. We can seek more accuracy by setting epsabs and epsrel, +but remember that we can’t do better than roundoff error.

+
+
+
I, err = integrate.quad(f, 0.0, 2.0*np.pi, epsabs=1.e-14, epsrel=1.e-14)
+print(I)
+print(err)
+
+
+
+
+
3.141592653589793
+3.4878684980086318e-15
+
+
+
+
+
+
+
#help(integrate.quad)
+
+
+
+
+
+

Additional arguments#

+

Sometimes our integrand function takes optional arguments. Let’s consider integrating

+
+\[g(x) = A e^{-(x/\sigma)^2}\]
+

now we want to be able to define the amplitude, \(A\), and width, \(\sigma\) as part of the function.

+
+
+
def g(x, A, sigma):
+    return A*np.exp(-x**2/sigma**2)
+
+
+
+
+
+
+
I, err = integrate.quad(g, -1.0, 1.0, args=(1.0, 2.0))
+print(I, err)
+
+
+
+
+
1.8451240256511698 2.0484991765669867e-14
+
+
+
+
+
+
+

Integrating to infinity#

+

numpy defines the inf quantity which can be used in the integration limits. We can integrate a Gaussian over \([-\infty, \infty]\) (we know the answer +is \(\sqrt{\pi}\)).

+
+

Note

+

Behind the scenes, what the integration function does is do a variable transform like: \(t = x/(c +x)\). This works when one limit is \(\infty\), giving, e.g.,

+
+\[\int_a^\infty f(x) dx = c \int_{a/(c + a)}^1 f\left (c\frac{t}{1-t}\right) (1 - t)^{-2} dt\]
+
+
+
+
I, err = integrate.quad(g, -np.inf, np.inf, args=(1.0, 1.0))
+print(I, err)
+
+
+
+
+
1.7724538509055159 1.420263678094492e-08
+
+
+
+
+
+
+

Multidimensional integrals#

+

Multidimensional integration can be done with successive calls to quad(), but there are wrappers that help

+

Let’s compute

+
+\[I = \int_{y=0}^{1/2} \int_{x=0}^{1-2y} xy dxdy = \frac{1}{96}\]
+

(this example comes from the SciPy tutorial)

+

Notice that the limits of integration in \(x\) depend on \(y\). This means that we need to do the \(x\) +integration first, which gives:

+
+\[I = \int_{y=0}^{1/2} \int_{x=0}^{1-2y} xy \,dxdy = \frac{1}{2} \int_{y=0}^{1/2} y \left [ x^2 \right |_0^{1-2y} dy = \frac{1}{2} \int_0^{1/2} (1-2y)^2 y \, dy = \frac{1}{96}\]
+

Note the form of the function:

+
dblquad(f, a, b, xlo, xhi)
+
+
+

where f = f(y, x) – the y argument is first to indicate that the \(y\) integration is done first and +then the \(x\) and \([a, b]\) are the limits of the \(x\) integration. We want the opposite in this example, +so we’ll switch the meaning of \(x\) and \(y\) in our example below.

+

The integral will be from: \(y = [0, 1/2]\), and \(x\) = xlo(y), \(x\) = xhi(y)

+
+
+
def integrand(x, y):
+    return x*y
+
+def x_lower_lim(y):
+    return 0
+    
+def x_upper_lim(y):
+    return 1-2*y
+
+# we change the definitions of x and y in this call
+I, err = integrate.dblquad(integrand, 0.0, 0.5, x_lower_lim, x_upper_lim)
+print(I, 1.0/I)
+
+
+
+
+
0.010416666666666668 95.99999999999999
+
+
+
+
+

If you remember the python lambda functions (one expression functions), you can do this more compactly:

+
+
+
I, err = integrate.dblquad(lambda x, y: x*y, 0.0, 0.5, lambda y: 0, lambda y: 1-2*y)
+print(I)
+
+
+
+
+
0.010416666666666668
+
+
+
+
+
+
+

Integration of a sampled function#

+

Here we integrate a function that is defined only at a sequence of points. A popular method +is Simpson’s rule which fits a parabola to 3 consecutive points +and integrates under the parabola.

+

Let’s compute

+
+\[I = \int_0^{2\pi} f(x_i) dx\]
+

with \(x_i = 0, \ldots, 2\pi\) defined at \(N\) points

+
+
+
N = 17
+x = np.linspace(0.0, 2.0*np.pi, N, endpoint=True)
+y = np.sin(x)**2
+
+I = integrate.simpson(y, x=x)
+print(I)
+
+
+
+
+
3.141592653589793
+
+
+
+
+

Romberg integration is specific to equally-spaced samples, where \(N = 2^k + 1\) and can be more converge faster (it uses extrapolation of coarser integration results to achieve higher accuracy)

+
+
+
N = 17
+x = np.linspace(0.0, 2.0*np.pi, N, endpoint=True)
+y = np.sin(x)**2
+
+I = integrate.romb(y, dx=x[1]-x[0])
+print(I)
+
+
+
+
+
3.1430658353300385
+
+
+
+
+
+
+
+

Interpolation#

+

Interpolation fills in the gaps between a discrete number of points by making an assumption about the behavior of the functional form of the data.

+

Many different types of interpolation exist

+
    +
  • some ensure no new extrema are introduced

  • +
  • some conserve the quantity being interpolated

  • +
  • some match derivative at end points

  • +
+
+

Caution

+

Pathologies exist—it is not always best to use a high-order polynomial to pass through all of the points in your dataset.

+
+

The interp1d() function allows for a variety of 1-d interpolation methods. It returns an object that acts as a function, which can be evaluated at any point.

+
+
+
import scipy.interpolate as interpolate
+
+
+
+
+
+
+
#help(interpolate.interp1d)
+
+
+
+
+

Let’s sample

+
+\[f(x) = x \sin(x)\]
+

and try to interpolate it.

+
+
+
def f_exact(x):
+    return np.sin(x)*x
+
+
+
+
+
+
+
N = 10
+x = np.linspace(0, 20, N)
+
+y = f_exact(x)
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+
+x_fine = np.linspace(0, 20, 10*N)
+
+ax.scatter(x, y)
+ax.plot(x_fine, f_exact(x_fine), ls=":", label="original function")
+
+
+
+
+
[<matplotlib.lines.Line2D at 0x7f75f70c4440>]
+
+
+a figure showing data points and an interpolant passing through them +
+
+

When we create an interpolant via interp1d, it creates a function object

+
+
+
f_interp = interpolate.interp1d(x, y, kind="cubic")
+
+
+
+
+
+
+
ax.plot(x_fine, f_interp(x_fine), label="interpolant")
+
+ax.legend(frameon=False, loc="best")
+fig
+
+
+
+
+a figure showing data points, an interpolant to them, and the original function we sampled +
+
+
+

Multi-d interpolation#

+

Here’s an example of mult-d interpolation from the official tutorial.

+

First we define the “answer”—this is the true function that we will sample at a number of points and then try to use interpolation to recover

+
+
+
def func(x, y):
+    return x*(1-x)*np.cos(4*np.pi*x) * np.sin(4*np.pi*y**2)**2
+
+
+
+
+

We’ll use np.meshgrid() to create the two-dimensional rectangular grid of points were we define our data.

+
+
+
nx = 100
+ny = 200
+
+x = np.linspace(0, 1, nx)
+y = np.linspace(0, 1, ny)
+
+x, y = np.meshgrid(x, y, indexing="ij")
+
+
+
+
+

here’s what the exact function looks like—note that our function is defined in x,y, but imshow is meant for plotting an array, so the first index is the row. We take the transpose when plotting

+
+
+
fig, ax = plt.subplots()
+data = func(x, y)
+im = ax.imshow(data.T, extent=(0, 1, 0, 1), origin="lower")
+fig.colorbar(im, ax=ax)
+
+
+
+
+
<matplotlib.colorbar.Colorbar at 0x7f75f6dbcc20>
+
+
+a heat-map figure showing a function with small-amplitude ripples +
+
+

Now we’ll coarsen it by taking only every 4th point

+
+
+
coarse = data[::4, ::4]
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+im = ax.imshow(coarse.T, extent=(0, 1, 0, 1), origin="lower")
+fig.colorbar(im, ax=ax)
+
+
+
+
+
<matplotlib.colorbar.Colorbar at 0x7f75f2bdc590>
+
+
+a heat-map showing coarsened representation of our function +
+
+

Let’s now use interpolation to try to recover the look of the original data.

+
+

Note

+

This is considered structured grid interpolation, and SciPy has the RegularGridInterpolator() class for this type of data.

+

If the data were unstructured, then you should explore griddata() instead.

+
+
+
+
from scipy import interpolate
+
+
+
+
+
+
+
x_coarse = np.linspace(0, 1, nx//4)
+y_coarse = np.linspace(0, 1, ny//4)
+
+interp = interpolate.RegularGridInterpolator((x_coarse, y_coarse), coarse, method="cubic")
+
+
+
+
+

Now interp() is a function that we can use to sample the coarsened data.

+

Now interpolate it onto the original grid

+
+
+
new_data = interp((x, y))
+new_data.shape
+
+
+
+
+
(100, 200)
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+im = ax.imshow(new_data.T, extent=(0, 1, 0, 1), origin="lower")
+fig.colorbar(im, ax=ax)
+
+
+
+
+
<matplotlib.colorbar.Colorbar at 0x7f75f0a474d0>
+
+
+a heat-map showing the reconstructed function via interpolation +
+
+

and let’s plot the difference

+
+
+
diff = new_data - data
+fig, ax = plt.subplots()
+im = ax.imshow(diff.T, origin="lower", extent=(0, 1, 0, 1))
+fig.colorbar(im, ax=ax)
+
+
+
+
+
<matplotlib.colorbar.Colorbar at 0x7f75f093a270>
+
+
+a heat-map showing the error in our interpolation.  It is better than 10% +
+
+
+
+
+

Root Finding#

+

Often we need to find a value of a variable that zeros a function – this is root finding. Sometimes, this is a multidimensional problem.

+

The brentq() method offers a very robust method for find roots from a scalar function. You do need to provide an interval that bounds the root.

+
+

Tip

+

It’s a good idea to plot the function, if you can, so you can learn how the function behaves +in the vicinity of a root (and how many roots there might be)

+
+

Let’s consider:

+

\(f(x) = \frac{x e^x}{e^x - 1} - 5\)

+
+
+
import scipy.optimize as optimize
+
+
+
+
+
+
+
def f(x):
+    return (x*np.exp(x)/(np.exp(x) - 1.0) - 5.0)
+
+
+
+
+
+
+
root, r = optimize.brentq(f, 0.1, 10.0, full_output=True)
+
+print(root)
+print(r.converged)
+
+
+
+
+
4.965114231744287
+True
+
+
+
+
+
+
+
x = np.linspace(0.1, 10.0, 1000)
+fig, ax = plt.subplots()
+ax.plot(x, f(x))
+ax.scatter(np.array([root]), np.array([f(root)]))
+ax.grid()
+
+
+
+
+a plot of our function with the root represented as a point +
+
+
+
+

ODEs#

+

Many methods exist for integrating ordinary differential equations. Most will want you to write your ODEs as a system of first order equations.

+

The Lorenz system is a very simple +model of convection in our atmosphere, but demonstrates the idea of chaos well.

+

This system of ODEs for the Lorenz system is:

+
+\[\frac{dx}{dt} = \sigma (y - x)\]
+
+\[\frac{dy}{dt} = rx - y - xz\]
+
+\[\frac{dz}{dt} = xy - bz\]
+

the steady states of this system correspond to:

+
+\[\begin{split}{\bf f}({\bf x}) = +\left ( +\begin{array}{c} +\sigma (y -x) \\ +rx - y -xz \\ +xy - bz +\end{array} +\right ) += 0\end{split}\]
+
+
+
# system parameters
+sigma = 10.0
+b = 8./3.
+r = 28.0
+
+def rhs(t, x):
+    xdot = sigma*(x[1] - x[0])
+    ydot = r*x[0] - x[1] - x[0]*x[2]
+    zdot = x[0]*x[1] - b*x[2]
+
+    return np.array([xdot, ydot, zdot])
+
+def jac(t, x):
+
+    return np.array(
+        [ [-sigma, sigma, 0.0], 
+          [r - x[2], -1.0, -x[0]],
+          [x[1], x[0], -b] ])
+
+def f(x):
+    return rhs(0.,x), jac(0.,x)
+
+
+
+
+

SciPy has a uniform interface to the different ODE solvers, solve_ivp()—we use that here.

+

These integrators will do error estimation along the way and adapt the stepsize to ensure that the accuracy +you request is met.

+
+
+
def ode_integrate(X0, dt, tmax):
+    """ integrate using the VODE method, storing the solution each dt """
+
+    r = integrate.solve_ivp(rhs, (0.0, tmax), X0,
+                            method="RK45", dense_output=True)
+
+    # get the solution at intermediate times
+    ts = np.arange(0.0, tmax, dt)
+    
+    Xs = r.sol(ts)
+    return ts, Xs
+
+
+
+
+
+

Tip

+

Execute

+
%matplotlib widget
+
+
+

in a cell before making this 3D plot and you will be able to interactively +rotate it in the notebook.

+

You may need to install the ipympl package first.

+
+
+
+
t, X = ode_integrate([1.0, 1.0, 20.0], 0.02, 30)
+from mpl_toolkits.mplot3d import Axes3D
+
+fig = plt.figure()
+ax = plt.axes(projection='3d')
+ax.plot(X[0,:], X[1,:], X[2,:])
+fig.set_size_inches(8.0,6.0)
+
+
+
+
+a 3D line plot of the solution -- it is dominated by two lobe-like structures +
+
+
+

try it

+

Rerun the integration, but change the initial conditions by 1 part in \(10^6\) for one of the components. +The make a plot of \(x\) vs. \(t\) comparing the solutions. You’ll see that the 2 solutions track well +for some time but then greatly diverged. This is the sensitivity to initial conditions that is the +hallmark of chaos.

+
+
+

Multi-variate root find#

+

We can find the steady points in this system by doing a multi-variate root find on the RHS vector

+
+
+
sol1 = optimize.root(f, [1., 1., 1.], jac=True)
+print(sol1.x)
+
+sol2 = optimize.root(f, [10., 10., 10.], jac=True)
+print(sol2.x)
+
+sol3 = optimize.root(f, [-10., -10., -10.], jac=True)
+print(sol3.x)
+
+
+
+
+
[0. 0. 0.]
+[ 8.48528137  8.48528137 27.        ]
+[-8.48528137 -8.48528137 27.        ]
+
+
+
+
+
+
+
fig = plt.figure()
+fig.set_size_inches(8, 8)
+ax = plt.axes(projection='3d')
+
+ax.plot(X[0,:], X[1,:], X[2,:])
+
+ax.scatter(sol1.x[0], sol1.x[1], sol1.x[2], marker="x", color="C1")
+ax.scatter(sol2.x[0], sol2.x[1], sol2.x[2], marker="x", color="C1")
+ax.scatter(sol3.x[0], sol3.x[1], sol3.x[2], marker="x", color="C1")
+
+ax.set_xlabel("x")
+ax.set_ylabel("y")
+ax.set_zlabel("z")
+
+
+
+
+
Text(0.5, 0, 'z')
+
+
+the 3D solution again represented as a line / trajectory, now with the stable-points marked +
+
+
+
+

Stiff system of ODEs#

+

A stiff system of ODEs is one where there are multiple disparate timescales for change and we need to respect all of them to get an accurate solution

+

Here is an example from Chemical Kinetics (see, ex. Byrne & Hindmarsh 1986, or the VODE source code)

+
+\[ +\frac{d}{dt} \left ( + \begin{array}{c} y_1 \newline y_2 \newline y_3 \end{array} + \right ) = +% +\left ( + \begin{array}{rrr} + -0.04 y_1 & + 10^4 y_2 y_3 & \newline + 0.04 y_1 & - 10^4 y_2 y_3 & -3\times 10^7 y_2^2 \newline + & & 3\times 10^7 y_2^2 +\end{array} +\right ) +\]
+
+\[ +{\bf J} = \left ( +\begin{array}{ccc} + -0.04 & 10^4 y_3 & 10^4 y_2 \newline + 0.04 & -10^4 y_3 - 6\times 10^7 y_2 & -10^4 y_2 \newline + 0 & 6\times 10^7 y_2 & 0 +\end{array} +\right ) +\]
+

start with \(y_1(0) = 1, y_2(0) = y_3(0) = 0\). Long term behavior is \(y_1, y_2 \rightarrow 0; y_3 \rightarrow 1\)

+
+
+
def rhs(t, Y):
+    """ RHS of the system -- using 0-based indexing """
+    y1 = Y[0]
+    y2 = Y[1]
+    y3 = Y[2]
+
+    dy1dt = -0.04*y1 + 1.e4*y2*y3
+    dy2dt =  0.04*y1 - 1.e4*y2*y3 - 3.e7*y2**2
+    dy3dt =                         3.e7*y2**2
+
+    return np.array([dy1dt, dy2dt, dy3dt])
+
+def jac(t, Y):
+    """ J_{i,j} = df_i/dy_j """
+
+    y1 = Y[0]
+    y2 = Y[1]
+    y3 = Y[2]
+
+    df1dy1 = -0.04
+    df1dy2 = 1.e4*y3
+    df1dy3 = 1.e4*y2
+
+    df2dy1 = 0.04
+    df2dy2 = -1.e4*y3 - 6.e7*y2
+    df2dy3 = -1.e4*y2
+
+    df3dy1 = 0.0
+    df3dy2 = 6.e7*y2
+    df3dy3 = 0.0
+
+    return np.array([ [ df1dy1, df1dy2, df1dy3 ],
+                      [ df2dy1, df2dy2, df2dy3 ],
+                      [ df3dy1, df3dy2, df3dy3 ] ])
+
+
+
+
+
+
+
def vode_integrate(Y0, tmax):
+    """ integrate using the NDF method """
+
+    r = integrate.solve_ivp(rhs, (0.0, tmax), Y0,
+                            method="BDF", jac=jac, rtol=1.e-7, atol=1.e-10)
+
+    # Note: this solver does not have a dens_output method, instead we 
+    # access the solution data where it was evaluated internally via
+    # the return object
+    
+    return r.t, r.y
+
+
+
+
+
+
+
Y0 = np.array([1.0, 0.0, 0.0])
+tmax = 4.e7
+
+ts, Ys = vode_integrate(Y0, tmax)
+
+fig, ax = plt.subplots()
+ax.loglog(ts, Ys[0,:], label=r"$y_1$")
+ax.loglog(ts, Ys[1,:], label=r"$y_2$")
+ax.loglog(ts, Ys[2,:], label=r"$y_3$")
+
+ax.legend(loc="best", frameon=False)
+ax.set_xlabel("time")
+
+
+
+
+
Text(0.5, 0, 'time')
+
+
+the time-evolution of the species on a log scale +
+
+
+

try it

+

Redo this integration, but now use the RK45 solver instead of BDF. Does it work?

+

You may need to use the kernel menu in Jupyter to interrupt the kernel if you get impatient.

+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/05-scipy/scipy-basics.html.backup b/05-scipy/scipy-basics.html.backup new file mode 100644 index 00000000..15a04508 --- /dev/null +++ b/05-scipy/scipy-basics.html.backup @@ -0,0 +1,1520 @@ + + + + + + + + + + + SciPy — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

SciPy#

+

SciPy is a collection of numerical algorithms with python interfaces. In many cases, these interfaces are wrappers around standard numerical libraries that have been developed in the community and are used with other languages. Usually detailed references are available to explain the implementation.

+
+

Note

+

There are many subpackages generally, you load the subpackages separately, e.g.

+
from scipy import linalg, optimize
+
+
+

then you have access to the methods in those namespaces

+
+
+

Important

+

One thing to keep in mind—all numerical methods have strengths and weaknesses, and make assumptions. You should always do some research into the method to understand what it is doing.

+
+
+

Tip

+

It is also always a good idea to run a new method on some test where you know the answer, to make sure it is behaving as expected.

+
+
+
+
import numpy as np
+import matplotlib.pyplot as plt
+
+
+
+
+
+

Integration#

+

we’ll do some integrals of the form

+
+\[I = \int_a^b f(x) dx\]
+

We can imagine two situations:

+
    +
  • our function \(f(x)\) is given by an analytic expression. This gives us the freedom to pick our integration points, and in general can allow us to optimize our result and get high accuracy

  • +
  • our function \(f(x)\) is defined on at a set of (possibly regular spaced) points.

  • +
+
+

Note

+

In numerical analysis, the term quadrature is used to describe any integration method that represents the integral as the weighted sum of a discrete number of points.

+
+
+
+
from scipy import integrate
+#help(integrate)
+
+
+
+
+

Let’s consider integrating

+
+\[I = \int_0^{2\pi} \sin^2(x) dx\]
+

quad() is the basic integrator for a general (not sampled) function. It uses a general-interface from the Fortran package QUADPACK (QAGS or QAGI). It will return the integral in an interval and an estimate of the error in the approximation

+
+
+
def f(x):
+    return np.sin(x)**2
+
+
+
+
+
+
+
#help(integrate.quad)
+
+
+
+
+

quad will return the integral and an estimate of the error. We can seek more accuracy by setting epsabs and epsrel, +but remember that we can’t do better than roundoff error.

+
+
+
I, err = integrate.quad(f, 0.0, 2.0*np.pi, epsabs=1.e-14, epsrel=1.e-14)
+print(I)
+print(err)
+
+
+
+
+
3.141592653589793
+3.4878684980086318e-15
+
+
+
+
+
+
+
#help(integrate.quad)
+
+
+
+
+
+

Additional arguments#

+

Sometimes our integrand function takes optional arguments. Let’s consider integrating

+
+\[g(x) = A e^{-(x/\sigma)^2}\]
+

now we want to be able to define the amplitude, \(A\), and width, \(\sigma\) as part of the function.

+
+
+
def g(x, A, sigma):
+    return A*np.exp(-x**2/sigma**2)
+
+
+
+
+
+
+
I, err = integrate.quad(g, -1.0, 1.0, args=(1.0, 2.0))
+print(I, err)
+
+
+
+
+
1.8451240256511698 2.0484991765669867e-14
+
+
+
+
+
+
+

Integrating to infinity#

+

numpy defines the inf quantity which can be used in the integration limits. We can integrate a Gaussian over \([-\infty, \infty]\) (we know the answer +is \(\sqrt{\pi}\)).

+
+

Note

+

Behind the scenes, what the integration function does is do a variable transform like: \(t = x/(c +x)\). This works when one limit is \(\infty\), giving, e.g.,

+
+\[\int_a^\infty f(x) dx = c \int_{a/(c + a)}^1 f\left (c\frac{t}{1-t}\right) (1 - t)^{-2} dt\]
+
+
+
+
I, err = integrate.quad(g, -np.inf, np.inf, args=(1.0, 1.0))
+print(I, err)
+
+
+
+
+
1.7724538509055159 1.420263678094492e-08
+
+
+
+
+
+
+

Multidimensional integrals#

+

Multidimensional integration can be done with successive calls to quad(), but there are wrappers that help

+

Let’s compute

+
+\[I = \int_{y=0}^{1/2} \int_{x=0}^{1-2y} xy dxdy = \frac{1}{96}\]
+

(this example comes from the SciPy tutorial)

+

Notice that the limits of integration in \(x\) depend on \(y\). This means that we need to do the \(x\) +integration first, which gives:

+
+\[I = \int_{y=0}^{1/2} \int_{x=0}^{1-2y} xy \,dxdy = \frac{1}{2} \int_{y=0}^{1/2} y \left [ x^2 \right |_0^{1-2y} dy = \frac{1}{2} \int_0^{1/2} (1-2y)^2 y \, dy = \frac{1}{96}\]
+

Note the form of the function:

+
dblquad(f, a, b, xlo, xhi)
+
+
+

where f = f(y, x) – the y argument is first to indicate that the \(y\) integration is done first and +then the \(x\) and \([a, b]\) are the limits of the \(x\) integration. We want the opposite in this example, +so we’ll switch the meaning of \(x\) and \(y\) in our example below.

+

The integral will be from: \(y = [0, 1/2]\), and \(x\) = xlo(y), \(x\) = xhi(y)

+
+
+
def integrand(x, y):
+    return x*y
+
+def x_lower_lim(y):
+    return 0
+    
+def x_upper_lim(y):
+    return 1-2*y
+
+# we change the definitions of x and y in this call
+I, err = integrate.dblquad(integrand, 0.0, 0.5, x_lower_lim, x_upper_lim)
+print(I, 1.0/I)
+
+
+
+
+
0.010416666666666668 95.99999999999999
+
+
+
+
+

If you remember the python lambda functions (one expression functions), you can do this more compactly:

+
+
+
I, err = integrate.dblquad(lambda x, y: x*y, 0.0, 0.5, lambda y: 0, lambda y: 1-2*y)
+print(I)
+
+
+
+
+
0.010416666666666668
+
+
+
+
+
+
+

Integration of a sampled function#

+

Here we integrate a function that is defined only at a sequence of points. A popular method +is Simpson’s rule which fits a parabola to 3 consecutive points +and integrates under the parabola.

+

Let’s compute

+
+\[I = \int_0^{2\pi} f(x_i) dx\]
+

with \(x_i = 0, \ldots, 2\pi\) defined at \(N\) points

+
+
+
N = 17
+x = np.linspace(0.0, 2.0*np.pi, N, endpoint=True)
+y = np.sin(x)**2
+
+I = integrate.simpson(y, x=x)
+print(I)
+
+
+
+
+
3.141592653589793
+
+
+
+
+

Romberg integration is specific to equally-spaced samples, where \(N = 2^k + 1\) and can be more converge faster (it uses extrapolation of coarser integration results to achieve higher accuracy)

+
+
+
N = 17
+x = np.linspace(0.0, 2.0*np.pi, N, endpoint=True)
+y = np.sin(x)**2
+
+I = integrate.romb(y, dx=x[1]-x[0])
+print(I)
+
+
+
+
+
3.1430658353300385
+
+
+
+
+
+
+
+

Interpolation#

+

Interpolation fills in the gaps between a discrete number of points by making an assumption about the behavior of the functional form of the data.

+

Many different types of interpolation exist

+
    +
  • some ensure no new extrema are introduced

  • +
  • some conserve the quantity being interpolated

  • +
  • some match derivative at end points

  • +
+
+

Caution

+

Pathologies exist—it is not always best to use a high-order polynomial to pass through all of the points in your dataset.

+
+

The interp1d() function allows for a variety of 1-d interpolation methods. It returns an object that acts as a function, which can be evaluated at any point.

+
+
+
import scipy.interpolate as interpolate
+
+
+
+
+
+
+
#help(interpolate.interp1d)
+
+
+
+
+

Let’s sample

+
+\[f(x) = x \sin(x)\]
+

and try to interpolate it.

+
+
+
def f_exact(x):
+    return np.sin(x)*x
+
+
+
+
+
+
+
N = 10
+x = np.linspace(0, 20, N)
+
+y = f_exact(x)
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+
+x_fine = np.linspace(0, 20, 10*N)
+
+ax.scatter(x, y)
+ax.plot(x_fine, f_exact(x_fine), ls=":", label="original function")
+# alt-text: a figure showing data points and an interpolant passing through them
+
+
+
+
+
[<matplotlib.lines.Line2D at 0x7f75f70c4440>]
+
+
+../_images/3ac4d61e01e4ad2f40fe01f62e57bd76a174429d7c754b847618ee8a04974bc3.png +
+
+

When we create an interpolant via interp1d, it creates a function object

+
+
+
f_interp = interpolate.interp1d(x, y, kind="cubic")
+
+
+
+
+
+
+
ax.plot(x_fine, f_interp(x_fine), label="interpolant")
+
+ax.legend(frameon=False, loc="best")
+fig
+# alt-text: a figure showing data points, an interpolant to them, and the original function we sampled
+
+
+
+
+../_images/ba892442d450d2d47e3d6ae549c3674d7660bb90ae3025a62565adab77515500.png +
+
+
+

Multi-d interpolation#

+

Here’s an example of mult-d interpolation from the official tutorial.

+

First we define the “answer”—this is the true function that we will sample at a number of points and then try to use interpolation to recover

+
+
+
def func(x, y):
+    return x*(1-x)*np.cos(4*np.pi*x) * np.sin(4*np.pi*y**2)**2
+
+
+
+
+

We’ll use np.meshgrid() to create the two-dimensional rectangular grid of points were we define our data.

+
+
+
nx = 100
+ny = 200
+
+x = np.linspace(0, 1, nx)
+y = np.linspace(0, 1, ny)
+
+x, y = np.meshgrid(x, y, indexing="ij")
+
+
+
+
+

here’s what the exact function looks like—note that our function is defined in x,y, but imshow is meant for plotting an array, so the first index is the row. We take the transpose when plotting

+
+
+
fig, ax = plt.subplots()
+data = func(x, y)
+im = ax.imshow(data.T, extent=(0, 1, 0, 1), origin="lower")
+fig.colorbar(im, ax=ax)
+# alt-text: a heat-map figure showing a function with small-amplitude ripples
+
+
+
+
+
<matplotlib.colorbar.Colorbar at 0x7f75f6dbcc20>
+
+
+../_images/7d75ee826cda9aea1c634d659e512abdd2e69cf123085dfb9f88fb2c0cb225a3.png +
+
+

Now we’ll coarsen it by taking only every 4th point

+
+
+
coarse = data[::4, ::4]
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+im = ax.imshow(coarse.T, extent=(0, 1, 0, 1), origin="lower")
+fig.colorbar(im, ax=ax)
+# alt-text: a heat-map showing coarsened representation of our function
+
+
+
+
+
<matplotlib.colorbar.Colorbar at 0x7f75f2bdc590>
+
+
+../_images/ff442053d64f0baf70c89195cd68af1f60dd275bdb42787829fb1b8bce9c50ec.png +
+
+

Let’s now use interpolation to try to recover the look of the original data.

+
+

Note

+

This is considered structured grid interpolation, and SciPy has the RegularGridInterpolator() class for this type of data.

+

If the data were unstructured, then you should explore griddata() instead.

+
+
+
+
from scipy import interpolate
+
+
+
+
+
+
+
x_coarse = np.linspace(0, 1, nx//4)
+y_coarse = np.linspace(0, 1, ny//4)
+
+interp = interpolate.RegularGridInterpolator((x_coarse, y_coarse), coarse, method="cubic")
+
+
+
+
+

Now interp() is a function that we can use to sample the coarsened data.

+

Now interpolate it onto the original grid

+
+
+
new_data = interp((x, y))
+new_data.shape
+
+
+
+
+
(100, 200)
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+im = ax.imshow(new_data.T, extent=(0, 1, 0, 1), origin="lower")
+fig.colorbar(im, ax=ax)
+# alt-text: a heat-map showing the reconstructed function via interpolation
+
+
+
+
+
<matplotlib.colorbar.Colorbar at 0x7f75f0a474d0>
+
+
+../_images/a087c096f127028aed4565dfdf3b10e2cdbd8048d1b2e886a77d05438f3f1ccb.png +
+
+

and let’s plot the difference

+
+
+
diff = new_data - data
+fig, ax = plt.subplots()
+im = ax.imshow(diff.T, origin="lower", extent=(0, 1, 0, 1))
+fig.colorbar(im, ax=ax)
+# alt-text: a heat-map showing the error in our interpolation.  It is better than 10%
+
+
+
+
+
<matplotlib.colorbar.Colorbar at 0x7f75f093a270>
+
+
+../_images/7ced87012e88958170d22c7d32d41f0aef30708641631267b24dbafcc0cea420.png +
+
+
+
+
+

Root Finding#

+

Often we need to find a value of a variable that zeros a function – this is root finding. Sometimes, this is a multidimensional problem.

+

The brentq() method offers a very robust method for find roots from a scalar function. You do need to provide an interval that bounds the root.

+
+

Tip

+

It’s a good idea to plot the function, if you can, so you can learn how the function behaves +in the vicinity of a root (and how many roots there might be)

+
+

Let’s consider:

+

\(f(x) = \frac{x e^x}{e^x - 1} - 5\)

+
+
+
import scipy.optimize as optimize
+
+
+
+
+
+
+
def f(x):
+    return (x*np.exp(x)/(np.exp(x) - 1.0) - 5.0)
+
+
+
+
+
+
+
root, r = optimize.brentq(f, 0.1, 10.0, full_output=True)
+
+print(root)
+print(r.converged)
+
+
+
+
+
4.965114231744287
+True
+
+
+
+
+
+
+
x = np.linspace(0.1, 10.0, 1000)
+fig, ax = plt.subplots()
+ax.plot(x, f(x))
+ax.scatter(np.array([root]), np.array([f(root)]))
+ax.grid()
+# alt-text: a plot of our function with the root represented as a point
+
+
+
+
+../_images/347a411bd510f339f1112f612899e17cdfa2108351c56d6d11a840fd782f14a3.png +
+
+
+
+

ODEs#

+

Many methods exist for integrating ordinary differential equations. Most will want you to write your ODEs as a system of first order equations.

+

The Lorenz system is a very simple +model of convection in our atmosphere, but demonstrates the idea of chaos well.

+

This system of ODEs for the Lorenz system is:

+
+\[\frac{dx}{dt} = \sigma (y - x)\]
+
+\[\frac{dy}{dt} = rx - y - xz\]
+
+\[\frac{dz}{dt} = xy - bz\]
+

the steady states of this system correspond to:

+
+\[\begin{split}{\bf f}({\bf x}) = +\left ( +\begin{array}{c} +\sigma (y -x) \\ +rx - y -xz \\ +xy - bz +\end{array} +\right ) += 0\end{split}\]
+
+
+
# system parameters
+sigma = 10.0
+b = 8./3.
+r = 28.0
+
+def rhs(t, x):
+    xdot = sigma*(x[1] - x[0])
+    ydot = r*x[0] - x[1] - x[0]*x[2]
+    zdot = x[0]*x[1] - b*x[2]
+
+    return np.array([xdot, ydot, zdot])
+
+def jac(t, x):
+
+    return np.array(
+        [ [-sigma, sigma, 0.0], 
+          [r - x[2], -1.0, -x[0]],
+          [x[1], x[0], -b] ])
+
+def f(x):
+    return rhs(0.,x), jac(0.,x)
+
+
+
+
+

SciPy has a uniform interface to the different ODE solvers, solve_ivp()—we use that here.

+

These integrators will do error estimation along the way and adapt the stepsize to ensure that the accuracy +you request is met.

+
+
+
def ode_integrate(X0, dt, tmax):
+    """ integrate using the VODE method, storing the solution each dt """
+
+    r = integrate.solve_ivp(rhs, (0.0, tmax), X0,
+                            method="RK45", dense_output=True)
+
+    # get the solution at intermediate times
+    ts = np.arange(0.0, tmax, dt)
+    
+    Xs = r.sol(ts)
+    return ts, Xs
+
+
+
+
+
+

Tip

+

Execute

+
%matplotlib widget
+
+
+

in a cell before making this 3D plot and you will be able to interactively +rotate it in the notebook.

+

You may need to install the ipympl package first.

+
+
+
+
t, X = ode_integrate([1.0, 1.0, 20.0], 0.02, 30)
+from mpl_toolkits.mplot3d import Axes3D
+
+fig = plt.figure()
+ax = plt.axes(projection='3d')
+ax.plot(X[0,:], X[1,:], X[2,:])
+fig.set_size_inches(8.0,6.0)
+# alt-text: a 3D line plot of the solution -- it is dominated by two lobe-like structures
+
+
+
+
+../_images/2ec66a83b5fc1eaac44c83d4f5d35f09293d9aaa6a3271a014463bd53c77613f.png +
+
+
+

try it

+

Rerun the integration, but change the initial conditions by 1 part in \(10^6\) for one of the components. +The make a plot of \(x\) vs. \(t\) comparing the solutions. You’ll see that the 2 solutions track well +for some time but then greatly diverged. This is the sensitivity to initial conditions that is the +hallmark of chaos.

+
+
+

Multi-variate root find#

+

We can find the steady points in this system by doing a multi-variate root find on the RHS vector

+
+
+
sol1 = optimize.root(f, [1., 1., 1.], jac=True)
+print(sol1.x)
+
+sol2 = optimize.root(f, [10., 10., 10.], jac=True)
+print(sol2.x)
+
+sol3 = optimize.root(f, [-10., -10., -10.], jac=True)
+print(sol3.x)
+
+
+
+
+
[0. 0. 0.]
+[ 8.48528137  8.48528137 27.        ]
+[-8.48528137 -8.48528137 27.        ]
+
+
+
+
+
+
+
fig = plt.figure()
+fig.set_size_inches(8, 8)
+ax = plt.axes(projection='3d')
+
+ax.plot(X[0,:], X[1,:], X[2,:])
+
+ax.scatter(sol1.x[0], sol1.x[1], sol1.x[2], marker="x", color="C1")
+ax.scatter(sol2.x[0], sol2.x[1], sol2.x[2], marker="x", color="C1")
+ax.scatter(sol3.x[0], sol3.x[1], sol3.x[2], marker="x", color="C1")
+
+ax.set_xlabel("x")
+ax.set_ylabel("y")
+ax.set_zlabel("z")
+# alt-text: the 3D solution again represented as a line / trajectory, now with the stable-points marked
+
+
+
+
+
Text(0.5, 0, 'z')
+
+
+../_images/082b81005f9f3f3c1ddd51267a746c559a66654295459a31d56575abd4226096.png +
+
+
+
+

Stiff system of ODEs#

+

A stiff system of ODEs is one where there are multiple disparate timescales for change and we need to respect all of them to get an accurate solution

+

Here is an example from Chemical Kinetics (see, ex. Byrne & Hindmarsh 1986, or the VODE source code)

+
+\[ +\frac{d}{dt} \left ( + \begin{array}{c} y_1 \newline y_2 \newline y_3 \end{array} + \right ) = +% +\left ( + \begin{array}{rrr} + -0.04 y_1 & + 10^4 y_2 y_3 & \newline + 0.04 y_1 & - 10^4 y_2 y_3 & -3\times 10^7 y_2^2 \newline + & & 3\times 10^7 y_2^2 +\end{array} +\right ) +\]
+
+\[ +{\bf J} = \left ( +\begin{array}{ccc} + -0.04 & 10^4 y_3 & 10^4 y_2 \newline + 0.04 & -10^4 y_3 - 6\times 10^7 y_2 & -10^4 y_2 \newline + 0 & 6\times 10^7 y_2 & 0 +\end{array} +\right ) +\]
+

start with \(y_1(0) = 1, y_2(0) = y_3(0) = 0\). Long term behavior is \(y_1, y_2 \rightarrow 0; y_3 \rightarrow 1\)

+
+
+
def rhs(t, Y):
+    """ RHS of the system -- using 0-based indexing """
+    y1 = Y[0]
+    y2 = Y[1]
+    y3 = Y[2]
+
+    dy1dt = -0.04*y1 + 1.e4*y2*y3
+    dy2dt =  0.04*y1 - 1.e4*y2*y3 - 3.e7*y2**2
+    dy3dt =                         3.e7*y2**2
+
+    return np.array([dy1dt, dy2dt, dy3dt])
+
+def jac(t, Y):
+    """ J_{i,j} = df_i/dy_j """
+
+    y1 = Y[0]
+    y2 = Y[1]
+    y3 = Y[2]
+
+    df1dy1 = -0.04
+    df1dy2 = 1.e4*y3
+    df1dy3 = 1.e4*y2
+
+    df2dy1 = 0.04
+    df2dy2 = -1.e4*y3 - 6.e7*y2
+    df2dy3 = -1.e4*y2
+
+    df3dy1 = 0.0
+    df3dy2 = 6.e7*y2
+    df3dy3 = 0.0
+
+    return np.array([ [ df1dy1, df1dy2, df1dy3 ],
+                      [ df2dy1, df2dy2, df2dy3 ],
+                      [ df3dy1, df3dy2, df3dy3 ] ])
+
+
+
+
+
+
+
def vode_integrate(Y0, tmax):
+    """ integrate using the NDF method """
+
+    r = integrate.solve_ivp(rhs, (0.0, tmax), Y0,
+                            method="BDF", jac=jac, rtol=1.e-7, atol=1.e-10)
+
+    # Note: this solver does not have a dens_output method, instead we 
+    # access the solution data where it was evaluated internally via
+    # the return object
+    
+    return r.t, r.y
+
+
+
+
+
+
+
Y0 = np.array([1.0, 0.0, 0.0])
+tmax = 4.e7
+
+ts, Ys = vode_integrate(Y0, tmax)
+
+fig, ax = plt.subplots()
+ax.loglog(ts, Ys[0,:], label=r"$y_1$")
+ax.loglog(ts, Ys[1,:], label=r"$y_2$")
+ax.loglog(ts, Ys[2,:], label=r"$y_3$")
+
+ax.legend(loc="best", frameon=False)
+ax.set_xlabel("time")
+# alt-text: the time-evolution of the species on a log scale
+
+
+
+
+
Text(0.5, 0, 'time')
+
+
+../_images/1a8b730832e97d8d0b31d24d2b778ff95ba6f3c3ccf0baa447e6022883f4a0e7.png +
+
+
+

try it

+

Redo this integration, but now use the RK45 solver instead of BDF. Does it work?

+

You may need to use the kernel menu in Jupyter to interrupt the kernel if you get impatient.

+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/05-scipy/scipy-exercises-2.html b/05-scipy/scipy-exercises-2.html new file mode 100644 index 00000000..95d4f721 --- /dev/null +++ b/05-scipy/scipy-exercises-2.html @@ -0,0 +1,894 @@ + + + + + + + + + + + More SciPy Exercises — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

More SciPy Exercises#

+
+
+
import numpy as np
+import matplotlib.pyplot as plt
+
+
+
+
+
+

Linear Algebra#

+
+
+

Q1: Condition number#

+

For a linear system, \({\bf A x} = {\bf b}\), we can only solve for \(x\) if the determinant of the matrix \({\bf A}\) is non-zero. If the determinant is zero, then we call the matrix singular. The condition number of a matrix is a measure of how close we are to being singular. The formal definition is:

+
+(1)#\[\begin{equation} +\mathrm{cond}({\bf A}) = \| {\bf A}\| \| {\bf A}^{-1} \| +\end{equation}\]
+

But we can think of it as a measure of how much \({\bf x}\) would change due to a small change in \({\bf b}\). A large condition number means that our solution for \({\bf x}\) could be inaccurate.

+

A Hilbert matrix has \(H_{ij} = (i + j + 1)^{-1}\), and is known to have a large condition number. Here’s a routine to generate a Hilbert matrix

+
+
+
def hilbert(n):
+    """ return a Hilbert matrix, H_ij = (i + j - 1)^{-1} """
+
+    H = np.zeros((n,n), dtype=np.float64)
+
+    for i in range(1, n+1):
+        for j in range(1, n+1):
+            H[i-1,j-1] = 1.0/(i + j - 1.0)
+    return H
+
+
+
+
+

Let’s solve \({\bf Hx} ={\bf b}\). Create a linear system by picking an \({\bf x}\) and generating a \({\bf b}\) by multiplying by the matrix \({\bf H}\). Then use the scipy.linalg.solve() function to recover \({\bf x}\). Compute the error in \({\bf x}\) as a function of the size of the matrix.

+

You won’t need a large matrix, \(n \sim 13\) or so, will start showing big errors.

+

You can compute the condition number with numpy.linalg.cond()

+

There are methods that can do a better job with nearly-singular matrices. Take a look at scipy.linalg.lstsq() for example.

+
+
+
+

FFTs#

+
+

Q2: Noisy signal#

+

A convolution is defined as:

+
+(2)#\[\begin{equation} + (f \star g)(t) \equiv \int_{-\infty}^{\infty} f(\tau) g(t - \tau) d\tau +\end{equation} \]
+

It is easy to compute this with FFTs, via the convolution theorem,

+
+(3)#\[\begin{equation} + \mathcal{F}\{f \star g\} = \mathcal{F}\{f\} \, \mathcal{F}\{g\} +\end{equation} \]
+

That is the Fourier transform of the convolution of \(f\) and \(g\) is simply +the product of the individual transforms of \(f\) and \(g\). This allows us +to compute the convolution via multiplication in Fourier space and then take +the inverse transform, \(\mathcal{F}^{-1}\{\}\), to recover the convolution in real space:

+
+(4)#\[\begin{equation} +f \star g = \mathcal{F}^{-1}\{ \mathcal{F}\{f\} \, \mathcal{F}\{g\}\} +\end{equation}\]
+

A common use of a convolution is to smooth noisy data, for example by convolving noisy data with a Gaussian. We’ll do that here.

+

Here’s some noisy data we’ll work with

+
+
+
def fdata(x, L):
+    A = L/10.0
+    return 2*np.sin(2*np.pi*x/L) + x*(L-x)**2/L**3 * np.cos(x) + \
+           5*x*(L-x)/L**2 + A/2 + 0.1*A*np.sin(13*np.pi*x/L)
+
+N = 2048
+L = 50.0
+x = np.linspace(0, L, N, endpoint=False)
+orig = fdata(x, L)
+
+rng = np.random.default_rng()
+noisy = orig + 0.5 * rng.standard_normal(N)
+
+
+
+
+
+
+
plt.plot(x, noisy)
+plt.plot(x, orig)
+
+
+
+
+
[<matplotlib.lines.Line2D at 0x7f170ee42ba0>]
+
+
+a plot showing noisy sinusoidal and the original, un-noised data +
+
+

SciPy provides a convolution function scipy.signal.convolve() that can do the convolution for us directly. To smooth the data, we want to use a Gaussian, which can be produced by scipy.signal.gaussian().

+

Convolve the noisy data with a Gaussian and plot the result together with the original data orig. You’ll need to play with the width of the Gaussian to get a nice smoothing. You also will need to normalize the Gaussian so that it sums to 1, otherwise, your convolved data will be shifted verfically from the original function.

+
+
+

Q3: FFT of chaotic pendulum#

+

Last time we looked at ODEs and the chaotic pendulum, and were interested in writing a method to integrate the pendulum in time.

+

Here we want to examine its behavior in frequency space. The code below will integrate the chaotic pendulum, while requesting that the solution be stored at points spaced with a fixed dt, which makes it suitable for taking the FFT.

+
+
+
from functools import partial
+from scipy.integrate import solve_ivp
+
+def rhs(t, Y, q, omega_d, b):
+    """ damped driven pendulum system derivatives.  Here, Y = (theta, omega) are
+        the solution variables. """
+    f = np.zeros_like(Y)
+        
+    f[0] = Y[1]
+    f[1] = -q*Y[1] - np.sin(Y[0]) + b*np.cos(omega_d*t)
+
+    return f
+
+def restrict_theta(theta):
+    """ convert theta to be restricted to lie between -pi and pi"""
+    tnew = theta + np.pi
+    tnew += -2.0*np.pi*np.floor(tnew/(2.0*np.pi))
+    tnew -= np.pi
+    return tnew
+
+def int_pendulum(theta0, q, omega_d, b, tend, dt):
+    """ integrate the pendulum and return solution with dt"""
+
+    # points in time where we'll request the solution
+    tpoints = np.arange(0.0, tend, dt)
+    
+    r = solve_ivp(partial(rhs, q=q, omega_d=omega_d, b=b),
+                  [0.0, tend], [theta0, 0.0],
+                  method='RK45', t_eval=tpoints)
+
+    return r.t, r.y
+
+
+
+
+

The call below will give an undamped pendulum. For a small amplitude, since we have \(L = g\) in our pendulum, the period is simply \(T = 2\pi\), and the frequency is \(\nu_k = 1/(2\pi)\). We plot things in terms of angular frequency, \(\omega_k = 2\pi \nu_k\), so all the power will be at \(\omega_k = 1\).

+
+
+
t, y = int_pendulum(np.radians(10), 0.0, 0.6666, 0.0, 200.0, 0.1)
+
+
+
+
+

Your task is to complete the power spectrum routine below to calculate the FFT of theta and plot it. Experiment with the damping and driving parameters to see the complexity of the pendulum in frequency space when it becomes chaotic. For reference, here’s a plot of the solution theta

+
+
+
plt.plot(t, restrict_theta(y[0,:]))
+plt.xlabel("t")
+plt.ylabel(r"$\theta$")
+
+
+
+
+
Text(0, 0.5, '$\\theta$')
+
+
+a plot showing many periods of a sinusoidal function +
+
+
+
+
def power_spectrum(t, theta0):
+    """ return the power spectrum of theta.  For the frequency
+        component, return it in terms of omega """
+
+    theta = restrict_theta(theta0)
+    
+    # fill in the rest -- take the FFT of theta and return omega_k and 
+    # the transform of theta
+
+
+
+
+
+
+
+

Fitting#

+
+

Q4: Let’s find the errors on our fit#

+

We looked at fits, but not what the errors are on the fit. Look at scipy.optimize.curve_fit(). This is a simplified wrapper on the least squares fitting. It can return the convariance matrix, the diagonals of which can give the error of the fit for the parameters.

+

Make up some data that models a non-linear function (by introducing some random noise) and perform a fit and find the errors on the parameters.

+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/05-scipy/scipy-exercises-2.html.backup b/05-scipy/scipy-exercises-2.html.backup new file mode 100644 index 00000000..1379ef75 --- /dev/null +++ b/05-scipy/scipy-exercises-2.html.backup @@ -0,0 +1,896 @@ + + + + + + + + + + + More SciPy Exercises — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

More SciPy Exercises#

+
+
+
import numpy as np
+import matplotlib.pyplot as plt
+
+
+
+
+
+

Linear Algebra#

+
+
+

Q1: Condition number#

+

For a linear system, \({\bf A x} = {\bf b}\), we can only solve for \(x\) if the determinant of the matrix \({\bf A}\) is non-zero. If the determinant is zero, then we call the matrix singular. The condition number of a matrix is a measure of how close we are to being singular. The formal definition is:

+
+(1)#\[\begin{equation} +\mathrm{cond}({\bf A}) = \| {\bf A}\| \| {\bf A}^{-1} \| +\end{equation}\]
+

But we can think of it as a measure of how much \({\bf x}\) would change due to a small change in \({\bf b}\). A large condition number means that our solution for \({\bf x}\) could be inaccurate.

+

A Hilbert matrix has \(H_{ij} = (i + j + 1)^{-1}\), and is known to have a large condition number. Here’s a routine to generate a Hilbert matrix

+
+
+
def hilbert(n):
+    """ return a Hilbert matrix, H_ij = (i + j - 1)^{-1} """
+
+    H = np.zeros((n,n), dtype=np.float64)
+
+    for i in range(1, n+1):
+        for j in range(1, n+1):
+            H[i-1,j-1] = 1.0/(i + j - 1.0)
+    return H
+
+
+
+
+

Let’s solve \({\bf Hx} ={\bf b}\). Create a linear system by picking an \({\bf x}\) and generating a \({\bf b}\) by multiplying by the matrix \({\bf H}\). Then use the scipy.linalg.solve() function to recover \({\bf x}\). Compute the error in \({\bf x}\) as a function of the size of the matrix.

+

You won’t need a large matrix, \(n \sim 13\) or so, will start showing big errors.

+

You can compute the condition number with numpy.linalg.cond()

+

There are methods that can do a better job with nearly-singular matrices. Take a look at scipy.linalg.lstsq() for example.

+
+
+
+

FFTs#

+
+

Q2: Noisy signal#

+

A convolution is defined as:

+
+(2)#\[\begin{equation} + (f \star g)(t) \equiv \int_{-\infty}^{\infty} f(\tau) g(t - \tau) d\tau +\end{equation} \]
+

It is easy to compute this with FFTs, via the convolution theorem,

+
+(3)#\[\begin{equation} + \mathcal{F}\{f \star g\} = \mathcal{F}\{f\} \, \mathcal{F}\{g\} +\end{equation} \]
+

That is the Fourier transform of the convolution of \(f\) and \(g\) is simply +the product of the individual transforms of \(f\) and \(g\). This allows us +to compute the convolution via multiplication in Fourier space and then take +the inverse transform, \(\mathcal{F}^{-1}\{\}\), to recover the convolution in real space:

+
+(4)#\[\begin{equation} +f \star g = \mathcal{F}^{-1}\{ \mathcal{F}\{f\} \, \mathcal{F}\{g\}\} +\end{equation}\]
+

A common use of a convolution is to smooth noisy data, for example by convolving noisy data with a Gaussian. We’ll do that here.

+

Here’s some noisy data we’ll work with

+
+
+
def fdata(x, L):
+    A = L/10.0
+    return 2*np.sin(2*np.pi*x/L) + x*(L-x)**2/L**3 * np.cos(x) + \
+           5*x*(L-x)/L**2 + A/2 + 0.1*A*np.sin(13*np.pi*x/L)
+
+N = 2048
+L = 50.0
+x = np.linspace(0, L, N, endpoint=False)
+orig = fdata(x, L)
+
+rng = np.random.default_rng()
+noisy = orig + 0.5 * rng.standard_normal(N)
+
+
+
+
+
+
+
plt.plot(x, noisy)
+plt.plot(x, orig)
+# alt-text: a plot showing noisy sinusoidal and the original, un-noised data
+
+
+
+
+
[<matplotlib.lines.Line2D at 0x7f170ee42ba0>]
+
+
+../_images/e57874be518cde2fb535060dc58c0248194f13547b08e0a540147be7889ca599.png +
+
+

SciPy provides a convolution function scipy.signal.convolve() that can do the convolution for us directly. To smooth the data, we want to use a Gaussian, which can be produced by scipy.signal.gaussian().

+

Convolve the noisy data with a Gaussian and plot the result together with the original data orig. You’ll need to play with the width of the Gaussian to get a nice smoothing. You also will need to normalize the Gaussian so that it sums to 1, otherwise, your convolved data will be shifted verfically from the original function.

+
+
+

Q3: FFT of chaotic pendulum#

+

Last time we looked at ODEs and the chaotic pendulum, and were interested in writing a method to integrate the pendulum in time.

+

Here we want to examine its behavior in frequency space. The code below will integrate the chaotic pendulum, while requesting that the solution be stored at points spaced with a fixed dt, which makes it suitable for taking the FFT.

+
+
+
from functools import partial
+from scipy.integrate import solve_ivp
+
+def rhs(t, Y, q, omega_d, b):
+    """ damped driven pendulum system derivatives.  Here, Y = (theta, omega) are
+        the solution variables. """
+    f = np.zeros_like(Y)
+        
+    f[0] = Y[1]
+    f[1] = -q*Y[1] - np.sin(Y[0]) + b*np.cos(omega_d*t)
+
+    return f
+
+def restrict_theta(theta):
+    """ convert theta to be restricted to lie between -pi and pi"""
+    tnew = theta + np.pi
+    tnew += -2.0*np.pi*np.floor(tnew/(2.0*np.pi))
+    tnew -= np.pi
+    return tnew
+
+def int_pendulum(theta0, q, omega_d, b, tend, dt):
+    """ integrate the pendulum and return solution with dt"""
+
+    # points in time where we'll request the solution
+    tpoints = np.arange(0.0, tend, dt)
+    
+    r = solve_ivp(partial(rhs, q=q, omega_d=omega_d, b=b),
+                  [0.0, tend], [theta0, 0.0],
+                  method='RK45', t_eval=tpoints)
+
+    return r.t, r.y
+
+
+
+
+

The call below will give an undamped pendulum. For a small amplitude, since we have \(L = g\) in our pendulum, the period is simply \(T = 2\pi\), and the frequency is \(\nu_k = 1/(2\pi)\). We plot things in terms of angular frequency, \(\omega_k = 2\pi \nu_k\), so all the power will be at \(\omega_k = 1\).

+
+
+
t, y = int_pendulum(np.radians(10), 0.0, 0.6666, 0.0, 200.0, 0.1)
+
+
+
+
+

Your task is to complete the power spectrum routine below to calculate the FFT of theta and plot it. Experiment with the damping and driving parameters to see the complexity of the pendulum in frequency space when it becomes chaotic. For reference, here’s a plot of the solution theta

+
+
+
plt.plot(t, restrict_theta(y[0,:]))
+plt.xlabel("t")
+plt.ylabel(r"$\theta$")
+# alt-text: a plot showing many periods of a sinusoidal function
+
+
+
+
+
Text(0, 0.5, '$\\theta$')
+
+
+../_images/b897fc22d6a1d0326dabca816283e8742aee42c4fa9ceaefb55d6e13c81f6c45.png +
+
+
+
+
def power_spectrum(t, theta0):
+    """ return the power spectrum of theta.  For the frequency
+        component, return it in terms of omega """
+
+    theta = restrict_theta(theta0)
+    
+    # fill in the rest -- take the FFT of theta and return omega_k and 
+    # the transform of theta
+
+
+
+
+
+
+
+

Fitting#

+
+

Q4: Let’s find the errors on our fit#

+

We looked at fits, but not what the errors are on the fit. Look at scipy.optimize.curve_fit(). This is a simplified wrapper on the least squares fitting. It can return the convariance matrix, the diagonals of which can give the error of the fit for the parameters.

+

Make up some data that models a non-linear function (by introducing some random noise) and perform a fit and find the errors on the parameters.

+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/05-scipy/scipy-exercises.html b/05-scipy/scipy-exercises.html new file mode 100644 index 00000000..1524d69f --- /dev/null +++ b/05-scipy/scipy-exercises.html @@ -0,0 +1,907 @@ + + + + + + + + + + + SciPy exercises — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

SciPy exercises#

+
+
+
import numpy as np
+import matplotlib.pyplot as plt
+
+
+
+
+
+

Integration#

+
+
+
from scipy import integrate
+
+
+
+
+
+

Q1: integrating an analytic function#

+

Numerical integration methods work differently depending on whether you have the analytic function available (in which case you can evaluate it freely at any point you please) or if it is sampled for you.

+

Consider the function \(f(x) = e^{-x^2}\). We want to integrate this from \([-5, 5]\). The +analytic integral is not easily obtained. Use integrate.quad to do the integration.

+
+
+

Q2: integrating a sampled function#

+

Consider now that you have data that represents a function sampled a N points, but you don’t know the analytic form of the function. Here, we create the sampling here for a Gaussian and we will do the same integral as in Q1.

+
+
+
N = 32
+x = np.linspace(-5, 5, N)
+f = np.exp(-x**2)
+f
+
+
+
+
+
array([1.38879439e-11, 3.15061953e-10, 5.80457065e-09, 8.68481106e-08,
+       1.05527775e-06, 1.04133225e-05, 8.34503173e-05, 5.43103745e-04,
+       2.87047478e-03, 1.23208538e-02, 4.29481052e-02, 1.21580337e-01,
+       2.79510942e-01, 5.21855680e-01, 7.91258065e-01, 9.74320895e-01,
+       9.74320895e-01, 7.91258065e-01, 5.21855680e-01, 2.79510942e-01,
+       1.21580337e-01, 4.29481052e-02, 1.23208538e-02, 2.87047478e-03,
+       5.43103745e-04, 8.34503173e-05, 1.04133225e-05, 1.05527775e-06,
+       8.68481106e-08, 5.80457065e-09, 3.15061953e-10, 1.38879439e-11])
+
+
+
+
+

Compute the integral of this sampled function using Simpson’s method (integrate.simps). Now, vary the number of sample points (try 64, 128, …) and see how the answer changes. Simpson’s method is 4-th order accurate, which means that the error should decrease by \(2^4\) when we double the number of sample points

+

Optional: Make a plot of the error (compared to the analytic integral from Q1) vs. N

+
+
+
+

Interpolation#

+
+
+
from scipy import interpolate
+
+
+
+
+
+

Q3: interpolation error#

+

There are a large number of different interpolation schemes available through scipy. Let’s test them out.

+

Create a python function, \(f(x)\), that is your true function. Now create \(N\) samples of it (either regularly spaced or irregularly spaced).

+

Try some of the different interolation routines. interpolate.interp1d takes a kind argument that let’s you choose the order of the interpolation. Measure the error in the method, by comparing the interpolated result with the actual function value. Also, try using cubic splines (look at CubicSpline)

+

Try plotting the resulting interpolant.

+
+
+
+

Root Finding#

+
+

Q4: scalar function roots#

+

Consider the function

+
+\[q(x) = x^3 - 2x^2 - 11x + 12\]
+

This has 3 roots, but is known to cause problems for some root-finding methods (it exhibits basis of attraction: https://en.wikipedia.org/wiki/Newton’s_method#Basins_of_attraction – very closely spaced initial guesses leave to very different roots)

+

Use the SciPy optimize.brentq method to find the roots. You might need to play around with the intervals to find all 3 roots (try plotting the function to help)

+
+
+
from scipy import optimize
+
+
+
+
+
+
+
+

ODEs#

+
+

Q5: orbits#

+

We want to consider planetary orbits. To do this, we need to solve Newton’s second law together with Newton’s law of gravity. If we restrict ourselves to the x-y plane, then there are 4 quantities we need to solve for: \(x\), \(y\), \(v_x\), and \(v_y\). These evolve according to:

+
+\[\begin{align*} +\frac{dx}{dt} &= v_x \\ +\frac{dy}{dt} &= v_y \\ +\frac{dv_x}{dt} &= a_x = -\frac{GM_\star x}{r^3} \\ +\frac{dv_y}{dt} &= a_y = -\frac{GM_\star y}{r^3} +\end{align*}\]
+

To integrate these forward in time, we need an initial condition for each quantity. We’ll setup our system such that the Sun is at the origin (that will be one focus), and the planet begins at perihelion and orbits counterclockwise.

+

geometry

+

The distance of perihelion from the focus is:

+
+\[r_p = a (1 - e)\]
+

where \(a\) is the semi-major axis and \(e\) is the eccentricity. The perihelion velocity is all in the \(y\) direction and is:

+
+\[v_y = v_p = \sqrt{\frac{GM_\star}{a} \frac{1+e}{1-e}}\]
+

We’ll work in units of AU, years, and solar masses, in which case, \(GM_\star = 4\pi^2\) (for the Sun).

+

Your initial conditions should be:

+
    +
  • \(x(t=0) = r_p\)

  • +
  • \(y(t=0) = 0\)

  • +
  • \(v_x(t=0) = 0\)

  • +
  • \(v_y(t=0) = v_p\)

  • +
+

Here’s a righthand side function for the ODEs:

+
+
+
def rhs(t, Y, GM=4*np.pi**2):
+    """RHS for orbits, Y is the solution vector, containing
+    x, y, v_x, and v_y"""
+
+    x, y, vx, vy = Y
+    f = np.zeros_like(Y)
+
+    # dx/dt = vx
+    f[0] = vx
+
+    # dy/dt = vy
+    f[1] = vy
+
+    # d(vx)/dt = -GMx/r**3
+    r = np.sqrt(x**2 + y**2)
+    f[2] = -GM*x/r**3
+
+    # d(vy)/dt = -GMy/r**3
+    f[3] = -GM*y/r**3
+
+    return f
+
+
+
+
+

Use the SciPy ODE integration methods to integrate an orbit and plot it

+
+
+

Q6: damped driven pendulum and chaos#

+

There are a large class of ODE integration methods available through the scipy.integrate.ode() function. Not all of them provide dense output – most will just give you the value at the end of the integration.

+

The explicit Runge-Kutta integrator will give you access to the solution at intermediate points and provides methods to interpolate to any value. You enable this via dense_output=True (see the example in our out-of-class notebook).

+

The damped driven pendulum obeys the following equations:

+
+\[\dot{\theta} = \omega\]
+
+\[\dot{\omega} = -q \omega - \sin \theta + b \cos \omega_d t\]
+

here, \(\theta\) is the angle of the pendulum from vertical and \(\omega\) is the angular velocity. \(q\) is a damping coefficient, \(b\) is a forcing amplitude, and \(\omega_d\) is a driving frequency.

+

Choose \(q = 0.5\) and \(\omega_d = 2/3\).

+

Integrate the system for different values of \(b\) (start with \(b = 0.9\) and increase by \(0.05\), and plot the results (\(\theta\) vs. \(t\)). Here’s a RHS function to get you started:

+
+
+
def rhs(t, Y, q, omega_d, b):
+        """ damped driven pendulum system derivatives.  Here, Y = (theta, omega) are
+        the solution variables. """
+        f = np.zeros_like(Y)
+        
+        f[0] = Y[1]
+        f[1] = -q*Y[1] - np.sin(Y[0]) + b*np.cos(omega_d*t)
+
+        return f
+
+
+
+
+

Note that the pendulum can flip over, giving values of \(\theta\) outside of \([-\pi, \pi]\). The following function can be used to restrict it back to \([-\pi, \pi]\) for plotting.

+
+
+
def restrict_theta(theta):
+    """ convert theta to be restricted to lie between -pi and pi"""
+    tnew = theta + np.pi
+    tnew += -2.0*np.pi*np.floor(tnew/(2.0*np.pi))
+    tnew -= np.pi
+    return tnew
+
+
+
+
+

Write a function that takes an initial angle, \(\theta_0\), and integrates the system and returns the solution.

+

Note, the righthand side function, rhs, takes additional arguments that you need to pass through the integrator. The preferred method to do this with the solve_ivp() interface appears to be to use functools.partial(), as:

+
from functools import partial
+
+r = solve_ivp(partial(rhs, q=q, omega_d=omega_d, b=b), ...)
+
+
+

Some values of \(b\) will show very non-periodic behavior. To see chaos, integrate two different pendula that are the same except for \(\theta_0\), with only a small difference between then (like 60 degrees and 60.0001 degrees. You’ll see the solutions track for a while, but then diverge.

+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/05-scipy/scipy.html b/05-scipy/scipy.html new file mode 100644 index 00000000..3757fc30 --- /dev/null +++ b/05-scipy/scipy.html @@ -0,0 +1,605 @@ + + + + + + + + + + + SciPy — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

SciPy

+ +
+
+ +
+
+
+ + + + +
+ +
+

SciPy#

+

SciPy provides implementations of many common numerical methods.

+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/06-sympy/sympy-examples.html b/06-sympy/sympy-examples.html new file mode 100644 index 00000000..7b7859d7 --- /dev/null +++ b/06-sympy/sympy-examples.html @@ -0,0 +1,1807 @@ + + + + + + + + + + + SymPy examples — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

SymPy examples#

+

sources: +http://docs.sympy.org/latest/tutorial/ +http://nbviewer.ipython.org/github/ipython/ipython/blob/master/examples/notebooks/SymPy Examples.ipynb

+

SymPy provides support for symbolic math to python, similar to what you would do with Mathematica or Maple. The major difference is that it acts just like any other python module, so you can use the symbolic math together in your own python projects with the rest of python functionality.

+

The following import and function (init_session()) sets up a nice environment for us when working in Jupyter

+
+
+
from sympy import init_session
+init_session(use_latex="mathjax")
+
+
+
+
+
IPython console for SymPy 1.14.0 (Python 3.14.3-64-bit) (ground types: python)
+
+These commands were executed:
+>>> from sympy import *
+>>> x, y, z, t = symbols('x y z t')
+>>> k, m, n = symbols('k m n', integer=True)
+>>> f, g, h = symbols('f g h', cls=Function)
+>>> init_printing()
+
+Documentation can be found at https://docs.sympy.org/1.14.0/
+
+
+
+
+
+
+
import math
+
+
+
+
+
+

SymPy types and basic symbolic manipulation#

+

Sympy defines its own types, you can convert them to python types, but you don’t always want to (and will probably lose accuracy when you do).

+
+
+
print(math.sqrt(2))
+
+
+
+
+
1.4142135623730951
+
+
+
+
+
+
+
print(sqrt(2))
+
+
+
+
+
sqrt(2)
+
+
+
+
+
+
+
print(sqrt(8))
+
+
+
+
+
2*sqrt(2)
+
+
+
+
+
+
+
#help(sqrt(8))
+
+
+
+
+

We can do symbolic math not just on numbers, but we can tell SymPy what to treat as a symbol, using symbols()

+
+
+
from sympy import symbols
+x, y, z = symbols("x y z")
+
+
+
+
+
+
+
expr = x + 2*y
+expr
+
+
+
+
+
+\[\displaystyle x + 2 y\]
+
+
+
+
+
expr - 1
+
+
+
+
+
+\[\displaystyle x + 2 y - 1\]
+
+
+
+
+
expr - y
+
+
+
+
+
+\[\displaystyle x + y\]
+
+
+
+
+
f = x*expr
+f
+
+
+
+
+
+\[\displaystyle x \left(x + 2 y\right)\]
+
+
+
+
+
g = expand(f)
+g
+
+
+
+
+
+\[\displaystyle x^{2} + 2 x y\]
+
+
+
+
+
factor(g)
+
+
+
+
+
+\[\displaystyle x \left(x + 2 y\right)\]
+
+
+
+
+

substitution#

+

SymPy provides methods to substitute values for symbols in symbolic expressions. Note, the follow likely does not do what you expect:

+
+
+
expr = sin(z*2*pi)
+z = 0
+expr
+
+
+
+
+
+\[\displaystyle \sin{\left(2 \pi z \right)}\]
+
+
+

We’ve now redefined z to be a python type

+
+
+
type(z)
+
+
+
+
+
int
+
+
+
+
+

to do substitution, we use the subs() method

+
+
+
expr = sin(x*2*pi)
+expr
+
+
+
+
+
+\[\displaystyle \sin{\left(2 \pi x \right)}\]
+
+
+
+
+
a = expr.subs(x, 0.125)
+a
+
+
+
+
+
+\[\displaystyle \frac{\sqrt{2}}{2}\]
+
+
+

Note that this is not a floating point number – it is still a SymPy object. To make it floating point, we can use evalf()

+
+
+
b = a.evalf()
+print(b, type(b))
+
+
+
+
+
0.707106781186548 <class 'sympy.core.numbers.Float'>
+
+
+
+
+

This is still a SymPy object, because SymPy can do arbitrary precision

+
+
+
a.evalf(50)
+
+
+
+
+
+\[\displaystyle 0.70710678118654752440084436210484903928483593768847\]
+
+
+

want regular python types?

+
+
+
c = float(b)
+print(c, type(c))
+
+
+
+
+
0.7071067811865476 <class 'float'>
+
+
+
+
+
+
+

Python and SymPy#

+
+
+
x, y, z, t = symbols('x y z t')
+
+
+
+
+

SymPy symbols are just objects and when you do operations on two sympy objects the result is a sympy object.

+

When you combine a sympy and python object, the result is also a sympy object.

+

But we need to be careful when doing fractions. For instance doing x + 1/3 will first compute 1/3 in python (giving 0.333...) and then add it to the sympy x symbol. The Rational() function makes this all happen in sympy

+
+
+
f = expr + Rational(1,3)
+f
+
+
+
+
+
+\[\displaystyle \sin{\left(2 \pi x \right)} + \frac{1}{3}\]
+
+
+
+
+
expr + 1/3
+
+
+
+
+
+\[\displaystyle \sin{\left(2 \pi x \right)} + 0.333333333333333\]
+
+
+
+
+

equality#

+

= is still the assignment operator of python (it does not mean symbolic equality), and == is still the logical test (exact structural equality). There is a separate object, Eq() to specify symbolic equality.

+

And testing for algebraic equality is not always accomplished using ==, since that tests for structural equality.

+
+
+
x + 1 == 4
+
+
+
+
+
False
+
+
+
+
+
+
+
Eq(x + 1, 4)
+
+
+
+
+
+\[\displaystyle x + 1 = 4\]
+
+
+
+
+
a = (x + 1)**2
+b = x**2 + 2*x + 1    # these are algebraically equal
+
+
+
+
+
+
+
a == b
+
+
+
+
+
False
+
+
+
+
+

We can use simplify() to test for algebraic equality

+
+
+
simplify(a - b)
+
+
+
+
+
+\[\displaystyle 0\]
+
+
+
+
+
a = cos(x) + I*sin(x)
+a
+
+
+
+
+
+\[\displaystyle i \sin{\left(x \right)} + \cos{\left(x \right)}\]
+
+
+
+
+
simplify(a)
+
+
+
+
+
+\[\displaystyle e^{i x}\]
+
+
+
+
+

More substitution#

+

note that substitution returns a new expression: SymPy expressions are immutable

+
+
+
expr = cos(x)
+expr.subs(x, 0)
+
+
+
+
+
+\[\displaystyle 1\]
+
+
+
+
+
expr
+
+
+
+
+
+\[\displaystyle \cos{\left(x \right)}\]
+
+
+
+
+
x
+
+
+
+
+
+\[\displaystyle x\]
+
+
+

multiple substitutions, pass a list of tuples

+
+
+
expr = x**3 + 4*x*y - z
+expr
+
+
+
+
+
+\[\displaystyle x^{3} + 4 x y - z\]
+
+
+
+
+
expr.subs([(x, 2), (y, 4), (z, 0)])
+
+
+
+
+
+\[\displaystyle 40\]
+
+
+
+
+

simplifying#

+

There is not unique definition of what the simplest form of an expression is.

+

simplify() tries lots of methods for simplification

+
+
+
simplify(sin(x)**2 + cos(x)**2)
+
+
+
+
+
+\[\displaystyle 1\]
+
+
+
+
+
simplify( (x**3 + x**2 - x - 1)/(x**2 + 2*x + 1) )
+
+
+
+
+
+\[\displaystyle x - 1\]
+
+
+
+
+
simplify(gamma(x)/gamma(x - 2))
+
+
+
+
+
+\[\displaystyle \left(x - 2\right) \left(x - 1\right)\]
+
+
+

but sometimes it doesn’t have your idea of what the simplest form is

+
+
+
simplify(x**2 + 2*x + 1)
+
+
+
+
+
+\[\displaystyle x^{2} + 2 x + 1\]
+
+
+

instead factor may be what you want

+
+
+
factor(x**2 + 2*x + 1)
+
+
+
+
+
+\[\displaystyle \left(x + 1\right)^{2}\]
+
+
+
+

polynomial simplification#

+
+
+
expand((x + 1)**2)
+
+
+
+
+
+\[\displaystyle x^{2} + 2 x + 1\]
+
+
+
+
+
expand((x + 2)*(x - 3))
+
+
+
+
+
+\[\displaystyle x^{2} - x - 6\]
+
+
+
+
+
expand( (x + 1)*(x - 2) - (x - 1)*x)
+
+
+
+
+
+\[\displaystyle -2\]
+
+
+
+
+
factor(x**2*z + 4*x*y*z + 4*y**2*z)
+
+
+
+
+
+\[\displaystyle z \left(x + 2 y\right)^{2}\]
+
+
+
+
+
factor_list(x**2*z + 4*x*y*z + 4*y**2*z)
+
+
+
+
+
+\[\displaystyle \left( 1, \ \left[ \left( z, \ 1\right), \ \left( x + 2 y, \ 2\right)\right]\right)\]
+
+
+

collect collects common powers

+
+
+
expr = x*y + x - 3 + 2*x**2 - z*x**2 + x**3
+expr
+
+
+
+
+
+\[\displaystyle x^{3} - x^{2} z + 2 x^{2} + x y + x - 3\]
+
+
+
+
+
collected_expr = collect(expr, x)
+collected_expr
+
+
+
+
+
+\[\displaystyle x^{3} + x^{2} \left(2 - z\right) + x \left(y + 1\right) - 3\]
+
+
+

cancel cancels

+
+
+
a = (x**2 + 2*x + 1)/(x**2 + x)
+a
+
+
+
+
+
+\[\displaystyle \frac{x^{2} + 2 x + 1}{x^{2} + x}\]
+
+
+
+
+
cancel(a)
+
+
+
+
+
+\[\displaystyle \frac{x + 1}{x}\]
+
+
+

trigsimp simplifies trigonometric identities

+
+
+
trigsimp(sin(x)**4 - 2*cos(x)**2*sin(x)**2 + cos(x)**4)
+
+
+
+
+
+\[\displaystyle \frac{\cos{\left(4 x \right)}}{2} + \frac{1}{2}\]
+
+
+
+
+
trigsimp(sin(x)*tan(x)/sec(x))
+
+
+
+
+
+\[\displaystyle \sin^{2}{\left(x \right)}\]
+
+
+

the tutorial discusses some of the nuances of simplification of powers and special functions

+
+
+
+

Calculus#

+

Calculus operations are simple in SymPy

+
+

derivatives#

+
+
+
diff(cos(x), x)
+
+
+
+
+
+\[\displaystyle - \sin{\left(x \right)}\]
+
+
+
+
+
diff(exp(x**2), x)
+
+
+
+
+
+\[\displaystyle 2 x e^{x^{2}}\]
+
+
+

third derivative

+
+
+
diff(x**4, x, 3)
+
+
+
+
+
+\[\displaystyle 24 x\]
+
+
+

differentiate different variables

+
+
+
expr = exp(x*y*z)
+diff(expr, x, y, z)
+
+
+
+
+
+\[\displaystyle \left(x^{2} y^{2} z^{2} + 3 x y z + 1\right) e^{x y z}\]
+
+
+

unevaluated derivatives can be useful for building up ODEs and PDEs

+
+
+
deriv = Derivative(expr, x, y, z)
+deriv
+
+
+
+
+
+\[\displaystyle \frac{\partial^{3}}{\partial z\partial y\partial x} e^{x y z}\]
+
+
+
+
+
deriv.doit()
+
+
+
+
+
+\[\displaystyle \left(x^{2} y^{2} z^{2} + 3 x y z + 1\right) e^{x y z}\]
+
+
+
+
+

integrals#

+

definite and indefinite integrals are supported

+
+
+
integrate(cos(x), x)
+
+
+
+
+
+\[\displaystyle \sin{\left(x \right)}\]
+
+
+

definite integral – note the construction of the infinity

+
+
+
integrate(exp(-x), (x, 0, oo))
+
+
+
+
+
+\[\displaystyle 1\]
+
+
+

double integral

+
+
+
integrate(exp(-x**2 - y**2), (x, -oo, oo), (y, -oo, oo))
+
+
+
+
+
+\[\displaystyle \pi\]
+
+
+

if it is unable to do the integral, it returns an Integral object

+
+
+
expr = integrate(x**x, x)
+print(expr)
+expr
+
+
+
+
+
Integral(x**x, x)
+
+
+
+\[\displaystyle \int x^{x}\, dx\]
+
+
+
+
+
a = x / sqrt(x**4 + 10*x**2 - 96*x - 71)   # example from Wikipedia Risch algorithm page)
+a
+
+
+
+
+
+\[\displaystyle \frac{x}{\sqrt{x^{4} + 10 x^{2} - 96 x - 71}}\]
+
+
+
+
+
integrate(a, x)     # this has a known solution, but SymPy fails to find it
+
+
+
+
+
+\[\displaystyle \int \frac{x}{\sqrt{x^{4} + 10 x^{2} - 96 x - 71}}\, dx\]
+
+
+
+
+

limits#

+
+
+
limit(sin(x)/x, x, 0)
+
+
+
+
+
+\[\displaystyle 1\]
+
+
+
+
+

series expansions#

+
+
+
expr = exp(sin(x))
+a = expr.series(x, 0, 10)
+a
+
+
+
+
+
+\[\displaystyle 1 + x + \frac{x^{2}}{2} - \frac{x^{4}}{8} - \frac{x^{5}}{15} - \frac{x^{6}}{240} + \frac{x^{7}}{90} + \frac{31 x^{8}}{5760} + \frac{x^{9}}{5670} + O\left(x^{10}\right)\]
+
+
+
+
+
c = log(x).series(x, x0=1, n=6)
+c
+
+
+
+
+
+\[\displaystyle -1 - \frac{\left(x - 1\right)^{2}}{2} + \frac{\left(x - 1\right)^{3}}{3} - \frac{\left(x - 1\right)^{4}}{4} + \frac{\left(x - 1\right)^{5}}{5} + x + O\left(\left(x - 1\right)^{6}; x\rightarrow 1\right)\]
+
+
+
+
+
simplify(c.removeO())
+
+
+
+
+
+\[\displaystyle \frac{x^{5}}{5} - \frac{5 x^{4}}{4} + \frac{10 x^{3}}{3} - 5 x^{2} + 5 x - \frac{137}{60}\]
+
+
+
+
+
+

solvers#

+

solveset() is the main interface to solvers in SymPy. Note that it used to be solve(), but this has been replaced (see http://docs.sympy.org/latest/modules/solvers/solveset.html)

+

If no Eq() is done, then it is assumed to be equal to 0

+
+
+
solveset(x**2 - x, x)
+
+
+
+
+
+\[\displaystyle \left\{0, 1\right\}\]
+
+
+

you can restrict the domain of the solution (e.g. to reals). Recall that Z is the set of integers

+
+
+
solveset(sin(x) - 1, x, domain=S.Reals)
+
+
+
+
+
+\[\displaystyle \left\{2 n \pi + \frac{\pi}{2}\; \middle|\; n \in \mathbb{Z}\right\}\]
+
+
+
+

linear systems#

+

linsolve() is the interface to linear systems

+
+
+
linsolve([x - y + 2, x + y - 3], [x, y])
+
+
+
+
+
+\[\displaystyle \left\{\left( \frac{1}{2}, \ \frac{5}{2}\right)\right\}\]
+
+
+
+
+
linsolve([x + y + z - 1, x + y + 2*z - 3 ], (x, y, z))
+
+
+
+
+
+\[\displaystyle \left\{\left( - y - 1, \ y, \ 2\right)\right\}\]
+
+
+

roots will report if a solution is multiple by listing it multiple times

+
+
+
roots(x**3 - 6*x**2 + 9*x, x)
+
+
+
+
+
+\[\displaystyle \left\{ 0 : 1, \ 3 : 2\right\}\]
+
+
+

0 is 1 root, and 3 is 2 more roots

+
+
+

Differential equations#

+

you need an undefined function (f and g already are by our init_session() above, but we’ve probably reset these

+
+
+
f, g = symbols('f g', cls=Function)
+
+
+
+
+
+
+
f(x)
+
+
+
+
+
+\[\displaystyle f{\left(x \right)}\]
+
+
+
+
+
f(x).diff(x)
+
+
+
+
+
+\[\displaystyle \frac{d}{d x} f{\left(x \right)}\]
+
+
+
+
+
diffeq = Eq(f(x).diff(x, 2) - 2*f(x).diff(x) + f(x), sin(x))
+
+
+
+
+
+
+
diffeq
+
+
+
+
+
+\[\displaystyle f{\left(x \right)} - 2 \frac{d}{d x} f{\left(x \right)} + \frac{d^{2}}{d x^{2}} f{\left(x \right)} = \sin{\left(x \right)}\]
+
+
+
+
+
dsolve(diffeq, f(x))
+
+
+
+
+
+\[\displaystyle f{\left(x \right)} = \left(C_{1} + C_{2} x\right) e^{x} + \frac{\cos{\left(x \right)}}{2}\]
+
+
+
+
+

Matrices#

+

consider the Euler equations:

+
+\[q_t + A(q) q_x = 0\]
+

where

+
+\[\begin{split}q = \left ( \begin{array}{c} \rho \\ u \\ p \end{array} \right ) +\qquad +A(q) = \left ( \begin{array}{ccc} u & \rho & 0 \\ + 0 & u & 1/\rho \\ + 0 & c^2 \rho & u \end{array} \right ) \end{split}\]
+
+
+
from sympy.abc import rho
+rho, u, c = symbols('rho u c')
+A = Matrix([[u, rho, 0], [0, u, rho**-1], [0, c**2 * rho, u]])
+A
+
+
+
+
+
+\[\begin{split}\displaystyle \left[\begin{matrix}u & \rho & 0\\0 & u & \frac{1}{\rho}\\0 & c^{2} \rho & u\end{matrix}\right]\end{split}\]
+
+
+
+
+
A.row(0)
+
+
+
+
+
+\[\displaystyle \left[\begin{matrix}u & \rho & 0\end{matrix}\right]\]
+
+
+

The eigenvalues of the system are the speeds at which information propagates

+
+
+
A.eigenvals()
+
+
+
+
+
+\[\displaystyle \left\{ u : 1, \ - c + u : 1, \ c + u : 1\right\}\]
+
+
+

You can diagonalize it, such that +$\( A = PDP^{-1}\)$

+
+
+
P, D = A.diagonalize()
+
+
+
+
+

\(D\) will be a matrix of the eigenvalues

+
+
+
D
+
+
+
+
+
+\[\begin{split}\displaystyle \left[\begin{matrix}u & 0 & 0\\0 & - c + u & 0\\0 & 0 & c + u\end{matrix}\right]\end{split}\]
+
+
+

\(P\) will be the matrix of right eigenvectors

+
+
+
P
+
+
+
+
+
+\[\begin{split}\displaystyle \left[\begin{matrix}1 & \frac{1}{c^{2}} & \frac{1}{c^{2}}\\0 & - \frac{1}{c \rho} & \frac{1}{c \rho}\\0 & 1 & 1\end{matrix}\right]\end{split}\]
+
+
+

Inverse

+
+
+
A**-1
+
+
+
+
+
+\[\begin{split}\displaystyle \left[\begin{matrix}\frac{1}{u} & - \frac{\rho}{- c^{2} + u^{2}} & \frac{1}{- c^{2} u + u^{3}}\\0 & \frac{u}{- c^{2} + u^{2}} & - \frac{1}{- c^{2} \rho + \rho u^{2}}\\0 & - \frac{c^{2} \rho}{- c^{2} + u^{2}} & \frac{u}{- c^{2} + u^{2}}\end{matrix}\right]\end{split}\]
+
+
+
+
+
+

Units#

+

Sympy can attach units to numbers and propagate them through

+
+
+
from sympy.physics.units import newton, kilogram, meter, second, convert_to
+
+
+
+
+
+
+
F = 1 * kilogram * 9.81 * meter / second**2
+convert_to(F, newton)
+
+
+
+
+
+\[\displaystyle 9.81 \text{N}\]
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/06-sympy/sympy-exercises.html b/06-sympy/sympy-exercises.html new file mode 100644 index 00000000..1c02a6c2 --- /dev/null +++ b/06-sympy/sympy-exercises.html @@ -0,0 +1,793 @@ + + + + + + + + + + + SymPy Exercises — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

SymPy Exercises#

+
+
+
import sympy as sym
+from sympy import init_session
+init_session()
+
+
+
+
+
IPython console for SymPy 1.14.0 (Python 3.14.3-64-bit) (ground types: python)
+
+These commands were executed:
+>>> from sympy import *
+>>> x, y, z, t = symbols('x y z t')
+>>> k, m, n = symbols('k m n', integer=True)
+>>> f, g, h = symbols('f g h', cls=Function)
+>>> init_printing()
+
+Documentation can be found at https://docs.sympy.org/1.14.0/
+
+
+
+
+
+

Q1: Creating an expression#

+

Create the expression:

+
+\[f = x e^{-x} + x (1-x)\]
+

Then evaluate it for

+
+\[x = 0, 0.1, 0.2, 0.4, 0.8\]
+
+
+

Q2: Factoring a polynomial, and finding its roots#

+

Factor

+
+\[x^{4} - 6 x^{3} + x^{2} + 24 x + 16\]
+

Then find its zeros.

+
+
+

Q3: Integratation and differentiation#

+

Integrate the function:

+
+\[f = \sin(x) e^{-x}\]
+

Then differentiate the result to see if you get back the original function

+
+
+

Q4: Parsing an expression#

+

Write a program that reads in a mathematical expression as a string (e.g., "sin(2*pi*x)"), converts it to a SymPy expression, and then evaluates it as needed.

+

Have your program either make a plot of the entered function, or use the input function as the function to fit a dataset to using curvefit.

+

The following will be helpful:

+

parse_expr() will convert a string into a SymPy expression

+
+
+
from sympy.parsing.sympy_parser import parse_expr
+
+
+
+
+
+
+
s = "sin(2*pi*x)"
+a = parse_expr(s)
+a
+
+
+
+
+../_images/bd2dc15da4e6fe950717320e2e27df939f63a85b567a3bd2dbf8a35f386abd83.png +
+
+

sympy.lambdify() will convert a SymPy expression into a function that is callable by python. You can make it a numpy-compatible function too (this means, e.g., that any sin() in your SymPy expression will be evaluate using np.sin())

+
+
+
f = sym.lambdify(x, a, "numpy")
+
+
+
+
+
+
+
f(1.0)
+
+
+
+
+../_images/433ba52e047896b8c7177f2df8930782d9cafe653b4234466b00034c22f8f4c7.png +
+
+
+
+
#help(lambdify)
+
+
+
+
+
+
+

Q5: Units#

+

SymPy can deal with physical units. See:

+

http://docs.sympy.org/latest/modules/physics/units/quantities.html

+

Let’s try this out. Newton’s 2nd law is

+
+\[F = ma\]
+

Create a mass of 1 kg and an acceleration of 10 m/s\(^2\), and compute the force, \(F\), and express the result in Newtons.

+

Note: the convert_to function was added in SymPy 1.1, so if you are using an earlier version, you will need to divide by the target unit to do the conversion.

+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/06-sympy/sympy.html b/06-sympy/sympy.html new file mode 100644 index 00000000..72448c57 --- /dev/null +++ b/06-sympy/sympy.html @@ -0,0 +1,607 @@ + + + + + + + + + + + SymPy — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

SymPy

+ +
+
+ +
+
+
+ + + + +
+ +
+

SymPy#

+

SymPy is the symbolic math library for python. A key design feature +of it is that it can interoperate with all of the libraries we’ve +already seen.

+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/09-packages/python-arguments.html b/09-packages/python-arguments.html new file mode 100644 index 00000000..08d940d0 --- /dev/null +++ b/09-packages/python-arguments.html @@ -0,0 +1,656 @@ + + + + + + + + + + + Command line arguments — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Command line arguments

+ +
+
+ +
+
+
+ + + + +
+ +
+

Command line arguments#

+

For standalone programs, we often want to have our program take +command line arguments that affect the runtime behavior of our +program. There are a variety of mechanisms to do this in python, but +the best option is the argparse +module.

+

Here’s an example of using argparse to take a variety of options:

+
#!/usr/bin/env python3
+
+# to get usage: use -h
+import argparse
+
+
+def setup_args():
+
+    # simple example of argparse
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument("-a", help="the -a option", action="store_true")
+    parser.add_argument("-b", help="-b takes a number", type=int, default=0)
+    parser.add_argument("-c", help="-c takes a string", type=str, default=None)
+    parser.add_argument("--darg", help="the --darg option", action="store_true")
+    parser.add_argument("--earg", help="--earg takes a string", type=str, metavar="test",
+                        default="example string")
+
+    # extra arguments (positional)
+    parser.add_argument("extras", metavar="extra", type=str, nargs="*",
+                        help="optional positional arguments")
+
+    return parser.parse_args()
+
+
+if __name__ == "__main__":
+
+    args = setup_args()
+
+
+    if args.a:
+        print("-a set")
+    print(f"-b = {args.b}")
+    print(f"-c = {args.c}")
+    if args.darg:
+        print("--dargs set")
+    print(f"--earg value = {args.earg}")
+
+    print(" ")
+    print("extra positional arguments: ")
+    if len(args.extras) > 0:
+        for e in args.extras:
+            print(e)
+
+
+

A nice feature of argparse is that it automatically generates help for us. If +we place the above code in argparse_example.py then we can do:

+
python argparse_example.py --help
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/09-packages/python-modules.html b/09-packages/python-modules.html new file mode 100644 index 00000000..df8077d5 --- /dev/null +++ b/09-packages/python-modules.html @@ -0,0 +1,707 @@ + + + + + + + + + + + Python Modules — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Python Modules

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

Python Modules#

+

So far, we’ve been writing our code all in Jupyter. But when it comes +time to write code that we want to reuse, we want to put it into a +standalone *.py file.

+

Then we can load it on in python (or Jupyter) and use the capabilities +it provides or make it a standalone program that can be run from the +command line.

+
+

Tip

+

Jupyter is great for interactive explorations and sharing your workflow with others +in a self-contained way. But if there is an operation that you do over and over, +you should put it into a separate module that you import. That way you only need to +maintain and debug a single instance of the function, and all your workflows can reuse it.

+
+
+

Editors#

+

There are a number of popular editors for writing python source. Some +popular ones include:

+ +
+
+

Standalone module#

+

Here’s a very simply module (lets call it hello.py):

+
def hello():
+    print("hello")
+
+if __name__ == "__main__":
+    hello()
+
+
+

There are two ways we can use this.

+
    +
  • Inside of python (or IPython), we can do:

    +
    import hello
    +hello.hello()
    +
    +
    +
  • +
  • From the command line, we can do:

    +
    python hello.py
    +
    +
    +
  • +
+

Additionally, on a Unix system, we can add:

+
#!/usr/bin/env python3
+
+
+

to the top and then mark the file as executable, via:

+
chmod a+x hello.py
+
+
+

allowing us to execute it simply as:

+
./hello.py
+
+
+
+

Hint

+

Here we see how the __name__ variable is treated by python:

+
    +
  • If we import our module into python, then __name__ is set to the module name

  • +
  • If we run the module from the command line, then __name__ is set to __main__

  • +
+
+
+
+

Changing module contents#

+

If we make changes to our module file, then we need to re-import it. This can be done as:

+
import importlib
+example = importlib.reload(example)
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/09-packages/python-more-modules.html b/09-packages/python-more-modules.html new file mode 100644 index 00000000..6d3da129 --- /dev/null +++ b/09-packages/python-more-modules.html @@ -0,0 +1,646 @@ + + + + + + + + + + + Module Paths — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Module Paths

+ +
+
+ +
+
+
+ + + + +
+ +
+

Module Paths#

+

How does python find modules? It has a search order:

+
    +
  • current directory

  • +
  • PYTHONPATH environment variable (this follows the same format as +the shell PATH environment variable)

  • +
  • System-wide python installation default path (usually has a +site-packages directory)

  • +
+

We can look at the path via sys.path. On my machine I get:

+
['/home/zingale/.local/bin',
+ '/home/zingale/classes/python-science/content/09-packages',
+ '/home/zingale/classes/numerical_exercises',
+ '/home/zingale/classes/astro_animations',
+ '/usr/lib64/python312.zip',
+ '/usr/lib64/python3.12',
+ '/usr/lib64/python3.12/lib-dynload',
+ '',
+ '/home/zingale/.local/lib/python3.12/site-packages',
+ '/usr/lib64/python3.12/site-packages',
+ '/usr/lib/python3.12/site-packages']
+
+
+
+
+

Note

+

You can explicitly add paths to the sys.path by setting the PYTHONPATH +environment variable.

+
+

Notice that the general places that it looks are in ~/.local and in +/usr. The first is the user-specific path—you can install things +here without admin privileges. The second is a system-wide path.

+

You can find your user-specific path via:

+
python3 -m site --user-site
+
+
+

on my machine, this gives:

+
/home/zingale/.local/lib/python3.12/site-packages
+
+
+
+

Tip

+

Using PYTHONPATH to quickly add a module to your search path is an easy hack, +but if you are developing a library that will be used by others, it is better +to make the modules installable to the system search paths. This is where +packaging comes into play.

+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/09-packages/python-packages.html b/09-packages/python-packages.html new file mode 100644 index 00000000..9272ef04 --- /dev/null +++ b/09-packages/python-packages.html @@ -0,0 +1,801 @@ + + + + + + + + + + + Packaging — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Packaging

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

Packaging#

+

Let’s look at the structure of creating an installable python package.

+
+

Note

+

The python packaging system is constantly evolving, and the current recommendations +of tools is list here: https://packaging.python.org/en/latest/guides/tool-recommendations/

+
+

xkcd comic on python packaging chaos

+

(from https://xkcd.com)

+
+

Our example#

+

We’ll work on an example that builds on the Mandelbrot set exercise +from our matplotlib discussion. Our example is hosted here:

+

sbu-python-class/mymodule

+

On your local computer, if you have git installed, you can clone this via:

+
git clone https://github.com/sbu-python-class/mymodule.git
+
+
+

The directory structure appears as:

+
mymodule/
+├── mymodule
+│   ├── __init__.py
+│   └── mandel.py
+├── pyproject.toml
+└── README.md
+
+
+

This is a rather common way of structuring a project:

+
    +
  • The top-level mymodule directory is not part of the python +package, but instead is where the source control (e.g. git) begins, +and also hosts setup files that are used for installation

  • +
  • mymodule/mymodule is the actual python module that we will load.

    +
    +

    Important

    +

    To make python recognize this as a module, we need an __init__.py +file there—it can be completely empty.

    +
    +
  • +
  • The actual *.py files that make up our module are in mymodule/mymodule

  • +
+

Right now, this package does not appear in our python search path, so +the only way to load it is to work in the top-level mymodule/ +directory, and then we can do:

+
import mymodule.mandel
+
+
+

we could also do:

+
from mymodule.mandel import mandelbrot
+
+
+
+
+

setuptools#

+

A popular set of packages are:

+
    +
  • Installation:

    +
      +
    • pip to install packages from PyPI

    • +
    • conda for disctribution cross-platform software stacks

    • +
    +
  • +
  • Packaging tools:

    +
      +
    • setuptools to create source distributions

    • +
    • build for binary distributions

    • +
    • twine to upload to PyPI

    • +
    +
  • +
+

We’ll look at how to use setuptools to package our library.

+
+

Note

+

A lot of setuptools documentation is out-of-date and +inconsistent with the packaging guidelines.

+

Packages used to create a setup.py file that had all of the project information, +but this is deprecated. Instead we should create a +pyproject.toml file—this +is consistent with PEP 517.

+
+

Here’s a first pyproject.toml:

+
[build-system]
+requires = ["setuptools"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "mymodule"
+description = "test module for PHY 546"
+readme = "README.md"
+license.text = "BSD"
+version="0.1.0"
+authors = [
+  {name="Michael Zingale"},
+  {email="michael.zingale@stonybrook.edu"},
+]
+
+dependencies = [
+  "numpy",
+  "matplotlib",
+]
+
+
+

Some notes:

+
    +
  • We have a [build-system] table that specifies the build tool. +Here we choose setuptools.

  • +
  • We have a lot of metadata for our project defined in the [project] +table.

  • +
  • We also list the dependencies of our project in the [project] table. +This will allow the installer to install any missing packages that are +required.

  • +
+

There are many additional options to specify how to find files that +are part of the project as well as data files, etc.

+
+

Tip

+

pyproject.toml also allows you to specify defaults for tools, like +pylint, flake8, and others with a [tool.X] subtable.

+
+
+

Note

+

Some projects also contain a setup.cfg +file when using setuptools. This is +usually not needed, since we can put everything +in the [project] table.

+
+
+
+

Installing#

+

We can now install simply as:

+
pip install .
+
+
+
+

Tip

+

Look in your .local/lib/python3.12/site-packages directory, and you’ll +see the module there.

+
+

If instead, we want to install in a way that still allows us to edit the source, +we can install as “editable” via:

+
pip install -e .
+
+
+

To uninstall, we can do:

+
pip uninstall mymodule
+
+
+

in a directory outside of our project (otherwise, pip may get confused).

+
+
+

Using our module#

+

Once the module is installed, we can use it from any directory. For example, if we do:

+
import mymodule
+print(mymodule.__file__)
+
+
+

it shows us where the module is installed on our system. In my case, it is:

+
/home/zingale/.local/lib/python3.12/site-packages/mymodule-0.1.0-py3.12.egg/mymodule/__init__.py
+
+
+

Let’s generate a plot:

+
from mymodule.mandel import mandelbrot
+fig = mandelbrot(128)
+fig.savefig("test.png")
+
+
+

This produces the plot shown below:

+

sample Mandelbrot set image using 128x128 points

+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/09-packages/python-tools.html b/09-packages/python-tools.html new file mode 100644 index 00000000..c952a990 --- /dev/null +++ b/09-packages/python-tools.html @@ -0,0 +1,683 @@ + + + + + + + + + + + Tools to Make Your Life Easier — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Tools to Make Your Life Easier

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

Tools to Make Your Life Easier#

+
+

Version control#

+

Generally, you should put your project into version control. The most widely used +package today is git. +git will track the changes you make to your code, allow you to revert changes, collaboratively +develop with others, work on several different features independently from one another while +keeping the main codebase clean and more.

+

git is often used together with github, which provides a web-based view +of your source code and provides additional mechanisms for collaboration.

+

A nice introduction to git/github is provided by the Software +Carpentry Version Control with Git +lesson.

+
+
+

Code checkers#

+

There are a number of tools that help check code for formatting and +syntax errors that are quite useful for developers. Many projects +automatically enforce these tools on changes submitted to github.

+
+

Tip

+

Many editors have plugins that can automatically run these tools +as your write your code.

+
+
    +
  • flake8

    +

    flake8 is a checker for PEP 8 +style conformance. You can turn off checks that you don’t like +via a .flake8 +file.

    +
  • +
  • pylint

    +

    pylint is a static code analyzer. It can find errors and also suggest improvements +to your code. You can generate a configuration file +to customize its behavior (or add a section to pyproject.toml).

    +
  • +
  • black

    +

    black is an uncompromising code formatted. It will automatically rewrite your code +based on PEP-8 style.

    +
  • +
  • pyupgrade

    +

    pyupgrade will upgrade source to a later python standard, making +use of new features where available. For instance, you can run as:

    +
    pyupgrade --py39-plus file.py
    +
    +
    +

    to update to python 3.9 support.

    +
  • +
  • isort

    +

    isort simply sorts the module imports at the top of your modules, +grouping the standard python ones together followed by +package-specific ones.

    +
  • +
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/10-testing/more-pytest.html b/10-testing/more-pytest.html new file mode 100644 index 00000000..2fd41653 --- /dev/null +++ b/10-testing/more-pytest.html @@ -0,0 +1,777 @@ + + + + + + + + + + + More pytest — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

More pytest

+ +
+ +
+
+ + + + +
+ +
+

More pytest#

+

Unit tests sometimes require some setup to be done before the test is run. +Fixtures provide this capability, allowing tests to run with a consistent +environment and data.

+

Standard pytest fixtures are written as functions with the @pytest.fixture +decorator:

+
@pytest.fixture
+def message():
+    return "Hello world!"
+
+
+

A fixture may return an object, which will be passed to any function +that requests it, or it may just do some setup tasks (like creating a file or +connecting to a database).

+

Test functions can request a fixture by specifying a parameter with the same +name as the fixture:

+
def test_split(message):
+    assert len(message.split()) == 2
+
+
+

An alternate method for initializing test state is with explicit setup/teardown +functions, which we’ll look at a bit later. This is a style that’s available in +many other languages as well: see https://en.wikipedia.org/wiki/XUnit.

+
+

Fixtures examples#

+

Fixtures are reusable across different tests. This lets us avoid repeating the +same setup code in multiple places, especially as we add more tests or need +more complicated inputs.

+

Here are some tests for the Item class that use fixtures, adapted from the +shopping cart exercise. The full code is available +here +on the github repository for this site. You can download this file and run +the tests with pytest -v test_item.py.

+

All the fixtures that a test depends on will run once for each test. +This gives each test a fresh copy of the data, so any changes made to the +fixture results inside a test won’t impact other tests.

+

We can also test that a function raises specific exceptions with pytest.raises:

+
+

Fixtures can request other fixtures#

+

This is useful to split up complex initialization into smaller parts. +A fixture can also modify the results of the fixtures it requests, which will +be visible to anything that includes the fixture.

+

Here is a set of tests that show how this can be used (test_list.py):

+
import pytest
+
+@pytest.fixture
+def numbers():
+    return []
+
+@pytest.fixture
+def append_1(numbers):
+    numbers.append(1)
+
+@pytest.fixture
+def append_2(numbers, append_1):
+    numbers.append(2)
+
+
+

Note that append_1() and append_2() only modify numbers, and don’t return +anything. append_2() requires append_1, to make sure they are run in the +right order.

+

This test only requires numbers, so it will receive an empty list:

+
def test_initial(numbers):
+    assert numbers == []
+
+
+

This test requires append_1, but not append_2:

+
def test_append_1(numbers, append_1):
+    assert numbers == [1]
+
+
+

This test requires append_2, which itself pulls in append_1:

+
def test_append_2(numbers, append_2):
+    assert numbers == [1, 2]
+
+
+
+
+
+

Example class#

+

It is common to use a class to organize a set of related unit tests. This is +not a full-fledged class – it simply helps to organize tests and data. In particular, +there is no constructor, __init__(). See https://stackoverflow.com/questions/21430900/py-test-skips-test-class-if-constructor-is-defined

+

We’ll look at an example with a NumPy array

+ +

Here’s an example:

+
# a test class is useful to hold data that we might want set up
+# for every test.
+
+import numpy as np
+from numpy.testing import assert_array_equal
+
+class TestClassExample:
+    @classmethod
+    def setup_class(cls):
+        """ this is run once for each class, before any tests """
+        pass
+
+    @classmethod
+    def teardown_class(cls):
+        """ this is run once for each class, after all tests """
+        pass
+
+    def setup_method(self):
+        """ this is run before each of the test methods """
+        self.a = np.arange(24).reshape(6, 4)
+
+    def teardown_method(self):
+        """ this is run after each of the test methods """
+        pass
+
+    def test_max(self):
+        assert self.a.max() == 23
+
+    def test_flat(self):
+        assert_array_equal(self.a.flat, np.arange(24))
+
+
+
+

Note

+

Here we see the @classmethod decorator. +This means that the function receives the class itself as the first argument rather than an instance, +e.g., self.

+
+

Put this into a file called test_class.py and then we can run as:

+
pytest -v
+
+
+
+

Quick Exercise

+

Try adding a new test that modifies self.a, above test_max(). +Does this behave as you expect? What happens if you move the array creation +into setup_class() instead?

+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/10-testing/pytest.html b/10-testing/pytest.html new file mode 100644 index 00000000..96e6f111 --- /dev/null +++ b/10-testing/pytest.html @@ -0,0 +1,736 @@ + + + + + + + + + + + pytest — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

pytest

+ +
+ +
+
+ + + + +
+ +
+

pytest#

+

pytest is a unit testing framework for python code.

+

Basic elements:

+
    +
  • Discoverability: it will find the tests

  • +
  • Automation

  • +
  • Fixtures (setup and teardown)

  • +
+
+

Installing#

+

You can install pytest for a single user as:

+
pip install pytest
+
+
+

This should put pytest in your search path, likely in ~/.local/bin.

+

If you want to generate coverage reports, you should also install pytest-cov:

+
pip install pytest-cov
+
+
+
+
+

Test discovery#

+

Adhering to these naming conventions will ensure that your tests are automatically found:

+
    +
  • File names should start or end with “test”:

    +
      +
    • test_example.py

    • +
    • example_test.py

    • +
    +
  • +
  • For tests in a class, the class name should begin with Test

    +
      +
    • e.g., TestExample

    • +
    • There should be no __init__()

    • +
    +
  • +
  • Test method / function names should start with test_

    +
      +
    • e.g., test_example()

    • +
    +
  • +
+
+
+

Assertions#

+

Tests use assertions (via python’s assert statement) to check behavior at runtime

+
    +
  • https://docs.python.org/3/reference/simple_stmts.html#assert

  • +
  • Basic usage: assert expression

    +
      +
    • Raises AssertionError if expression is not true

    • +
    • e.g., assert 1 == 0 will fail with an exception

    • +
    +
  • +
  • pytest does some magic under the hood to add more details about what +exactly went wrong, which we will see below

  • +
+
+
+

Simple pytest example#

+

Create a file named test_simple.py with the following content:

+
def multiply(a, b):
+    return a*b
+
+def test_multiply():
+    assert multiply(4, 6) == 24
+
+def test_multiply2():
+    assert multiply(5, 6) == 2
+
+
+

then we can run the tests as:

+
pytest -v
+
+
+

and we get the output:

+
============================= test session starts ==============================
+platform linux -- Python 3.11.3, pytest-7.2.2, pluggy-1.0.0 -- /usr/bin/python3
+cachedir: .pytest_cache
+rootdir: /home/zingale/temp/pytest
+plugins: anyio-3.6.2
+collected 2 items                                                              
+
+test_simple.py::test_multiply PASSED                                     [ 50%]
+test_simple.py::test_multiply2 FAILED                                    [100%]
+
+=================================== FAILURES ===================================
+________________________________ test_multiply2 ________________________________
+
+    def test_multiply2():
+>       assert multiply(5, 6) == 2
+E       assert 30 == 2
+E        +  where 30 = multiply(5, 6)
+
+test_simple.py:8: AssertionError
+=========================== short test summary info ============================
+FAILED test_simple.py::test_multiply2 - assert 30 == 2
+========================= 1 failed, 1 passed in 0.04s ==========================
+
+
+

this is telling us that one of our tests has failed.

+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/10-testing/real-world-example.html b/10-testing/real-world-example.html new file mode 100644 index 00000000..d61ee0b2 --- /dev/null +++ b/10-testing/real-world-example.html @@ -0,0 +1,712 @@ + + + + + + + + + + + Real World Example — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Real World Example

+ +
+ +
+
+ + + + +
+ +
+

Real World Example#

+

Let’s look at the testing in a larger python package. We’ll use our +group’s python hydrodynamics code, pyro, as a test:

+

python-hydro/pyro2

+
+

Installing#

+

We need to install the package first, via the setup.py:

+
python setup.py install --user
+
+
+

or alternately as

+
pip install .
+
+
+
+
+

Running the tests#

+

We can run the tests via:

+
pytest -v pyro
+
+
+
+
+

Using notebooks as tests#

+

Sometimes we want to use Jupyter notebooks as tests themselves—this +is enabled via the nbval plugin. In +this way, pytest will execute the cells in the notebook and compare +the result to the result stored in the notebook. If they agree, then +the test passes.

+

Sometimes there’s a particular cell that we don’t want to be part of the +testing—we can disable these on a cell-by-cell basis by adding +tags to a cell.

+

We can test notebooks as:

+
pytest -v --nbval pyro
+
+
+
+
+

Coverage report#

+

The pytest-cov plugin enables the generation +of a coverage report. This will tell you what fraction of each python file was tested. +We run this as:

+
pytest -v --cov=pyro --nbval pyro
+
+
+

We can also generate a more detailed interactive report with

+
coverage html
+
+
+
+
+

Other types of tests#

+

Unit tests are only one form of testing—they test a function in +isolation of others. Sometimes we need to test everything working together. +For scientific codes, regression testing is often used. The basic workflow +is:

+
    +
  • Start with the project working in a way you are happy with

  • +
  • Store the output of one (or more) runs as a benchmark.

  • +
  • Each time you make changes, run the code and compare the new output +to the stored benchmark.

    +
      +
    • If there are no differences, then your changes are likely good +(but there is always the case of some feature not being tested).

    • +
    • If there are differences, then either you introduced a bug, in which +case you should fix it, or you fixed a bug, in which case you should +update the benchmarks.

    • +
    +
  • +
+

For our example code, pyro, the regression test runs simulations using +all the different solvers and compares against the stored output, zone-by-zone +for any differences. The comparison itself is built into the main driver +of the code and can be invoked as:

+
./pyro/test.py
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/10-testing/testing.html b/10-testing/testing.html new file mode 100644 index 00000000..d4d35150 --- /dev/null +++ b/10-testing/testing.html @@ -0,0 +1,687 @@ + + + + + + + + + + + Testing — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Testing

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

Testing#

+

Testing is an integral part of the software development process. We want to catch +mistakes early, before they go on to affect our results.

+
+

Types of testing#

+

There are a lot of different types of software testing that exist. +Most commonly, for scientific codes, we hear about:

+
    +
  • Unit testing : Tests that a single function does what it was designed to do

  • +
  • Integration testing : Tests whether the individual pieces work together as intended. +Sometimes done one piece at a time (iteratively)

  • +
  • Regression testing : Checks whether code changes have changed answers

  • +
  • Verification & Validation (from the science perspective)

    +
      +
    • Verification: are we solving the equations correctly?

    • +
    • Validation: are we solving the correct equations?

    • +
    +
  • +
+
+
+

Automating testing#

+

The best testing is automated. Github provides a continuous integration service that can +be run on pull requests. You write a short definition (a Github workflow) that tells Github +how to run your tests and then any time there is a change, the tests are run.

+
+
+

Unit testing#

+
    +
  • When to write tests?

    +
      +
    • Some people advocate writing a unit test for a specification +before you write the functions they will test

      + +
    • +
    • This helps you understand the interface, return values, +side-effects, etc. of what you intend to write

    • +
    +
  • +
  • Often we already have code, so we can start by writing tests to +cover some core functionality

    +
      +
    • Add new tests when you encounter a bug, precisely to ensure that +this bug doesn’t arise again

    • +
    +
  • +
  • Tests should be short and simple

    +
      +
    • You want to be able to run them frequently

    • +
    • The more granular your tests are, the easier it will be to track down bugs

    • +
    +
  • +
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/11-machine-learning/gradient-descent.html b/11-machine-learning/gradient-descent.html new file mode 100644 index 00000000..a660b46c --- /dev/null +++ b/11-machine-learning/gradient-descent.html @@ -0,0 +1,851 @@ + + + + + + + + + + + Gradient Descent — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Gradient Descent

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

Gradient Descent#

+

Gradient descent is a simple algorithm for finding the minimum of a function of multiple variables. It works on the principle of looking at the local gradient of a function then then moving in the direction where it decreases the fastest.

+
+

Warning

+

There is no guarantee that you arrive at the global minimum instead of a local minimum.

+
+

Given a function \(f({\bf x})\), where \({\bf x} = (x_0, x_1, \ldots, x_{N-1})\), +the idea is to first compute the derivative:

+
+\[\partial f / \partial {\bf x} = (\partial f/\partial x_0, \partial f/\partial x_1, \ldots, \partial f/\partial x_{N-1})\]
+

and then move in the opposite direction by some fraction, \(\eta\):

+
+\[{\bf x} \leftarrow {\bf x} - \eta \frac{\partial f}{\partial {\bf x}}\]
+

There are different ways to define what \(\eta\) should be, but we’ll use a fixed value. We’ll call \(\eta\) the learning rate.

+

Let’s demonstrate this.

+
+
+
import numpy as np
+import matplotlib.pyplot as plt
+
+
+
+
+
+

Test function#

+

The Rosenbrock function +or the banana function is a very difficult problem for minimization. It +has the form:

+
+\[f(x, y) = (a - x)^2 + b (y - x^2)^2\]
+

and for \(a = 1\) and \(b = 100\), the minimimum is at a point \((a, a^2)\).

+
+
+
def rosenbrock(x0, x1, a, b):
+    return (a - x0)**2 + b*(x1 - x0**2)**2
+
+def drosdx(x, a, b):
+    x0 = x[0]
+    x1 = x[1]
+    return np.array([-2.0*(a - x0) - 4.0*b*(x1 - x0**2)*x0,
+                     2.0*b*(x1 - x0**2)])
+
+
+
+
+

Let’s plot the function

+
+
+
xmin = -2.0
+xmax = 2.0
+ymin = -1.0
+ymax = 3.0
+
+
+
+
+
+
+
a = 1.0
+b = 100.0
+
+
+
+
+
+
+
N = 256
+x = np.linspace(xmin, xmax, N)
+y = np.linspace(ymin, ymax, N)
+
+x2d, y2d = np.meshgrid(x, y, indexing="ij")
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+
+im = ax.imshow(np.log10(np.transpose(rosenbrock(x2d, y2d, a, b))),
+               origin="lower", extent=[xmin, xmax, ymin, ymax])
+
+fig.colorbar(im, ax=ax)
+
+
+
+
+
<matplotlib.colorbar.Colorbar at 0x7fc2531b7380>
+
+
+a heat-map plot of the banana function +
+
+
+
+

Implementing gradient descent#

+

Let’s start with an initial guess. We’ll keep guessing until the change in the solution is small.

+

Note: our success is very sensitive to our choice of \(\eta\).

+
+
+
x0 = np.array([-1.0, 1.5])
+
+
+
+
+

We’ll set a tolerance and keep iterating until the change in the solution, dx is small

+
+
+
def do_descent(dfdx, x0, eps=1.e-5, eta=2.e-3, args=None, ax=None):
+
+    # dx will be the change in the solution -- we'll iterate until this
+    # is small
+    dx = 1.e30
+    xp_old = x0.copy()
+
+    if args:
+        grad = dfdx(xp_old, *args)
+    else:
+        grad = dfdx(xp_old)
+
+    while dx > eps:
+
+        xp = xp_old - eta * grad
+        
+        if ax:
+            ax.plot([xp_old[0], xp[0]], [xp_old[1], xp[1]], color="C1")
+            
+        dx = np.linalg.norm(xp - xp_old)
+        
+        if args:
+            grad_new = dfdx(xp, *args)
+        else:
+            grad_new = dfdx(xp)
+            
+        #eta_new = np.abs(np.transpose(xp) @ (grad_new - grad)) / np.linalg.norm(grad_new - grad)**2
+        #eta = min(10*eta, eta_new)
+        
+        grad = grad_new
+        
+        xp_old[:] = xp
+
+
+
+
+
+
+
do_descent(drosdx, x0, args=(a, b), ax=ax)
+
+
+
+
+
+
+
fig
+
+
+
+
+a heat-map of the banana function with the gradient descent trajectory plotted +
+
+
+
+

momentum#

+

A variation on gradient descent is to add “momentum” +to the update. This means that the correct depends +on the past gradients as well as the current one, +via some combination. This has the effect of reducing +the zig-zag effect that we see in our attempt above.

+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/11-machine-learning/gradient-descent.html.backup b/11-machine-learning/gradient-descent.html.backup new file mode 100644 index 00000000..a0811249 --- /dev/null +++ b/11-machine-learning/gradient-descent.html.backup @@ -0,0 +1,853 @@ + + + + + + + + + + + Gradient Descent — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Gradient Descent

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

Gradient Descent#

+

Gradient descent is a simple algorithm for finding the minimum of a function of multiple variables. It works on the principle of looking at the local gradient of a function then then moving in the direction where it decreases the fastest.

+
+

Warning

+

There is no guarantee that you arrive at the global minimum instead of a local minimum.

+
+

Given a function \(f({\bf x})\), where \({\bf x} = (x_0, x_1, \ldots, x_{N-1})\), +the idea is to first compute the derivative:

+
+\[\partial f / \partial {\bf x} = (\partial f/\partial x_0, \partial f/\partial x_1, \ldots, \partial f/\partial x_{N-1})\]
+

and then move in the opposite direction by some fraction, \(\eta\):

+
+\[{\bf x} \leftarrow {\bf x} - \eta \frac{\partial f}{\partial {\bf x}}\]
+

There are different ways to define what \(\eta\) should be, but we’ll use a fixed value. We’ll call \(\eta\) the learning rate.

+

Let’s demonstrate this.

+
+
+
import numpy as np
+import matplotlib.pyplot as plt
+
+
+
+
+
+

Test function#

+

The Rosenbrock function +or the banana function is a very difficult problem for minimization. It +has the form:

+
+\[f(x, y) = (a - x)^2 + b (y - x^2)^2\]
+

and for \(a = 1\) and \(b = 100\), the minimimum is at a point \((a, a^2)\).

+
+
+
def rosenbrock(x0, x1, a, b):
+    return (a - x0)**2 + b*(x1 - x0**2)**2
+
+def drosdx(x, a, b):
+    x0 = x[0]
+    x1 = x[1]
+    return np.array([-2.0*(a - x0) - 4.0*b*(x1 - x0**2)*x0,
+                     2.0*b*(x1 - x0**2)])
+
+
+
+
+

Let’s plot the function

+
+
+
xmin = -2.0
+xmax = 2.0
+ymin = -1.0
+ymax = 3.0
+
+
+
+
+
+
+
a = 1.0
+b = 100.0
+
+
+
+
+
+
+
N = 256
+x = np.linspace(xmin, xmax, N)
+y = np.linspace(ymin, ymax, N)
+
+x2d, y2d = np.meshgrid(x, y, indexing="ij")
+
+
+
+
+
+
+
fig, ax = plt.subplots()
+
+im = ax.imshow(np.log10(np.transpose(rosenbrock(x2d, y2d, a, b))),
+               origin="lower", extent=[xmin, xmax, ymin, ymax])
+
+fig.colorbar(im, ax=ax)
+# alt-text: a heat-map plot of the banana function
+
+
+
+
+
<matplotlib.colorbar.Colorbar at 0x7fc2531b7380>
+
+
+../_images/b59abeb56bbd6be6c26cb8b33dbf9c2044a5bf2811adf7cb6f93af01d1c1da38.png +
+
+
+
+

Implementing gradient descent#

+

Let’s start with an initial guess. We’ll keep guessing until the change in the solution is small.

+

Note: our success is very sensitive to our choice of \(\eta\).

+
+
+
x0 = np.array([-1.0, 1.5])
+
+
+
+
+

We’ll set a tolerance and keep iterating until the change in the solution, dx is small

+
+
+
def do_descent(dfdx, x0, eps=1.e-5, eta=2.e-3, args=None, ax=None):
+
+    # dx will be the change in the solution -- we'll iterate until this
+    # is small
+    dx = 1.e30
+    xp_old = x0.copy()
+
+    if args:
+        grad = dfdx(xp_old, *args)
+    else:
+        grad = dfdx(xp_old)
+
+    while dx > eps:
+
+        xp = xp_old - eta * grad
+        
+        if ax:
+            ax.plot([xp_old[0], xp[0]], [xp_old[1], xp[1]], color="C1")
+            
+        dx = np.linalg.norm(xp - xp_old)
+        
+        if args:
+            grad_new = dfdx(xp, *args)
+        else:
+            grad_new = dfdx(xp)
+            
+        #eta_new = np.abs(np.transpose(xp) @ (grad_new - grad)) / np.linalg.norm(grad_new - grad)**2
+        #eta = min(10*eta, eta_new)
+        
+        grad = grad_new
+        
+        xp_old[:] = xp
+
+
+
+
+
+
+
do_descent(drosdx, x0, args=(a, b), ax=ax)
+
+
+
+
+
+
+
fig
+# alt-text: a heat-map of the banana function with the gradient descent trajectory plotted
+
+
+
+
+../_images/14a55c4b37454039b454f3503c5f615e6dccb3f245e259af494d20119331fbe7.png +
+
+
+
+

momentum#

+

A variation on gradient descent is to add “momentum” +to the update. This means that the correct depends +on the past gradients as well as the current one, +via some combination. This has the effect of reducing +the zig-zag effect that we see in our attempt above.

+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/11-machine-learning/keras-clustering.html b/11-machine-learning/keras-clustering.html new file mode 100644 index 00000000..cb8c3f71 --- /dev/null +++ b/11-machine-learning/keras-clustering.html @@ -0,0 +1,1509 @@ + + + + + + + + + + + Clustering — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Clustering

+ +
+
+ +
+
+
+ + + + +
+ +
+

Clustering#

+

Clustering seeks to group data into clusters based on their properties and then allow us to predict which cluster a new member belongs.

+
+
+
import numpy as np
+import matplotlib.pyplot as plt
+
+
+
+
+

We’ll use a dataset generator that is part of scikit-learn called make_moons. This generates data that falls into 2 different sets with a shape that looks like half-moons.

+
+
+
from sklearn import datasets
+
+
+
+
+
+
+
def generate_data():
+    xvec, val = datasets.make_moons(200, noise=0.2)
+
+    # encode the output to be 2 elements
+    x = []
+    v = []
+    for xv, vv in zip(xvec, val):
+        x.append(np.array(xv))
+        v.append(vv)
+
+    return np.array(x), np.array(v)
+
+
+
+
+
+
+
x, v = generate_data()
+
+
+
+
+

Let’s look at a point and it’s value

+
+
+
print(f"x = {x[0]}, value = {v[0]}")
+
+
+
+
+
x = [-0.61359357  0.91506266], value = 0
+
+
+
+
+

Now let’s plot the data

+
+
+
def plot_data(x, v):
+    xpt = [q[0] for q in x]
+    ypt = [q[1] for q in x]
+
+    fig, ax = plt.subplots()
+    ax.scatter(xpt, ypt, s=40, c=v, cmap="viridis")
+    ax.set_aspect("equal")
+    return fig
+
+
+
+
+
+
+
fig = plot_data(x, v)
+
+
+
+
+../_images/1ed90fdaf901f74754a93aeac3dfa7a13609cabc38460ba215bd912932326486.png +
+
+

We want to partition this domain into 2 regions, such that when we come in with a new point, we know which group it belongs to.

+

First we setup and train our network

+
+
+
from keras.models import Sequential
+from keras.layers import Dense, Dropout, Activation, Input
+from keras.optimizers import RMSprop
+
+
+
+
+
+
+
model = Sequential()
+model.add(Input(shape=(2,)))
+model.add(Dense(50, activation="relu"))
+model.add(Dense(20, activation="relu"))
+model.add(Dense(1, activation="sigmoid"))
+
+
+
+
+
+
+
rms = RMSprop()
+model.compile(loss='binary_crossentropy',
+              optimizer=rms, metrics=['accuracy'])
+
+
+
+
+
+
+
model.summary()
+
+
+
+
+
Model: "sequential"
+
+
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
+┃ Layer (type)                     Output Shape                  Param # ┃
+┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
+│ dense (Dense)                   │ (None, 50)             │           150 │
+├─────────────────────────────────┼────────────────────────┼───────────────┤
+│ dense_1 (Dense)                 │ (None, 20)             │         1,020 │
+├─────────────────────────────────┼────────────────────────┼───────────────┤
+│ dense_2 (Dense)                 │ (None, 1)              │            21 │
+└─────────────────────────────────┴────────────────────────┴───────────────┘
+
+
 Total params: 1,191 (4.65 KB)
+
+
 Trainable params: 1,191 (4.65 KB)
+
+
 Non-trainable params: 0 (0.00 B)
+
+
+
+

We seem to need a lot of epochs here to get a good result

+
+
+
epochs = 100
+results = model.fit(x, v, batch_size=50, epochs=epochs, verbose=2)
+
+
+
+
+
Epoch 1/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.5100 - loss: 0.6910
+
+
+
Epoch 2/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.5750 - loss: 0.6563
+
+
+
Epoch 3/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.7100 - loss: 0.6340
+
+
+
Epoch 4/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.7800 - loss: 0.6140
+
+
+
Epoch 5/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8200 - loss: 0.5950
+
+
+
Epoch 6/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8100 - loss: 0.5776
+
+
+
Epoch 7/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8250 - loss: 0.5611
+
+
+
Epoch 8/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8250 - loss: 0.5449
+
+
+
Epoch 9/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8350 - loss: 0.5291
+
+
+
Epoch 10/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8300 - loss: 0.5137
+
+
+
Epoch 11/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8350 - loss: 0.4990
+
+
+
Epoch 12/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8350 - loss: 0.4852
+
+
+
Epoch 13/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8350 - loss: 0.4722
+
+
+
Epoch 14/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8350 - loss: 0.4600
+
+
+
Epoch 15/100
+
+
+
4/4 - 0s - 19ms/step - accuracy: 0.8350 - loss: 0.4489
+
+
+
Epoch 16/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8400 - loss: 0.4378
+
+
+
Epoch 17/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8400 - loss: 0.4274
+
+
+
Epoch 18/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8450 - loss: 0.4183
+
+
+
Epoch 19/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8400 - loss: 0.4087
+
+
+
Epoch 20/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8450 - loss: 0.4006
+
+
+
Epoch 21/100
+
+
+
4/4 - 0s - 10ms/step - accuracy: 0.8450 - loss: 0.3928
+
+
+
Epoch 22/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8600 - loss: 0.3856
+
+
+
Epoch 23/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8550 - loss: 0.3786
+
+
+
Epoch 24/100
+
+
+
4/4 - 0s - 6ms/step - accuracy: 0.8600 - loss: 0.3728
+
+
+
Epoch 25/100
+
+
+
4/4 - 0s - 6ms/step - accuracy: 0.8600 - loss: 0.3674
+
+
+
Epoch 26/100
+
+
+
4/4 - 0s - 6ms/step - accuracy: 0.8600 - loss: 0.3615
+
+
+
Epoch 27/100
+
+
+
4/4 - 0s - 19ms/step - accuracy: 0.8650 - loss: 0.3572
+
+
+
Epoch 28/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8700 - loss: 0.3524
+
+
+
Epoch 29/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8750 - loss: 0.3474
+
+
+
Epoch 30/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8800 - loss: 0.3436
+
+
+
Epoch 31/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8800 - loss: 0.3395
+
+
+
Epoch 32/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8800 - loss: 0.3363
+
+
+
Epoch 33/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8800 - loss: 0.3326
+
+
+
Epoch 34/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8800 - loss: 0.3294
+
+
+
Epoch 35/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8800 - loss: 0.3266
+
+
+
Epoch 36/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8800 - loss: 0.3245
+
+
+
Epoch 37/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8800 - loss: 0.3223
+
+
+
Epoch 38/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8800 - loss: 0.3199
+
+
+
Epoch 39/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8800 - loss: 0.3189
+
+
+
Epoch 40/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8800 - loss: 0.3160
+
+
+
Epoch 41/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8750 - loss: 0.3151
+
+
+
Epoch 42/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8800 - loss: 0.3141
+
+
+
Epoch 43/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8750 - loss: 0.3111
+
+
+
Epoch 44/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8750 - loss: 0.3102
+
+
+
Epoch 45/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8800 - loss: 0.3082
+
+
+
Epoch 46/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8800 - loss: 0.3065
+
+
+
Epoch 47/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8800 - loss: 0.3057
+
+
+
Epoch 48/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8750 - loss: 0.3041
+
+
+
Epoch 49/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8750 - loss: 0.3020
+
+
+
Epoch 50/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8700 - loss: 0.3028
+
+
+
Epoch 51/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8700 - loss: 0.3001
+
+
+
Epoch 52/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8700 - loss: 0.2989
+
+
+
Epoch 53/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8750 - loss: 0.2980
+
+
+
Epoch 54/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8750 - loss: 0.2956
+
+
+
Epoch 55/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8750 - loss: 0.2956
+
+
+
Epoch 56/100
+
+
+
4/4 - 0s - 27ms/step - accuracy: 0.8700 - loss: 0.2933
+
+
+
Epoch 57/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8750 - loss: 0.2922
+
+
+
Epoch 58/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8800 - loss: 0.2925
+
+
+
Epoch 59/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8800 - loss: 0.2900
+
+
+
Epoch 60/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8750 - loss: 0.2893
+
+
+
Epoch 61/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8750 - loss: 0.2875
+
+
+
Epoch 62/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8850 - loss: 0.2872
+
+
+
Epoch 63/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8850 - loss: 0.2849
+
+
+
Epoch 64/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8850 - loss: 0.2833
+
+
+
Epoch 65/100
+
+
+
4/4 - 0s - 12ms/step - accuracy: 0.8850 - loss: 0.2853
+
+
+
Epoch 66/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8850 - loss: 0.2823
+
+
+
Epoch 67/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8850 - loss: 0.2805
+
+
+
Epoch 68/100
+
+
+
4/4 - 0s - 7ms/step - accuracy: 0.8800 - loss: 0.2796
+
+
+
Epoch 69/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8850 - loss: 0.2779
+
+
+
Epoch 70/100
+
+
+
4/4 - 0s - 6ms/step - accuracy: 0.8850 - loss: 0.2784
+
+
+
Epoch 71/100
+
+
+
4/4 - 0s - 6ms/step - accuracy: 0.8850 - loss: 0.2758
+
+
+
Epoch 72/100
+
+
+
4/4 - 0s - 19ms/step - accuracy: 0.8800 - loss: 0.2740
+
+
+
Epoch 73/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8850 - loss: 0.2736
+
+
+
Epoch 74/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8850 - loss: 0.2722
+
+
+
Epoch 75/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8850 - loss: 0.2717
+
+
+
Epoch 76/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8850 - loss: 0.2700
+
+
+
Epoch 77/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8850 - loss: 0.2677
+
+
+
Epoch 78/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8900 - loss: 0.2681
+
+
+
Epoch 79/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8900 - loss: 0.2657
+
+
+
Epoch 80/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8900 - loss: 0.2652
+
+
+
Epoch 81/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8950 - loss: 0.2636
+
+
+
Epoch 82/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8950 - loss: 0.2626
+
+
+
Epoch 83/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8950 - loss: 0.2607
+
+
+
Epoch 84/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.9000 - loss: 0.2595
+
+
+
Epoch 85/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8950 - loss: 0.2598
+
+
+
Epoch 86/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8950 - loss: 0.2581
+
+
+
Epoch 87/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.9000 - loss: 0.2568
+
+
+
Epoch 88/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8950 - loss: 0.2548
+
+
+
Epoch 89/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.9000 - loss: 0.2543
+
+
+
Epoch 90/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.9000 - loss: 0.2523
+
+
+
Epoch 91/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8950 - loss: 0.2518
+
+
+
Epoch 92/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8950 - loss: 0.2524
+
+
+
Epoch 93/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8950 - loss: 0.2485
+
+
+
Epoch 94/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.9000 - loss: 0.2486
+
+
+
Epoch 95/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.9000 - loss: 0.2468
+
+
+
Epoch 96/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.8950 - loss: 0.2469
+
+
+
Epoch 97/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.9000 - loss: 0.2450
+
+
+
Epoch 98/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.9000 - loss: 0.2429
+
+
+
Epoch 99/100
+
+
+
4/4 - 0s - 5ms/step - accuracy: 0.9000 - loss: 0.2418
+
+
+
Epoch 100/100
+
+
+
4/4 - 0s - 32ms/step - accuracy: 0.9000 - loss: 0.2407
+
+
+
+
+
+
+
score = model.evaluate(x, v, verbose=0)
+print(f"score = {score[0]}")
+print(f"accuracy = {score[1]}")
+
+
+
+
+
score = 0.23850904405117035
+accuracy = 0.8999999761581421
+
+
+
+
+

Let’s look at a prediction. We need to feed in a single point as an array of shape (N, 2), where N is the number of points

+
+
+
res = model.predict(np.array([[-2, 2]]))
+res
+
+
+
+
+
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step
+
+
+

+1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step
+
+
+
array([[5.108066e-08]], dtype=float32)
+
+
+
+
+

We see that we get a floating point number. We will need to convert this to 0 or 1 by rounding.

+

Let’s plot the partitioning

+
+
+
M = 128
+N = 128
+
+xmin = -1.75
+xmax = 2.5
+ymin = -1.25
+ymax = 1.75
+
+xpt = np.linspace(xmin, xmax, M)
+ypt = np.linspace(ymin, ymax, N)
+
+
+
+
+

To make the prediction go faster, we want to feed in a vector of these points, of the form:

+
[[xpt[0], ypt[0]],
+ [xpt[1], ypt[1]],
+ ...
+]
+
+
+

We can see that this packs them into the vector

+
+
+
pairs = np.array(np.meshgrid(xpt, ypt)).T.reshape(-1, 2)
+pairs[0]
+
+
+
+
+
array([-1.75, -1.25])
+
+
+
+
+

Now we do the prediction. We will get a vector out, which we reshape to match the original domain.

+
+
+
res = model.predict(pairs, verbose=0)
+res.shape = (M, N)
+
+
+
+
+

Finally, round to 0 or 1

+
+
+
domain = np.where(res > 0.5, 1, 0)
+
+
+
+
+

and we can plot the data

+
+
+
fig, ax = plt.subplots()
+ax.imshow(domain.T, origin="lower",
+          extent=[xmin, xmax, ymin, ymax], alpha=0.25)
+xpt = [q[0] for q in x]
+ypt = [q[1] for q in x]
+
+ax.scatter(xpt, ypt, s=40, c=v, cmap="viridis")
+
+
+
+
+
<matplotlib.collections.PathCollection at 0x7f3916647890>
+
+
+../_images/db2583cf6aec7a55e71d7e16af03a5ebda9f71fd6cdd2a2d93a6f2cf0a606806.png +
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/11-machine-learning/keras-mnist.html b/11-machine-learning/keras-mnist.html new file mode 100644 index 00000000..b9858bac --- /dev/null +++ b/11-machine-learning/keras-mnist.html @@ -0,0 +1,1666 @@ + + + + + + + + + + + KERAS and MNIST — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

KERAS and MNIST#

+
+
+
import matplotlib.pyplot as plt
+import numpy as np
+
+
+
+
+

We’ll apply the ideas we just learned to a neural network that does character recognition using the MNIST database. This is a set of handwritten digits (0–9) represented as a 28×28 pixel grayscale image.

+

There are 2 datasets, the training set with 60,000 images and the test set with 10,000 images.

+
+
+
import keras
+
+
+
+
+
+

Important

+

Keras requires a backend, which can be tensorflow, pytorch, or jax.

+

By default, it will assume tensorflow.

+

This notebook has been tested with both pytorch and tensorflow.

+
+
+

Tip

+

To have keras use pytorch, set the environment variable KERAS_BACKEND as:

+
export KERAS_BACKEND="torch"
+
+
+
+

We follow the example for setting up the network: +Vict0rSch/deep_learning

+
+

Note

+

For visualization of the network, you need to have pydot installed.

+
+
+

The MNIST data#

+

The keras library can download the MNIST data directly and provides a function to give us both the training and test images and the corresponding digits. This is already in a format that Keras wants, so we don’t use the classes that we defined earlier.

+
+
+
from keras.datasets import mnist
+
+
+
+
+
+
+
(X_train, y_train), (X_test, y_test) = mnist.load_data()
+
+
+
+
+
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
+
+
+
       0/11490434 ━━━━━━━━━━━━━━━━━━━━ 0s 0s/step
+
+
+

+  696320/11490434 ━━━━━━━━━━━━━━━━━━━ 0s 0us/step
+
+
+

+10379264/11490434 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step
+
+
+

+11490434/11490434 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step
+
+
+
+
+

As before, the training set consists of 60000 digits represented as a 28x28 array (there are no color channels, so this is grayscale data). They are also integer data.

+
+
+
X_train.shape
+
+
+
+
+
(60000, 28, 28)
+
+
+
+
+
+
+
X_train.dtype
+
+
+
+
+
dtype('uint8')
+
+
+
+
+

Let’s look at the first digit and the “y” value (target) associated with it—that’s the correct answer.

+
+
+
plt.imshow(X_train[0], cmap="gray_r")
+print(y_train[0])
+
+
+
+
+
5
+
+
+the number 5 represented as a small grayscale image +
+
+
+
+

Preparing the Data#

+

The neural network takes a 1-d vector of input and will return a 1-d vector of output. We need to convert our data to this form.

+

We’ll scale the image data to fall in [0, 1) and the numerical output to be categorized as an array. Finally, we need the input data to be one-dimensional, so we fill flatten the 28x28 images into a single 784 vector.

+
+
+
X_train = X_train.astype('float32')/255
+X_test = X_test.astype('float32')/255
+
+X_train = np.reshape(X_train, (60000, 784))
+X_test = np.reshape(X_test, (10000, 784))
+
+
+
+
+
+
+
X_train[0]
+
+
+
+
+
array([0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.01176471, 0.07058824, 0.07058824,
+       0.07058824, 0.49411765, 0.53333336, 0.6862745 , 0.10196079,
+       0.6509804 , 1.        , 0.96862745, 0.49803922, 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.11764706, 0.14117648, 0.36862746, 0.6039216 ,
+       0.6666667 , 0.99215686, 0.99215686, 0.99215686, 0.99215686,
+       0.99215686, 0.88235295, 0.6745098 , 0.99215686, 0.9490196 ,
+       0.7647059 , 0.2509804 , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.19215687, 0.93333334,
+       0.99215686, 0.99215686, 0.99215686, 0.99215686, 0.99215686,
+       0.99215686, 0.99215686, 0.99215686, 0.9843137 , 0.3647059 ,
+       0.32156864, 0.32156864, 0.21960784, 0.15294118, 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.07058824, 0.85882354, 0.99215686, 0.99215686,
+       0.99215686, 0.99215686, 0.99215686, 0.7764706 , 0.7137255 ,
+       0.96862745, 0.94509804, 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.3137255 , 0.6117647 , 0.41960785, 0.99215686, 0.99215686,
+       0.8039216 , 0.04313726, 0.        , 0.16862746, 0.6039216 ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.05490196,
+       0.00392157, 0.6039216 , 0.99215686, 0.3529412 , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.54509807,
+       0.99215686, 0.74509805, 0.00784314, 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.04313726, 0.74509805, 0.99215686,
+       0.27450982, 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.13725491, 0.94509804, 0.88235295, 0.627451  ,
+       0.42352942, 0.00392157, 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.31764707, 0.9411765 , 0.99215686, 0.99215686, 0.46666667,
+       0.09803922, 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.1764706 ,
+       0.7294118 , 0.99215686, 0.99215686, 0.5882353 , 0.10588235,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.0627451 , 0.3647059 ,
+       0.9882353 , 0.99215686, 0.73333335, 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.9764706 , 0.99215686,
+       0.9764706 , 0.2509804 , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.18039216, 0.50980395,
+       0.7176471 , 0.99215686, 0.99215686, 0.8117647 , 0.00784314,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.15294118,
+       0.5803922 , 0.8980392 , 0.99215686, 0.99215686, 0.99215686,
+       0.98039216, 0.7137255 , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.09411765, 0.44705883, 0.8666667 , 0.99215686, 0.99215686,
+       0.99215686, 0.99215686, 0.7882353 , 0.30588236, 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.09019608, 0.25882354, 0.8352941 , 0.99215686,
+       0.99215686, 0.99215686, 0.99215686, 0.7764706 , 0.31764707,
+       0.00784314, 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.07058824, 0.67058825, 0.85882354,
+       0.99215686, 0.99215686, 0.99215686, 0.99215686, 0.7647059 ,
+       0.3137255 , 0.03529412, 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.21568628, 0.6745098 ,
+       0.8862745 , 0.99215686, 0.99215686, 0.99215686, 0.99215686,
+       0.95686275, 0.52156866, 0.04313726, 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.53333336, 0.99215686, 0.99215686, 0.99215686,
+       0.83137256, 0.5294118 , 0.5176471 , 0.0627451 , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        ], dtype=float32)
+
+
+
+
+

We will use categorical data. Keras includes routines to categorize data. In our case, since there are 10 possible digits, we want to put the output into 10 categories (represented by 10 neurons)

+
+
+
from keras.utils import to_categorical
+
+y_train = to_categorical(y_train, 10)
+y_test = to_categorical(y_test, 10)
+
+
+
+
+

Now let’s look at the target for the first training digit. We know from above that it was ‘5’. Here we see that there is a 1 in the index corresponding to 5 (remember we start counting at 0 in python).

+
+
+
y_train[0]
+
+
+
+
+
array([0., 0., 0., 0., 0., 1., 0., 0., 0., 0.])
+
+
+
+
+
+
+

Build the Neural Network#

+

Now we’ll build the neural network. We will have 2 hidden layers, and the number of neurons will look like:

+

784 → 500 → 300 → 10

+
+

Layers#

+

Let’s start by setting up the layers. For each layer, we tell keras the number of output neurons. It infers the number of inputs from the previous layer (with the exception of the input layer, where we need to tell it what to expect as input).

+

Properties on the layers:

+ +
+
+
from keras.models import Sequential
+from keras.layers import Dense, Dropout, Activation, Input
+
+model = Sequential()
+model.add(Input(shape=(784,)))
+model.add(Dense(500, activation="relu"))
+model.add(Dropout(0.4))
+model.add(Dense(300, activation="relu"))
+model.add(Dropout(0.4))
+model.add(Dense(10, activation="softmax"))
+
+
+
+
+
+
+

Loss function#

+

We need to specify what we want to optimize and how we are going to do it.

+

Recall: the loss (or cost) function measures how well our predictions match the expected target. +Previously we were using the sum of the squares of the error.

+

For categorical data, like we have, the “cross-entropy” metric is often used. See here for an explanation: https://jamesmccaffrey.wordpress.com/2013/11/05/why-you-should-use-cross-entropy-error-instead-of-classification-error-or-mean-squared-error-for-neural-network-classifier-training/

+
+
+

Optimizer#

+

We also need to specify an optimizer. This could be gradient descent, as we used before. Here’s a list of the optimizers supoprted by keras: https://keras.io/api/optimizers/ We’ll use RMPprop, which builds off of gradient descent and includes some momentum.

+

Finally, we need to specify a metric that is evaluated during training and testing. We’ll use "accuracy" here. This means that we’ll see the accuracy of our model reported as we are training and testing.

+

More details on these options is here: https://keras.io/api/models/model/

+
+
+
from keras.optimizers import RMSprop
+
+rms = RMSprop()
+model.compile(loss='categorical_crossentropy',
+              optimizer=rms, metrics=['accuracy'])
+
+
+
+
+
+
+

Network summary#

+

Let’s take a look at the network:

+
+
+
model.summary()
+
+
+
+
+
Model: "sequential"
+
+
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
+┃ Layer (type)                     Output Shape                  Param # ┃
+┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
+│ dense (Dense)                   │ (None, 500)            │       392,500 │
+├─────────────────────────────────┼────────────────────────┼───────────────┤
+│ dropout (Dropout)               │ (None, 500)            │             0 │
+├─────────────────────────────────┼────────────────────────┼───────────────┤
+│ dense_1 (Dense)                 │ (None, 300)            │       150,300 │
+├─────────────────────────────────┼────────────────────────┼───────────────┤
+│ dropout_1 (Dropout)             │ (None, 300)            │             0 │
+├─────────────────────────────────┼────────────────────────┼───────────────┤
+│ dense_2 (Dense)                 │ (None, 10)             │         3,010 │
+└─────────────────────────────────┴────────────────────────┴───────────────┘
+
+
 Total params: 545,810 (2.08 MB)
+
+
 Trainable params: 545,810 (2.08 MB)
+
+
 Non-trainable params: 0 (0.00 B)
+
+
+
+

We see that there are > 500k parameters that we will be training

+
+
+
+
+

Train#

+

For training, we pass in the inputs and target and the number of epochs to run and it will optimize the network by adjusting the weights between the nodes in the layers.

+

The number of epochs is the number of times the entire data set is passed forward and backward through the network. The batch size is the number of training pairs you pass through the network at a given time. You update the parameter in your model (the weights) once for each batch. This makes things more efficient and less noisy.

+
+
+
epochs = 20
+batch_size = 256
+model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size,
+          validation_data=(X_test, y_test), verbose=2)
+
+
+
+
+
Epoch 1/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.8846 - loss: 0.3774 - val_accuracy: 0.9475 - val_loss: 0.1665
+
+
+
Epoch 2/20
+
+
+
235/235 - 3s - 15ms/step - accuracy: 0.9515 - loss: 0.1611 - val_accuracy: 0.9676 - val_loss: 0.1048
+
+
+
Epoch 3/20
+
+
+
235/235 - 4s - 16ms/step - accuracy: 0.9650 - loss: 0.1192 - val_accuracy: 0.9754 - val_loss: 0.0790
+
+
+
Epoch 4/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9702 - loss: 0.0951 - val_accuracy: 0.9779 - val_loss: 0.0738
+
+
+
Epoch 5/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9747 - loss: 0.0821 - val_accuracy: 0.9785 - val_loss: 0.0727
+
+
+
Epoch 6/20
+
+
+
235/235 - 4s - 16ms/step - accuracy: 0.9776 - loss: 0.0722 - val_accuracy: 0.9806 - val_loss: 0.0655
+
+
+
Epoch 7/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9801 - loss: 0.0649 - val_accuracy: 0.9811 - val_loss: 0.0641
+
+
+
Epoch 8/20
+
+
+
235/235 - 4s - 16ms/step - accuracy: 0.9818 - loss: 0.0594 - val_accuracy: 0.9805 - val_loss: 0.0669
+
+
+
Epoch 9/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9829 - loss: 0.0541 - val_accuracy: 0.9832 - val_loss: 0.0599
+
+
+
Epoch 10/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9840 - loss: 0.0503 - val_accuracy: 0.9828 - val_loss: 0.0622
+
+
+
Epoch 11/20
+
+
+
235/235 - 4s - 16ms/step - accuracy: 0.9855 - loss: 0.0456 - val_accuracy: 0.9836 - val_loss: 0.0655
+
+
+
Epoch 12/20
+
+
+
235/235 - 4s - 16ms/step - accuracy: 0.9863 - loss: 0.0431 - val_accuracy: 0.9831 - val_loss: 0.0635
+
+
+
Epoch 13/20
+
+
+
235/235 - 4s - 16ms/step - accuracy: 0.9873 - loss: 0.0398 - val_accuracy: 0.9827 - val_loss: 0.0655
+
+
+
Epoch 14/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9881 - loss: 0.0372 - val_accuracy: 0.9830 - val_loss: 0.0631
+
+
+
Epoch 15/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9896 - loss: 0.0328 - val_accuracy: 0.9837 - val_loss: 0.0688
+
+
+
Epoch 16/20
+
+
+
235/235 - 4s - 16ms/step - accuracy: 0.9891 - loss: 0.0334 - val_accuracy: 0.9841 - val_loss: 0.0662
+
+
+
Epoch 17/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9896 - loss: 0.0315 - val_accuracy: 0.9835 - val_loss: 0.0662
+
+
+
Epoch 18/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9900 - loss: 0.0316 - val_accuracy: 0.9846 - val_loss: 0.0613
+
+
+
Epoch 19/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9905 - loss: 0.0309 - val_accuracy: 0.9852 - val_loss: 0.0575
+
+
+
Epoch 20/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9912 - loss: 0.0270 - val_accuracy: 0.9837 - val_loss: 0.0671
+
+
+
<keras.src.callbacks.history.History at 0x7f0ac99327b0>
+
+
+
+
+
+

Test#

+

keras has a routine, evaluate() that can take the inputs and targets of a test data set and return the loss value and accuracy (or other defined metrics) on this data.

+

Here we see we are > 98% accurate on the test data—this is the data that the model has never seen before (and was not trained with).

+
+
+
loss_value, accuracy = model.evaluate(X_test, y_test, batch_size=16)
+print(accuracy)
+
+
+
+
+
  1/625 ━━━━━━━━━━━━━━━━━━━━ 3s 6ms/step - accuracy: 0.9375 - loss: 0.1659
+
+
+

+  5/625 ━━━━━━━━━━━━━━━━━━━━ 18s 30ms/step - accuracy: 0.9715 - loss: 0.0759
+
+
+

+ 17/625 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9793 - loss: 0.0572 
+
+
+

+ 29/625 ━━━━━━━━━━━━━━━━━━━━ 4s 8ms/step - accuracy: 0.9805 - loss: 0.0646 
+
+
+

+ 40/625 ━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.9807 - loss: 0.0663
+
+
+

+ 46/625 ━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.9807 - loss: 0.0676
+
+
+

+ 56/625 ━━━━━━━━━━━━━━━━━━━ 3s 7ms/step - accuracy: 0.9804 - loss: 0.0698
+
+
+

+ 66/625 ━━━━━━━━━━━━━━━━━━━━ 3s 7ms/step - accuracy: 0.9801 - loss: 0.0713
+
+
+

+ 69/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.9801 - loss: 0.0718
+
+
+

+ 80/625 ━━━━━━━━━━━━━━━━━━━━ 3s 7ms/step - accuracy: 0.9800 - loss: 0.0747
+
+
+

+ 92/625 ━━━━━━━━━━━━━━━━━━━━ 3s 7ms/step - accuracy: 0.9797 - loss: 0.0793
+
+
+

+104/625 ━━━━━━━━━━━━━━━━━━━━ 3s 6ms/step - accuracy: 0.9795 - loss: 0.0834
+
+
+

+116/625 ━━━━━━━━━━━━━━━━━━━━ 3s 6ms/step - accuracy: 0.9793 - loss: 0.0864
+
+
+

+129/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9793 - loss: 0.0883
+
+
+

+142/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9791 - loss: 0.0900
+
+
+

+155/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9790 - loss: 0.0913
+
+
+

+167/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9789 - loss: 0.0923
+
+
+

+178/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9788 - loss: 0.0932
+
+
+

+190/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9788 - loss: 0.0938
+
+
+

+202/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9789 - loss: 0.0943
+
+
+

+214/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9789 - loss: 0.0944
+
+
+

+218/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9789 - loss: 0.0944
+
+
+

+227/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9790 - loss: 0.0945
+
+
+

+237/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9790 - loss: 0.0946
+
+
+

+242/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9790 - loss: 0.0946
+
+
+

+254/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9790 - loss: 0.0947
+
+
+

+266/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9790 - loss: 0.0948
+
+
+

+278/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9790 - loss: 0.0949
+
+
+

+289/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9790 - loss: 0.0948
+
+
+

+301/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9790 - loss: 0.0948
+
+
+

+314/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9789 - loss: 0.0947
+
+
+

+326/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9789 - loss: 0.0945
+
+
+

+338/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9790 - loss: 0.0942
+
+
+

+348/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9790 - loss: 0.0939
+
+
+

+351/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9790 - loss: 0.0938
+
+
+

+364/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9790 - loss: 0.0935
+
+
+

+376/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9791 - loss: 0.0932
+
+
+

+388/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9791 - loss: 0.0929
+
+
+

+400/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9791 - loss: 0.0926
+
+
+

+410/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9791 - loss: 0.0923
+
+
+

+415/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9791 - loss: 0.0922
+
+
+

+425/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9792 - loss: 0.0920
+
+
+

+437/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9792 - loss: 0.0916
+
+
+

+450/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9792 - loss: 0.0913
+
+
+

+463/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9793 - loss: 0.0909
+
+
+

+475/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9793 - loss: 0.0905
+
+
+

+487/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9794 - loss: 0.0900
+
+
+

+499/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9795 - loss: 0.0896
+
+
+

+512/625 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - accuracy: 0.9796 - loss: 0.0891
+
+
+

+523/625 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - accuracy: 0.9796 - loss: 0.0887
+
+
+

+524/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9796 - loss: 0.0887
+
+
+

+536/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9797 - loss: 0.0882
+
+
+

+548/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9798 - loss: 0.0878
+
+
+

+560/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9799 - loss: 0.0873
+
+
+

+565/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9799 - loss: 0.0871
+
+
+

+575/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9800 - loss: 0.0868
+
+
+

+585/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9800 - loss: 0.0864
+
+
+

+588/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9801 - loss: 0.0863
+
+
+

+600/625 ━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9801 - loss: 0.0859
+
+
+

+612/625 ━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9802 - loss: 0.0855
+
+
+

+623/625 ━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9803 - loss: 0.0852
+
+
+

+625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 6ms/step - accuracy: 0.9837 - loss: 0.0671
+
+
+
0.9836999773979187
+
+
+
+
+
+
+

Predicting#

+

Suppose we simply want to ask our neural network to predict the target for an input. We can use the predict() method to return the category array with the predictions. We can then use np.argmax() to select the most probable.

+
+
+
np.argmax(model.predict(np.array([X_test[0]])))
+
+
+
+
+
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step
+
+
+

+1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step
+
+
+
np.int64(7)
+
+
+
+
+
+
+
y_test[0]
+
+
+
+
+
array([0., 0., 0., 0., 0., 0., 0., 1., 0., 0.])
+
+
+
+
+

Now let’s loop over the test set and print out what we predict vs. the true answer for those we get wrong. We can also plot the image of the digit.

+
+
+
wrong = 0
+max_wrong = 10
+
+for n, (x, y) in enumerate(zip(X_test, y_test)):
+    try:
+        res = model.predict(np.array([x]), verbose=0)
+        if np.argmax(res) != np.argmax(y):
+            print(f"test {n}: prediction = {np.argmax(res)}, truth is {np.argmax(y)}")
+            plt.imshow(x.reshape(28, 28), cmap="gray_r")
+            plt.show()
+            wrong += 1
+            if (wrong > max_wrong-1):
+                break
+    except KeyboardInterrupt:
+        print("stopping")
+        break
+
+
+
+
+
test 8: prediction = 6, truth is 5
+
+
+../_images/227f2d3a8e3db865c48a39a8063f17dbfc53956e8dd10a2759234d6ca2e0c629.png +
test 115: prediction = 9, truth is 4
+
+
+../_images/58d940405d5b2d97a8ac4387c0747f19aceabb1b381df374961dae4b6e883716.png +
test 149: prediction = 3, truth is 2
+
+
+../_images/e89ded31bb0805eaed85d00c64bd2ea93dcb6c61745dc293d035532da31af2ac.png +
test 151: prediction = 8, truth is 9
+
+
+../_images/0e2cd18fde8a96eb62e54a21f4cddab1053d71c343dfc1c7c1df90e2eb02cf76.png +
test 247: prediction = 2, truth is 4
+
+
+../_images/95b9f0fd23894c2cbbb25bb94ff4162bea2142c17024708eb2e068cc777e852f.png +
test 321: prediction = 7, truth is 2
+
+
+../_images/ffee7b61de1ff038024f9ad240685159d4c292312da298aac782027770fecb9c.png +
test 340: prediction = 3, truth is 5
+
+
+../_images/c8c2834b4172a70240f93d1cb14ae0d552f4a26654861da536d16eea043dd641.png +
test 445: prediction = 0, truth is 6
+
+
+../_images/99aa1a1124655bc04ed0c253cede4ee4f50d860b4a8e1e8796107a753cbcfabf.png +
test 447: prediction = 9, truth is 4
+
+
+../_images/e4e9c1c1a046a645e43f47b6e48b05626dcbc0bbdccfc36aa5896cda1097ad1d.png +
test 495: prediction = 2, truth is 8
+
+
+../_images/ae7d94ffa26d5baa2e15a13dae0847ac6a63895412a6d03f86c08f8e3f328f37.png +
+
+
+
+

Experimenting#

+

There are a number of things we can play with to see how the network performance +changes:

+
    +
  • batch size

  • +
  • adding or removing hidden layers

  • +
  • changing the dropout

  • +
  • changing the activation function

  • +
+
+
+

Callbacks#

+

Keras allows for callbacks each epoch to store some information. These can allow you to, +for example, plot of the accuracy vs. epoch by adding a callback. Take a look here for some inspiration:

+

https://www.tensorflow.org/api_docs/python/tf/keras/callbacks/History

+
+
+

Going Further#

+

Convolutional neural networks are often used for image recognition, especially with larger images. They use filter to try to recognize patterns in portions of images (A tile). See this for a keras example:

+

https://www.tensorflow.org/tutorials/images/cnn

+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/11-machine-learning/keras-mnist.html.backup b/11-machine-learning/keras-mnist.html.backup new file mode 100644 index 00000000..49f65e97 --- /dev/null +++ b/11-machine-learning/keras-mnist.html.backup @@ -0,0 +1,1667 @@ + + + + + + + + + + + KERAS and MNIST — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

KERAS and MNIST#

+
+
+
import matplotlib.pyplot as plt
+import numpy as np
+
+
+
+
+

We’ll apply the ideas we just learned to a neural network that does character recognition using the MNIST database. This is a set of handwritten digits (0–9) represented as a 28×28 pixel grayscale image.

+

There are 2 datasets, the training set with 60,000 images and the test set with 10,000 images.

+
+
+
import keras
+
+
+
+
+
+

Important

+

Keras requires a backend, which can be tensorflow, pytorch, or jax.

+

By default, it will assume tensorflow.

+

This notebook has been tested with both pytorch and tensorflow.

+
+
+

Tip

+

To have keras use pytorch, set the environment variable KERAS_BACKEND as:

+
export KERAS_BACKEND="torch"
+
+
+
+

We follow the example for setting up the network: +Vict0rSch/deep_learning

+
+

Note

+

For visualization of the network, you need to have pydot installed.

+
+
+

The MNIST data#

+

The keras library can download the MNIST data directly and provides a function to give us both the training and test images and the corresponding digits. This is already in a format that Keras wants, so we don’t use the classes that we defined earlier.

+
+
+
from keras.datasets import mnist
+
+
+
+
+
+
+
(X_train, y_train), (X_test, y_test) = mnist.load_data()
+
+
+
+
+
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
+
+
+
       0/11490434 ━━━━━━━━━━━━━━━━━━━━ 0s 0s/step
+
+
+

+  696320/11490434 ━━━━━━━━━━━━━━━━━━━ 0s 0us/step
+
+
+

+10379264/11490434 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step
+
+
+

+11490434/11490434 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step
+
+
+
+
+

As before, the training set consists of 60000 digits represented as a 28x28 array (there are no color channels, so this is grayscale data). They are also integer data.

+
+
+
X_train.shape
+
+
+
+
+
(60000, 28, 28)
+
+
+
+
+
+
+
X_train.dtype
+
+
+
+
+
dtype('uint8')
+
+
+
+
+

Let’s look at the first digit and the “y” value (target) associated with it—that’s the correct answer.

+
+
+
plt.imshow(X_train[0], cmap="gray_r")
+print(y_train[0])
+# alt-text: the number 5 represented as a small grayscale image
+
+
+
+
+
5
+
+
+../_images/4567d8f9bd61f12d86168899465c03b2a4ce67b2904092490c6a2b9dc7107b30.png +
+
+
+
+

Preparing the Data#

+

The neural network takes a 1-d vector of input and will return a 1-d vector of output. We need to convert our data to this form.

+

We’ll scale the image data to fall in [0, 1) and the numerical output to be categorized as an array. Finally, we need the input data to be one-dimensional, so we fill flatten the 28x28 images into a single 784 vector.

+
+
+
X_train = X_train.astype('float32')/255
+X_test = X_test.astype('float32')/255
+
+X_train = np.reshape(X_train, (60000, 784))
+X_test = np.reshape(X_test, (10000, 784))
+
+
+
+
+
+
+
X_train[0]
+
+
+
+
+
array([0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.01176471, 0.07058824, 0.07058824,
+       0.07058824, 0.49411765, 0.53333336, 0.6862745 , 0.10196079,
+       0.6509804 , 1.        , 0.96862745, 0.49803922, 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.11764706, 0.14117648, 0.36862746, 0.6039216 ,
+       0.6666667 , 0.99215686, 0.99215686, 0.99215686, 0.99215686,
+       0.99215686, 0.88235295, 0.6745098 , 0.99215686, 0.9490196 ,
+       0.7647059 , 0.2509804 , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.19215687, 0.93333334,
+       0.99215686, 0.99215686, 0.99215686, 0.99215686, 0.99215686,
+       0.99215686, 0.99215686, 0.99215686, 0.9843137 , 0.3647059 ,
+       0.32156864, 0.32156864, 0.21960784, 0.15294118, 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.07058824, 0.85882354, 0.99215686, 0.99215686,
+       0.99215686, 0.99215686, 0.99215686, 0.7764706 , 0.7137255 ,
+       0.96862745, 0.94509804, 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.3137255 , 0.6117647 , 0.41960785, 0.99215686, 0.99215686,
+       0.8039216 , 0.04313726, 0.        , 0.16862746, 0.6039216 ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.05490196,
+       0.00392157, 0.6039216 , 0.99215686, 0.3529412 , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.54509807,
+       0.99215686, 0.74509805, 0.00784314, 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.04313726, 0.74509805, 0.99215686,
+       0.27450982, 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.13725491, 0.94509804, 0.88235295, 0.627451  ,
+       0.42352942, 0.00392157, 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.31764707, 0.9411765 , 0.99215686, 0.99215686, 0.46666667,
+       0.09803922, 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.1764706 ,
+       0.7294118 , 0.99215686, 0.99215686, 0.5882353 , 0.10588235,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.0627451 , 0.3647059 ,
+       0.9882353 , 0.99215686, 0.73333335, 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.9764706 , 0.99215686,
+       0.9764706 , 0.2509804 , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.18039216, 0.50980395,
+       0.7176471 , 0.99215686, 0.99215686, 0.8117647 , 0.00784314,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.15294118,
+       0.5803922 , 0.8980392 , 0.99215686, 0.99215686, 0.99215686,
+       0.98039216, 0.7137255 , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.09411765, 0.44705883, 0.8666667 , 0.99215686, 0.99215686,
+       0.99215686, 0.99215686, 0.7882353 , 0.30588236, 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.09019608, 0.25882354, 0.8352941 , 0.99215686,
+       0.99215686, 0.99215686, 0.99215686, 0.7764706 , 0.31764707,
+       0.00784314, 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.07058824, 0.67058825, 0.85882354,
+       0.99215686, 0.99215686, 0.99215686, 0.99215686, 0.7647059 ,
+       0.3137255 , 0.03529412, 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.21568628, 0.6745098 ,
+       0.8862745 , 0.99215686, 0.99215686, 0.99215686, 0.99215686,
+       0.95686275, 0.52156866, 0.04313726, 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.53333336, 0.99215686, 0.99215686, 0.99215686,
+       0.83137256, 0.5294118 , 0.5176471 , 0.0627451 , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        , 0.        ,
+       0.        , 0.        , 0.        , 0.        ], dtype=float32)
+
+
+
+
+

We will use categorical data. Keras includes routines to categorize data. In our case, since there are 10 possible digits, we want to put the output into 10 categories (represented by 10 neurons)

+
+
+
from keras.utils import to_categorical
+
+y_train = to_categorical(y_train, 10)
+y_test = to_categorical(y_test, 10)
+
+
+
+
+

Now let’s look at the target for the first training digit. We know from above that it was ‘5’. Here we see that there is a 1 in the index corresponding to 5 (remember we start counting at 0 in python).

+
+
+
y_train[0]
+
+
+
+
+
array([0., 0., 0., 0., 0., 1., 0., 0., 0., 0.])
+
+
+
+
+
+
+

Build the Neural Network#

+

Now we’ll build the neural network. We will have 2 hidden layers, and the number of neurons will look like:

+

784 → 500 → 300 → 10

+
+

Layers#

+

Let’s start by setting up the layers. For each layer, we tell keras the number of output neurons. It infers the number of inputs from the previous layer (with the exception of the input layer, where we need to tell it what to expect as input).

+

Properties on the layers:

+ +
+
+
from keras.models import Sequential
+from keras.layers import Dense, Dropout, Activation, Input
+
+model = Sequential()
+model.add(Input(shape=(784,)))
+model.add(Dense(500, activation="relu"))
+model.add(Dropout(0.4))
+model.add(Dense(300, activation="relu"))
+model.add(Dropout(0.4))
+model.add(Dense(10, activation="softmax"))
+
+
+
+
+
+
+

Loss function#

+

We need to specify what we want to optimize and how we are going to do it.

+

Recall: the loss (or cost) function measures how well our predictions match the expected target. +Previously we were using the sum of the squares of the error.

+

For categorical data, like we have, the “cross-entropy” metric is often used. See here for an explanation: https://jamesmccaffrey.wordpress.com/2013/11/05/why-you-should-use-cross-entropy-error-instead-of-classification-error-or-mean-squared-error-for-neural-network-classifier-training/

+
+
+

Optimizer#

+

We also need to specify an optimizer. This could be gradient descent, as we used before. Here’s a list of the optimizers supoprted by keras: https://keras.io/api/optimizers/ We’ll use RMPprop, which builds off of gradient descent and includes some momentum.

+

Finally, we need to specify a metric that is evaluated during training and testing. We’ll use "accuracy" here. This means that we’ll see the accuracy of our model reported as we are training and testing.

+

More details on these options is here: https://keras.io/api/models/model/

+
+
+
from keras.optimizers import RMSprop
+
+rms = RMSprop()
+model.compile(loss='categorical_crossentropy',
+              optimizer=rms, metrics=['accuracy'])
+
+
+
+
+
+
+

Network summary#

+

Let’s take a look at the network:

+
+
+
model.summary()
+
+
+
+
+
Model: "sequential"
+
+
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
+┃ Layer (type)                     Output Shape                  Param # ┃
+┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
+│ dense (Dense)                   │ (None, 500)            │       392,500 │
+├─────────────────────────────────┼────────────────────────┼───────────────┤
+│ dropout (Dropout)               │ (None, 500)            │             0 │
+├─────────────────────────────────┼────────────────────────┼───────────────┤
+│ dense_1 (Dense)                 │ (None, 300)            │       150,300 │
+├─────────────────────────────────┼────────────────────────┼───────────────┤
+│ dropout_1 (Dropout)             │ (None, 300)            │             0 │
+├─────────────────────────────────┼────────────────────────┼───────────────┤
+│ dense_2 (Dense)                 │ (None, 10)             │         3,010 │
+└─────────────────────────────────┴────────────────────────┴───────────────┘
+
+
 Total params: 545,810 (2.08 MB)
+
+
 Trainable params: 545,810 (2.08 MB)
+
+
 Non-trainable params: 0 (0.00 B)
+
+
+
+

We see that there are > 500k parameters that we will be training

+
+
+
+
+

Train#

+

For training, we pass in the inputs and target and the number of epochs to run and it will optimize the network by adjusting the weights between the nodes in the layers.

+

The number of epochs is the number of times the entire data set is passed forward and backward through the network. The batch size is the number of training pairs you pass through the network at a given time. You update the parameter in your model (the weights) once for each batch. This makes things more efficient and less noisy.

+
+
+
epochs = 20
+batch_size = 256
+model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size,
+          validation_data=(X_test, y_test), verbose=2)
+
+
+
+
+
Epoch 1/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.8846 - loss: 0.3774 - val_accuracy: 0.9475 - val_loss: 0.1665
+
+
+
Epoch 2/20
+
+
+
235/235 - 3s - 15ms/step - accuracy: 0.9515 - loss: 0.1611 - val_accuracy: 0.9676 - val_loss: 0.1048
+
+
+
Epoch 3/20
+
+
+
235/235 - 4s - 16ms/step - accuracy: 0.9650 - loss: 0.1192 - val_accuracy: 0.9754 - val_loss: 0.0790
+
+
+
Epoch 4/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9702 - loss: 0.0951 - val_accuracy: 0.9779 - val_loss: 0.0738
+
+
+
Epoch 5/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9747 - loss: 0.0821 - val_accuracy: 0.9785 - val_loss: 0.0727
+
+
+
Epoch 6/20
+
+
+
235/235 - 4s - 16ms/step - accuracy: 0.9776 - loss: 0.0722 - val_accuracy: 0.9806 - val_loss: 0.0655
+
+
+
Epoch 7/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9801 - loss: 0.0649 - val_accuracy: 0.9811 - val_loss: 0.0641
+
+
+
Epoch 8/20
+
+
+
235/235 - 4s - 16ms/step - accuracy: 0.9818 - loss: 0.0594 - val_accuracy: 0.9805 - val_loss: 0.0669
+
+
+
Epoch 9/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9829 - loss: 0.0541 - val_accuracy: 0.9832 - val_loss: 0.0599
+
+
+
Epoch 10/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9840 - loss: 0.0503 - val_accuracy: 0.9828 - val_loss: 0.0622
+
+
+
Epoch 11/20
+
+
+
235/235 - 4s - 16ms/step - accuracy: 0.9855 - loss: 0.0456 - val_accuracy: 0.9836 - val_loss: 0.0655
+
+
+
Epoch 12/20
+
+
+
235/235 - 4s - 16ms/step - accuracy: 0.9863 - loss: 0.0431 - val_accuracy: 0.9831 - val_loss: 0.0635
+
+
+
Epoch 13/20
+
+
+
235/235 - 4s - 16ms/step - accuracy: 0.9873 - loss: 0.0398 - val_accuracy: 0.9827 - val_loss: 0.0655
+
+
+
Epoch 14/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9881 - loss: 0.0372 - val_accuracy: 0.9830 - val_loss: 0.0631
+
+
+
Epoch 15/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9896 - loss: 0.0328 - val_accuracy: 0.9837 - val_loss: 0.0688
+
+
+
Epoch 16/20
+
+
+
235/235 - 4s - 16ms/step - accuracy: 0.9891 - loss: 0.0334 - val_accuracy: 0.9841 - val_loss: 0.0662
+
+
+
Epoch 17/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9896 - loss: 0.0315 - val_accuracy: 0.9835 - val_loss: 0.0662
+
+
+
Epoch 18/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9900 - loss: 0.0316 - val_accuracy: 0.9846 - val_loss: 0.0613
+
+
+
Epoch 19/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9905 - loss: 0.0309 - val_accuracy: 0.9852 - val_loss: 0.0575
+
+
+
Epoch 20/20
+
+
+
235/235 - 4s - 15ms/step - accuracy: 0.9912 - loss: 0.0270 - val_accuracy: 0.9837 - val_loss: 0.0671
+
+
+
<keras.src.callbacks.history.History at 0x7f0ac99327b0>
+
+
+
+
+
+

Test#

+

keras has a routine, evaluate() that can take the inputs and targets of a test data set and return the loss value and accuracy (or other defined metrics) on this data.

+

Here we see we are > 98% accurate on the test data—this is the data that the model has never seen before (and was not trained with).

+
+
+
loss_value, accuracy = model.evaluate(X_test, y_test, batch_size=16)
+print(accuracy)
+
+
+
+
+
  1/625 ━━━━━━━━━━━━━━━━━━━━ 3s 6ms/step - accuracy: 0.9375 - loss: 0.1659
+
+
+

+  5/625 ━━━━━━━━━━━━━━━━━━━━ 18s 30ms/step - accuracy: 0.9715 - loss: 0.0759
+
+
+

+ 17/625 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9793 - loss: 0.0572 
+
+
+

+ 29/625 ━━━━━━━━━━━━━━━━━━━━ 4s 8ms/step - accuracy: 0.9805 - loss: 0.0646 
+
+
+

+ 40/625 ━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.9807 - loss: 0.0663
+
+
+

+ 46/625 ━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.9807 - loss: 0.0676
+
+
+

+ 56/625 ━━━━━━━━━━━━━━━━━━━ 3s 7ms/step - accuracy: 0.9804 - loss: 0.0698
+
+
+

+ 66/625 ━━━━━━━━━━━━━━━━━━━━ 3s 7ms/step - accuracy: 0.9801 - loss: 0.0713
+
+
+

+ 69/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.9801 - loss: 0.0718
+
+
+

+ 80/625 ━━━━━━━━━━━━━━━━━━━━ 3s 7ms/step - accuracy: 0.9800 - loss: 0.0747
+
+
+

+ 92/625 ━━━━━━━━━━━━━━━━━━━━ 3s 7ms/step - accuracy: 0.9797 - loss: 0.0793
+
+
+

+104/625 ━━━━━━━━━━━━━━━━━━━━ 3s 6ms/step - accuracy: 0.9795 - loss: 0.0834
+
+
+

+116/625 ━━━━━━━━━━━━━━━━━━━━ 3s 6ms/step - accuracy: 0.9793 - loss: 0.0864
+
+
+

+129/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9793 - loss: 0.0883
+
+
+

+142/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9791 - loss: 0.0900
+
+
+

+155/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9790 - loss: 0.0913
+
+
+

+167/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9789 - loss: 0.0923
+
+
+

+178/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9788 - loss: 0.0932
+
+
+

+190/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9788 - loss: 0.0938
+
+
+

+202/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9789 - loss: 0.0943
+
+
+

+214/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9789 - loss: 0.0944
+
+
+

+218/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9789 - loss: 0.0944
+
+
+

+227/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9790 - loss: 0.0945
+
+
+

+237/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9790 - loss: 0.0946
+
+
+

+242/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9790 - loss: 0.0946
+
+
+

+254/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9790 - loss: 0.0947
+
+
+

+266/625 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.9790 - loss: 0.0948
+
+
+

+278/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9790 - loss: 0.0949
+
+
+

+289/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9790 - loss: 0.0948
+
+
+

+301/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9790 - loss: 0.0948
+
+
+

+314/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9789 - loss: 0.0947
+
+
+

+326/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9789 - loss: 0.0945
+
+
+

+338/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9790 - loss: 0.0942
+
+
+

+348/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9790 - loss: 0.0939
+
+
+

+351/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9790 - loss: 0.0938
+
+
+

+364/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9790 - loss: 0.0935
+
+
+

+376/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9791 - loss: 0.0932
+
+
+

+388/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9791 - loss: 0.0929
+
+
+

+400/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9791 - loss: 0.0926
+
+
+

+410/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9791 - loss: 0.0923
+
+
+

+415/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9791 - loss: 0.0922
+
+
+

+425/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9792 - loss: 0.0920
+
+
+

+437/625 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - accuracy: 0.9792 - loss: 0.0916
+
+
+

+450/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9792 - loss: 0.0913
+
+
+

+463/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9793 - loss: 0.0909
+
+
+

+475/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9793 - loss: 0.0905
+
+
+

+487/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9794 - loss: 0.0900
+
+
+

+499/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9795 - loss: 0.0896
+
+
+

+512/625 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - accuracy: 0.9796 - loss: 0.0891
+
+
+

+523/625 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - accuracy: 0.9796 - loss: 0.0887
+
+
+

+524/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9796 - loss: 0.0887
+
+
+

+536/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9797 - loss: 0.0882
+
+
+

+548/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9798 - loss: 0.0878
+
+
+

+560/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9799 - loss: 0.0873
+
+
+

+565/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9799 - loss: 0.0871
+
+
+

+575/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9800 - loss: 0.0868
+
+
+

+585/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9800 - loss: 0.0864
+
+
+

+588/625 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9801 - loss: 0.0863
+
+
+

+600/625 ━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9801 - loss: 0.0859
+
+
+

+612/625 ━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9802 - loss: 0.0855
+
+
+

+623/625 ━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - accuracy: 0.9803 - loss: 0.0852
+
+
+

+625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 6ms/step - accuracy: 0.9837 - loss: 0.0671
+
+
+
0.9836999773979187
+
+
+
+
+
+
+

Predicting#

+

Suppose we simply want to ask our neural network to predict the target for an input. We can use the predict() method to return the category array with the predictions. We can then use np.argmax() to select the most probable.

+
+
+
np.argmax(model.predict(np.array([X_test[0]])))
+
+
+
+
+
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step
+
+
+

+1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step
+
+
+
np.int64(7)
+
+
+
+
+
+
+
y_test[0]
+
+
+
+
+
array([0., 0., 0., 0., 0., 0., 0., 1., 0., 0.])
+
+
+
+
+

Now let’s loop over the test set and print out what we predict vs. the true answer for those we get wrong. We can also plot the image of the digit.

+
+
+
wrong = 0
+max_wrong = 10
+
+for n, (x, y) in enumerate(zip(X_test, y_test)):
+    try:
+        res = model.predict(np.array([x]), verbose=0)
+        if np.argmax(res) != np.argmax(y):
+            print(f"test {n}: prediction = {np.argmax(res)}, truth is {np.argmax(y)}")
+            plt.imshow(x.reshape(28, 28), cmap="gray_r")
+            plt.show()
+            wrong += 1
+            if (wrong > max_wrong-1):
+                break
+    except KeyboardInterrupt:
+        print("stopping")
+        break
+
+
+
+
+
test 8: prediction = 6, truth is 5
+
+
+../_images/227f2d3a8e3db865c48a39a8063f17dbfc53956e8dd10a2759234d6ca2e0c629.png +
test 115: prediction = 9, truth is 4
+
+
+../_images/58d940405d5b2d97a8ac4387c0747f19aceabb1b381df374961dae4b6e883716.png +
test 149: prediction = 3, truth is 2
+
+
+../_images/e89ded31bb0805eaed85d00c64bd2ea93dcb6c61745dc293d035532da31af2ac.png +
test 151: prediction = 8, truth is 9
+
+
+../_images/0e2cd18fde8a96eb62e54a21f4cddab1053d71c343dfc1c7c1df90e2eb02cf76.png +
test 247: prediction = 2, truth is 4
+
+
+../_images/95b9f0fd23894c2cbbb25bb94ff4162bea2142c17024708eb2e068cc777e852f.png +
test 321: prediction = 7, truth is 2
+
+
+../_images/ffee7b61de1ff038024f9ad240685159d4c292312da298aac782027770fecb9c.png +
test 340: prediction = 3, truth is 5
+
+
+../_images/c8c2834b4172a70240f93d1cb14ae0d552f4a26654861da536d16eea043dd641.png +
test 445: prediction = 0, truth is 6
+
+
+../_images/99aa1a1124655bc04ed0c253cede4ee4f50d860b4a8e1e8796107a753cbcfabf.png +
test 447: prediction = 9, truth is 4
+
+
+../_images/e4e9c1c1a046a645e43f47b6e48b05626dcbc0bbdccfc36aa5896cda1097ad1d.png +
test 495: prediction = 2, truth is 8
+
+
+../_images/ae7d94ffa26d5baa2e15a13dae0847ac6a63895412a6d03f86c08f8e3f328f37.png +
+
+
+
+

Experimenting#

+

There are a number of things we can play with to see how the network performance +changes:

+
    +
  • batch size

  • +
  • adding or removing hidden layers

  • +
  • changing the dropout

  • +
  • changing the activation function

  • +
+
+
+

Callbacks#

+

Keras allows for callbacks each epoch to store some information. These can allow you to, +for example, plot of the accuracy vs. epoch by adding a callback. Take a look here for some inspiration:

+

https://www.tensorflow.org/api_docs/python/tf/keras/callbacks/History

+
+
+

Going Further#

+

Convolutional neural networks are often used for image recognition, especially with larger images. They use filter to try to recognize patterns in portions of images (A tile). See this for a keras example:

+

https://www.tensorflow.org/tutorials/images/cnn

+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/11-machine-learning/machine-learning-basics.html b/11-machine-learning/machine-learning-basics.html new file mode 100644 index 00000000..051f7139 --- /dev/null +++ b/11-machine-learning/machine-learning-basics.html @@ -0,0 +1,806 @@ + + + + + + + + + + + Machine Learning Introduction — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Machine Learning Introduction

+ +
+ +
+
+ + + + +
+ +
+

Machine Learning Introduction#

+
+

Neural networks#

+

When we talk about machine learning, we usually mean an artifical neural network. +A neural network mimics the action of neurons in your brain.

+

Basic idea:

+
    +
  • Create a nonlinear fitting routine with free parameters

  • +
  • Train the network on data with known inputs and outputs to set the parameters

  • +
  • Use the trained network on new data to predict the outcome

  • +
+

We can think of a neural network as a map that takes a set of n parameters and returns a set of m parameters, \(\mathbb{R}^n \rightarrow \mathbb{R}^m\) and we can express this as:

+
+\[{\bf z} = {\bf A} {\bf x}\]
+

where \({\bf x} \in \mathbb{R}^n\) are the inputs, \({\bf z} \in \mathbb{R}^m\) are the outputs, and \({\bf A}\) is an \(m \times n\) matrix.

+

Our goal is to determine the matrix elements of \({\bf A}\).

+
+

Some nomeclature#

+

We can visualize a neural network as:

+

NN diagram

+
    +
  • Neural networks are divided into layers

    +
      +
    • There is always an input layer—it doesn’t do any processing

    • +
    • There is always an output layer

    • +
    +
  • +
  • Within a layer there are neurons or nodes.

    +
      +
    • For input, there will be one node for each input variable.

    • +
    +
  • +
  • Every node in the first layer connects to every node in the next layer

    +
      +
    • The weight associated with the connection can vary—these are the matrix elements.

    • +
    +
  • +
  • In this example, the processing is done in layer 2 (the output)

  • +
  • When you train the neural network, you are adjusting the weights connecting to the nodes

    +
      +
    • Some connections might have zero weight

    • +
    • This mimics nature—a single neuron can connect to several (or lots) of other neurons.

    • +
    +
  • +
+
+
+
+

Universal approximation theorem and non-linearity#

+

A neural network can be designed to approximate any function, \(f(x)\). For this to work, there must be a source of non-linearity in the network. This is applied on a layer. This is a result of the universal approximation theorem.

+

We call this an activation function and it has the form:

+
+\[\begin{split}g({\bf x}) = \left ( \begin{array}{c} g(x_0) \\ g(x_1) \\ \vdots \\ g(x_{n-1}) \end{array} \right )\end{split}\]
+

Then our neural network has the form: \({\bf z} = g({\bf A x})\)

+

We want to choose a \(g(x)\) that is differentiable. A common choice is the sigmoid function:

+
+\[g(p) = \frac{1}{1 + e^{-p}}\]
+
+
+
import numpy as np
+import matplotlib.pyplot as plt
+
+
+
+
+
+
+
def sigmoid(p):
+    return 1 / (1 + np.exp(-p))
+
+
+
+
+
+
+
p = np.linspace(-10, 10, 200)
+
+fig, ax = plt.subplots()
+
+ax.plot(p, sigmoid(p))
+
+
+
+
+
[<matplotlib.lines.Line2D at 0x7f8e6c182120>]
+
+
+../_images/da56e7aa5a4fec26cc6454ce53c70390b3cfe2458b4c55e3a75f4a8462d6dd90.png +
+
+

Notice that the sigmoid scales all output to be in \(z_i \in (0, 1)\)

+

This means that we need to ensure that our training set set is likewise mapped to \((0, 1)\), and if not, we need to transform it.

+
+
+

Basic algorithm#

+
    +
  • Training

    +
      +
    • We have \(T\) pairs \((x^k, y^k)\) for \(k = 1, \ldots, T\)

    • +
    • We require that \(g({\bf A x}^k) = {\bf y}^k\) for all \(k\)

      +

      Recall that \(g(p)\) is a scalar function that works element-by-element:

      +
      +\[z_i = g([{\bf A x}]_i) = g \left ( \sum_j A_{ij} x_j \right )\]
      +
    • +
    • Find the elements of \({\bf A}\)

      +

      This is a minimization problem, where we are minimizing:

      +
      +\[f(A_{ij}) = \| g({\bf A x}^k) - {\bf y}^k \|^2\]
      +

      We call this function the cost function.

      +

      A common minimization technique is gradient descent.

      +

      Some caveats:

      +
        +
      • When you minimize with one set of training data, there is no guarantee that your are still minimimzed with respect to the others. We do multiple epochs or passes through the training data to fix this.

      • +
      • We often don’t apply the full correction from gradient descent, but instead scale it by some \(\eta < 1\) called the learning rate.

      • +
      +
    • +
    +
  • +
  • Using the network

    +

    With the trained \({\bf A}\), we can now use the network on data we haven’t seen before

    +
  • +
+
+
+

Hidden layers#

+

We can get better performance from a neural network by adding a hidden layer:

+

hidden layers

+

The side of the hidden layer is independent of the size of the input and output layers. Now we have an additional matrix \({\bf B}\) to train. This can all be done together using the same algorithm described above. Where we now minimize:

+
+\[f(A_{ls}, B_{ij}) = \sum_{l=1}^m (z_l - y_l)^2\]
+
+\[\tilde{z}_i = g \left ( \sum_{j=1}^n B_{ij} x_j \right )\]
+
+\[z_l = g \left ( \sum_{s=1}^k A_{ls} \tilde{z}_s \right )\]
+
+
+ + + + +
+ + + + + + +
+ +
+
+
+ +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/11-machine-learning/machine-learning-libraries.html b/11-machine-learning/machine-learning-libraries.html new file mode 100644 index 00000000..d7a47b9f --- /dev/null +++ b/11-machine-learning/machine-learning-libraries.html @@ -0,0 +1,717 @@ + + + + + + + + + + + Diving Deeper into Machine Learning — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Diving Deeper into Machine Learning

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

Diving Deeper into Machine Learning#

+

We’ve focused on neural networks, using labeled data that we +can use to learn the trends in our data. This is an example +of supervised learning.

+

Broadly speaking there are +3 main approaches to machine learning

+
    +
  • Supervised learning

    +

    This uses labeled pairs (input and output) to train the model +to learn how to predict the outputs from the inputs.

    +
  • +
  • Unsupervised learning

    +

    No labeled data is provided. Instead the machine learning +algorithm seeks to find the structure on its own. The goal +is to learn patterns and features to be able to produce +new data.

    +
  • +
  • Reinforcement learning

    +

    As with unsupervised learning, no labeled data is used, +but the model is “rewarded” when it does something right, +and the model tries to maximize rewards (think: self-driving +cars).

    +
  • +
+
+

Libraries#

+

There are a number of popular libraries that implement machine learning algorithms. +Their features and performance vary quite a bit. An comparison of their +features is provided by Wikipedia: Comparison of deep learning software.

+

Some additional comparisons are provided here: https://ritza.co/articles/scikit-learn-vs-tensorflow-vs-pytorch-vs-keras/

+
    +
  • TensorFlow

    +

    This is an open source machine learning library released by Google. It has support +for CPUs, GPUs, and TPUs, +and provides all the features you need to build deep learning workflows: +TensorFlow feactures.

    +

    You can install tensorflow via:

    +
    pip install tensorflow
    +
    +
    +
    +

    Note

    +

    At the moment, tensorflow only supports python <= 3.12. So I’ll be using +pytorch instead (since I am running python 3.13).

    +
    +
  • +
  • PyTorch

    +

    This is a machine learning library build off of the Torch library, originally +developed by Facebook.

    +

    You can install pytorch via:

    +
    pip install torch
    +
    +
    +
  • +
  • scikit-learn

    +

    This is a python library developed for machine learning. It has a lot of +sample datasets that provide a nice means to learn how different methods work. +It is designed to work with NumPy and SciPy.

    +

    General recommendations on the web seem to be to use Scikit-learn to get +started with machine learning and to explore ideas, but to switch to +one of the other packages for computationally-intensive work.

    +

    You can install scikit-learn via:

    +
    pip install scikit-learn
    +
    +
    +

    Scikit-learn provides some nice sample datasets:

    +

    https://scikit-learn.org/stable/datasets/toy_dataset.html

    +

    as well as generators for +datasets:

    +

    https://scikit-learn.org/stable/datasets/sample_generators.html

    +
  • +
+

There are also tools that provide higher-level interfaces to these

+
    +
  • Keras

    +

    Keras provides a common python interface to several different machine learning +libraries, including tensorflow and torch. This hides a lot of the implementation +details and makes it easy to get started using these libraries.

    +
  • +
+
+
+

Keras#

+

We’ll focus on Keras.

+

There are a large number of examples provided by Keras:

+

https://keras.io/examples/

+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/11-machine-learning/machine-learning.html b/11-machine-learning/machine-learning.html new file mode 100644 index 00000000..66852d10 --- /dev/null +++ b/11-machine-learning/machine-learning.html @@ -0,0 +1,583 @@ + + + + + + + + + + + Machine Learning — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Machine Learning

+ +
+
+ +
+
+
+ + + + +
+ +
+

Machine Learning#

+

We’ll look at a popular library for machine learning.

+
+ + + + +
+ + + + + + +
+ +
+
+
+ +
+ + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/11-machine-learning/neural-net-basics.html b/11-machine-learning/neural-net-basics.html new file mode 100644 index 00000000..0f78740c --- /dev/null +++ b/11-machine-learning/neural-net-basics.html @@ -0,0 +1,781 @@ + + + + + + + + + + + Artificial Neural Network Basics — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Artificial Neural Network Basics

+ +
+ +
+
+ + + + +
+ +
+

Artificial Neural Network Basics#

+
+

Neural networks#

+

An artifical +neural +network +mimics the action of neurons in your brain to form connections +between nodes (neurons) that link the input to the output.

+
+

Note

+

We’ll loosely +follow the notation from Computational Methods for Physics by +Franklin.

+
+

Basic idea:

+
    +
  • Create a nonlinear fitting routine with free parameters

  • +
  • Train the network on data with known inputs and outputs to set the parameters

  • +
  • Use the trained network on new data to predict the outcome

  • +
+

We can think of a neural network as a map that takes a set of +\(N_\mathrm{in}\) parameters and returns a set of \(N_\mathrm{out}\) +parameters, which we can express this as:

+
+\[{\bf z} = {\bf A} {\bf x}\]
+

where

+
+\[{\bf x} = (x_1, x_2, \ldots, x_{N_\mathrm{in}})\]
+

are the inputs,

+
+\[{\bf z} = (z_1, z_2, \ldots, z_{N_\mathrm{out}})\]
+

are the outputs, and +\({\bf A}\) is an \(N_\mathrm{out} \times N_\mathrm{in}\) matrix.

+

Our goal is to determine the matrix elements of \({\bf A}\).

+
+
+

Nomenclature#

+

We can visualize a neural network as:

+

neural network diagram showing nodes in layers linked

+
    +
  • Neural networks are divided into layers

    +
      +
    • There is always an input layer—it doesn’t do any processing.

    • +
    • There is always an output layer.

    • +
    +
  • +
  • Within a layer there are neurons or nodes.

    +
      +
    • For input, there will be one node for each input variable. In this figure, +there are 3 nodes on the input layer.

    • +
    • The output layer will have as many nodes are needed to convey the answer +we are seeking from the network. In this case, there are 2 nodes on the +output layer.

    • +
    +
  • +
  • Every node in the first layer connects to every node in the next layer

    +
      +
    • The weight associated with the connection can vary—these are the matrix elements.

      +
      +

      Note

      +

      This is called a dense layer. There are alternate types of layers +we can explore where the nodes are connected differently.

      +
      +
    • +
    +
  • +
  • In this example, the processing is done in layer 2 (the output)

  • +
  • When you train the neural network, you are adjusting the weights connecting to the nodes

    +
      +
    • Some connections might have zero weight

    • +
    • This mimics nature—a single neuron can connect to several (or lots) of other neurons.

    • +
    +
  • +
+
+
+

Universal approximation theorem#

+

A neural network can be designed to approximate any function, \(f(x)\). For this to work, there must be a source of non-linearity in the network—this is a result of the universal approximation theorem.

+

We use a nonlinear activation function that is applied in a layer. It has +the form:

+
+\[\begin{split}g({\bf v}) = \left ( \begin{array}{c} g(v_0) \\ g(v_1) \\ \vdots \\ g(v_{n-1}) \end{array} \right )\end{split}\]
+
+

Note

+

The activation function, \(g({\bf v})\) works element-by-element on the vector \({\bf v}\).

+
+

Then our neural network has the form: \({\bf z} = g({\bf A x})\)

+

We want to choose a function \(g(\xi)\) that is differentiable. A common choice is the sigmoid function:

+
+\[g(\xi) = \frac{1}{1 + e^{-\xi}}\]
+
+a plot of the sigmoid function +
+

Fig. 1 The sigmoid function#

+
+
+
+

Note

+

There are many choices for the activation function which have +different properties. Often the choice of activation function will be empirical, by experimenting with the +performance of the network.

+
+
+
+

Basic algorithm#

+

We’ll consider the case where we have training data—a set of inputs, \({\bf x}^k\), +together with the expected output (answer), \({\bf y}^k\). These training pairs +allow us to constrain the output of the network and train the weights.

+
    +
  • Training

    +
      +
    • Loop over the \(T\) pairs \(({\bf x}^k, {\bf y}^k)\) for \(k = 1, \ldots, T\)

      +
        +
      • Predict the output for \({\bf x}^k\) as:

        +
        +\[z_i = g([{\bf A x}^k]_i) = g \left ( \sum_{j=1}^{N_\mathrm{in}} A_{ij} x^k_j \right )\]
        +
      • +
      • Constrain that \({\bf z} = {\bf y}^k\).

        +

        This is a minimization problem, where we are minimizing:

        +
        +\[\begin{align*} +\mathcal{L}(A_{ij}) &= \| g({\bf A x}^k) - {\bf y}^k \|^2 \\ + &= \sum_{i=1}^{N_\mathrm{out}} \left [ g\left (\sum_{j=1}^{N_\mathrm{in}} A_{ij} x^k_j \right ) - y^k_i \right ]^2 +\end{align*}\]
        +

        We call this function, \(\mathcal{L}\), the cost function or loss function.

        +
        +

        Note

        +

        This is called the mean square error loss function, and is one possible choice for \(\mathcal{L}(A_{ij})\), but many others exist.

        +
        +
      • +
      • Update the matrix \({\bf A}\) based on the training pair \(({\bf x}^k, {\bf y^{k}})\).

      • +
      +
    • +
    +
  • +
  • Using the network

    +

    With the trained \({\bf A}\), we can now use the network on data we haven’t seen before, \(\boldsymbol \chi\):

    +
    +\[z_i = g([{\bf A {\boldsymbol \chi}}^k]_i) = g \left ( \sum_{j=1}^{N_\mathrm{in}} A_{ij} \chi^k_j \right )\]
    +
  • +
+

There are a lot of details that we still need to figure out involving the training and minimization. +We’ll start with minimization: a common minimization technique used with +neural networks is gradient descent.

+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/11-machine-learning/neural-net-derivation.html b/11-machine-learning/neural-net-derivation.html new file mode 100644 index 00000000..e8b06618 --- /dev/null +++ b/11-machine-learning/neural-net-derivation.html @@ -0,0 +1,716 @@ + + + + + + + + + + + Deriving the Learning Correction — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Deriving the Learning Correction

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

Deriving the Learning Correction#

+

For gradient descent, we need to derive the update to the matrix +\({\bf A}\) based on training on a set of our data, \(({\bf x}^k, {\bf y}^k)\).

+
+

Important

+

The derivation we do here is specific to our choice of loss function, \(\mathcal{L}(A_{ij})\) +and activation function, \(g(\xi)\).

+
+

Let’s start with our cost function:

+
+\[\mathcal{L}(A_{ij}) = \sum_{i=1}^{N_\mathrm{out}} (z_i - y_i^k)^2 = \sum_{i=1}^{N_\mathrm{out}} + \Biggl [ g\biggl (\underbrace{\sum_{j=1}^{N_\mathrm{in}} A_{ij} x^k_j}_{\equiv \alpha_i} \biggr ) - y^k_i \Biggr ]^2\]
+

where we’ll refer to the product \(\boldsymbol{\alpha} \equiv {\bf +Ax}\) to help simplify notation. This means that \({\bf z} = g(\boldsymbol{\alpha})\).

+

We can compute the derivative with respect to a single matrix +element, \(A_{pq}\) by applying the chain rule:

+
+\[\frac{\partial \mathcal{L}}{\partial A_{pq}} = + 2 \sum_{i=1}^{N_\mathrm{out}} (z_i - y^k_i) \left . \frac{\partial g}{\partial \xi} \right |_{\xi=\alpha_i} \frac{\partial \alpha_i}{\partial A_{pq}}\]
+

with

+
+\[\frac{\partial \alpha_i}{\partial A_{pq}} = \sum_{j=1}^{N_\mathrm{in}} \frac{\partial A_{ij}}{\partial A_{pq}} x^k_j = \sum_{j=1}^{N_\mathrm{in}} \delta_{ip} \delta_{jq} x^k_j = \delta_{ip} x^k_q\]
+

and for \(g(\xi)\), we will assume the sigmoid function,so

+
+\[\frac{\partial g}{\partial \xi} + = \frac{\partial}{\partial \xi} \frac{1}{1 + e^{-\xi}} + =- (1 + e^{-\xi})^{-2} (- e^{-\xi}) + = g(\xi) \frac{e^{-\xi}}{1+ e^{-\xi}} = g(\xi) (1 - g(\xi))\]
+

which gives us:

+
+\[\begin{align*} +\frac{\partial \mathcal{L}}{\partial A_{pq}} &= 2 \sum_{i=1}^{N_\mathrm{out}} + (z_i - y^k_i) z_i (1 - z_i) \delta_{ip} x^k_q \\ + &= 2 (z_p - y^k_p) z_p (1- z_p) x^k_q +\end{align*}\]
+

where we used the fact that the \(\delta_{ip}\) means that only a single term contributes to the sum.

+
+

Note

+

Observe that:

+
    +
  • \(e_p^k \equiv (z_p - y_p^k)\) is the error on the output layer, +and the correction is proportional to the error (as we would +expect).

  • +
  • The \(k\) superscripts here remind us that this is the result of +only a single pair of data from the training set.

  • +
+
+

Now \({\bf z}\) and \({\bf y}^k\) are all vectors of size \(N_\mathrm{out} \times 1\) and \({\bf x}^k\) is a vector of size \(N_\mathrm{in} \times 1\), so we can write this expression for the matrix as a whole as:

+
+\[\frac{\partial \mathcal{L}}{\partial {\bf A}} = 2 ({\bf z} - {\bf y}^k) \circ {\bf z} \circ (1 - {\bf z}) \cdot ({\bf x}^k)^\intercal\]
+

where the operator \(\circ\) represents element-by-element multiplication (the Hadamard product).

+
+

Performing the update#

+

We could do the update like we just saw with our gradient descent +example: take a single data point, \(({\bf x}^k, {\bf y}^k)\) and +do the full minimization, continually estimating the correction, +\(\partial \mathcal{L}/\partial {\bf A}\) and updating \({\bf A}\) until we +reach a minimum. The problem with this is that \(({\bf x}^k, {\bf y}^k)\) is only one point in our training data, and there is no +guarantee that if we minimize completely with point \(k\) that we will +also be a minimum with point \(k+1\).

+

Instead we take multiple passes through the training data (called epochs) and apply only a single push in the direction that gradient +descent suggests, scaled by a learning rate \(\eta\).

+

The overall minimization appears as:

+
+
+
+Minimization
+
    +
  • Loop over epochs

    +
      +
    • Loop over the training data, \(\{ ({\bf x}^0, {\bf y}^0), ({\bf x}^1, {\bf y}^1), \ldots \}\). We’ll refer to the current training +pair as \(({\bf x}^k, {\bf y}^k)\)

      +
        +
      • Propagate \({\bf x}^k\) through the network, getting the output +\({\bf z} = g({\bf A x}^k)\)

      • +
      • Compute the error on the output layer, \({\bf e}^k = {\bf z} - {\bf y}^k\)

      • +
      • Update the matrix \({\bf A}\) according to:

        +
        +\[{\bf A} \leftarrow {\bf A} - 2 \,\eta\, {\bf e}^k \circ {\bf z} \circ (1 - {\bf z}) \cdot ({\bf x}^k)^\intercal\]
        +
      • +
      +
    • +
    +
  • +
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/11-machine-learning/neural-net-hidden.html b/11-machine-learning/neural-net-hidden.html new file mode 100644 index 00000000..85aed9b1 --- /dev/null +++ b/11-machine-learning/neural-net-hidden.html @@ -0,0 +1,726 @@ + + + + + + + + + + + Hidden Layers — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Hidden Layers

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

Hidden Layers#

+

We can get better performance from a neural network by adding a hidden layer:

+

neutron network diagram showing a single hidden layer

+

The size of the hidden layer is independent of the size of the input and output +layers. In this case, we have a hidden layer that is larger +than either the input or output layers.

+

Now we have an additional matrix \({\bf B}\) to train. The matrix sizes are:

+
    +
  • \({\bf A}\) : \(N_\mathrm{out} \times N_\mathrm{hidden}\)

  • +
  • \({\bf B}\) : \(N_\mathrm{hidden} \times N_\mathrm{in}\)

  • +
+
+

Note

+

Neglecting the activation functions, the action of the network +is to do \({\bf z} = {\bf A B x}\) which has size \(N_\mathrm{out}\).

+
+

The derivation of the corrections to matrices \({\bf A}\) and \({\bf B}\) can be done +via the chain rule.

+
+

Note

+

We’ll consider the case of a single hidden layer, but the derivation we +do here generalizes to multiple hidden layers.

+
+
+(5)#\[\begin{equation} +\mathcal{L}(A_{lm}, B_{ij}) = \sum_{l=1}^{N_\mathrm{out}} (z_l - y^k_l)^2 +\end{equation} \]
+
+\[\tilde{z}_i = g \biggl ( \underbrace{\sum_{j=1}^{N_\mathrm{in}} B_{ij} x^k_j}_{\equiv \beta_i} \biggr )\]
+
+\[z_l = g \biggl ( \underbrace{\sum_{m=1}^{N_\mathrm{hidden}} A_{lm} \tilde{z}_m}_{\equiv \alpha_l} \biggr )\]
+

Note that we are assuming here that the same activation function, \(g(\xi)\) +is used on each layer.

+
+

Updates to \({\bf A}\)#

+

Matrix \({\bf A}\) is trained based on the output layer, we know the error there +directly, \({\bf e}^k = {\bf z} - {\bf y}^k\). As a result, we can just use +the result that we got for a single layer, but now the input is \(\tilde{\bf z}\) +instead of \({\bf x}\):

+
+\[\frac{\partial \mathcal{L}}{\partial {\bf A}} = 2 {\bf e}^k \circ {\bf z} \circ (1 - {\bf z}) \cdot \tilde{\bf z}^\intercal\]
+
+
+

Updates to \({\bf B}\)#

+

To find the corrections to matrix \({\bf B}\), we essentially need to know what the +error is on the hidden layer. But we only know the error on the output layer, so +by applying the chainrule on our cost function, we will work out this correction, +and in the process see how the error on the output layer informs the error on the +hidden layer—a process called backpropagation.

+

Let’s start with our cost function:

+
+\[\begin{align*} +\mathcal{L}(A_{lm}, B_{ij}) &= \sum_{l=1}^{N_\mathrm{out}} (z_l - y^k_l)^2 \\ + &= \sum_{l=1}^{N_\mathrm{out}} \Biggl [ g \biggl ( \sum_{m=1}^{N_\mathrm{hidden}} A_{lm} \tilde{z}_m \biggr ) - y_l^k \Biggr ]^2 \\ + &= \sum_{l=1}^{N_\mathrm{out}} \Biggl [ g \biggl ( \sum_{m=1}^{N_\mathrm{hidden}} A_{lm} \,g \biggl ( \sum_{j=1}^{N_\mathrm{in}} B_{mj} x_j^k \biggr ) \biggr ) - y_l^k \Biggr ]^2 +\end{align*} \]
+

Differentiating with respect to an element in matrix \({\bf B}\), we apply the chain rule over and over, +giving:

+
+\[\frac{\partial \mathcal{L}}{\partial B_{pq}} = 2 \sum_{l=1}^{N_\mathrm{out}} (z_l - y_l^k) + \left .\frac{\partial g}{\partial \xi} \right |_{\xi = \alpha_l} + \sum_{m=1}^{N_\mathrm{hidden}} A_{lm}\, \left . \frac{\partial g}{\partial \xi} \right |_{\xi = \beta_m} + \sum_{j=1}^{N_\mathrm{in}} \frac{\partial B_{mj}}{\partial B_{pq}} x_j^k \]
+

Now we have 3 derivatives left, which are straightforward:

+
+\[\left .\frac{\partial g}{\partial \xi} \right |_{\xi = \alpha_l} = g(\alpha_l)\left [ 1 - g(\alpha_l)\right ] + = z_l (1 - z_l)\]
+
+\[\left .\frac{\partial g}{\partial \xi} \right |_{\xi = \beta_m} = g(\beta_m)\left [ 1 - g(\beta_m)\right ] + = \tilde{z}_m (1 - \tilde{z}_m)\]
+
+\[\frac{\partial B_{mj}}{\partial B_{pq}} = \delta_{mp} \delta_{jq}\]
+

Inserting these dervatives and using the \(\delta\)’s, we are left with:

+
+\[\frac{\partial \mathcal{L}}{\partial B_{pq}} = 2 \sum_{l=1}^{N_\mathrm{out}} + \underbrace{(z_l - y_l^k)}_{ = e_l^k} z_l (1 - z_l) A_{lp} \tilde{z}_p (1 - \tilde{z}_p) x^k_q\]
+

Now, that remaining sum is contracting on the first of the indices of +the matrix \({\bf A}\), indicating a matrix vector product involving +\({\bf A}^\intercal\). This allows us to define the error backpropagated to the hidden layer:

+
+\[\tilde{e}_p^k = \sum_{l=1}^{N_\mathrm{out}} e_l^k z_l (1 - z_l) A_{lp} + = \left [ {\bf A}^\intercal \cdot ({\bf e}^k \circ {\bf z} \circ (1 - {\bf z})) \right ]_p\]
+

and we can write

+
+\[\frac{\partial \mathcal{L}}{\partial {\bf B}} = 2 \tilde{\bf e}^k \circ \tilde{\bf z} \circ (1 - \tilde{\bf z}) \cdot ({\bf x}^k)^\intercal\]
+

Notice the symmetry in the update of each matrix:

+
+\[\begin{align*} +\frac{\partial \mathcal{L}}{\partial {\bf A}} &= 2 {\bf e}^k \circ {\bf z} \circ (1 - {\bf z}) \cdot \tilde{\bf z}^\intercal \\ +\frac{\partial \mathcal{L}}{\partial {\bf B}} &= 2 \tilde{\bf e}^k \circ \tilde{\bf z} \circ (1 - \tilde{\bf z}) \cdot ({\bf x}^k)^\intercal +\end{align*}\]
+

Adding additional hidden layers would continue the trend, with each hidden layer’s matrix update depending +on the error backpropagated to that layer.

+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/11-machine-learning/neural-net-improvements.html b/11-machine-learning/neural-net-improvements.html new file mode 100644 index 00000000..3cde5e0b --- /dev/null +++ b/11-machine-learning/neural-net-improvements.html @@ -0,0 +1,741 @@ + + + + + + + + + + + Improvements — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

Improvements#

+

There are many ways we could improve the performance of our network, but these will add +a lot of complexity to the simple class that we wrote. Fortunately there are a lot of +machine learning libraries that provide these features, and work efficiently, so for +real applications we would want to use one of those libraries (we’ll explore these next).

+
+

Batching#

+

Right now, we did our training as:

+
    +
  • Loop over the \(T\) pairs \(({\bf x}^k, {\bf y}^k)\) for \(k = 1, \ldots, T\)

    +
      +
    • Propagate \(({\bf x}^k, {\bf y}^k)\) through the network

    • +
    • Compute the corrections \(\partial \mathcal{L}/\partial {\bf A}\), \(\partial \mathcal{L}/\partial {\bf B}\)

    • +
    • Update the matrices:

      +
      +\[{\bf A} \leftarrow {\bf A} - \eta \frac{\partial \mathcal{L}}{\partial {\bf A}}\]
      +
      +\[{\bf B} \leftarrow {\bf B} - \eta \frac{\partial \mathcal{L}}{\partial {\bf B}}\]
      +
    • +
    +
  • +
+

In this manner, each training pair sees slightly different +matrices \({\bf A}\) and \({\bf B}\), as each previous pair +updates it immediately.

+

We could instead divide our training set into \(N\) batches, +each with \(\tau = T/N\) training pairs and do our update as:

+
    +
  • Loop over \(N\) batches

    +
      +
    • Loop over the \(\tau\) pairs \(({\bf x}^k, {\bf y}^k)\) for \(k = 1, \ldots, \tau\) in the current batch

      +
        +
      • Propagate \(({\bf x}^k, {\bf y}^k)\) through the network

      • +
      • Compute the gradients \(\partial \mathcal{L}/\partial {\bf A}^k\), \(\partial \mathcal{L}/\partial {\bf B}^k\) from the current pair

      • +
      • Accumulate the gradients:

        +
        +\[\frac{\partial \mathcal{L}}{\partial {\bf A}} = \frac{\partial \mathcal{L}}{\partial {\bf A}} + \frac{\partial \mathcal{L}}{\partial {\bf A}^k}\]
        +
        +\[\frac{\partial \mathcal{L}}{\partial {\bf B}} = \frac{\partial \mathcal{L}}{\partial {\bf B}} + \frac{\partial \mathcal{L}}{\partial {\bf B}^k}\]
        +
      • +
      +
    • +
    • Apply a single update to the matrices for this batch:

      +
      +\[{\bf A} \leftarrow {\bf A} - \frac{\eta}{\tau} \frac{\partial \mathcal{L}}{\partial {\bf A}}\]
      +
      +\[{\bf B} \leftarrow {\bf B} - \frac{\eta}{\tau} \frac{\partial \mathcal{L}}{\partial {\bf B}}\]
      +
    • +
    +
  • +
+
+

Note

+

We normalize the accumulated gradients by the batch size, \(\tau\), which means that +we are applying the average gradient over the batch.

+
+

The advantage of this is that the \(\tau\) trainings in a batch +can all be done in parallel now, spread across many CPU cores +or GPU cores. This greatly accelerates the training time.

+
+
+

Different activation or cost functions#

+

We used a simple cost function: the sum of the square of the errors. This is analogous to the \(L_2\) norm we discussed previously. But there are a lot of other cost functions +we could explore. Changing the cost function will require +us to recompute our derivatives.

+

Likewise, there are a wide number of activation functions, +some of which are not differentiable. The choice of activation +function can depend on what type of data you are using. You +might also want to use a different activation function +on each layer. Again, this would require us to redo +our derivatives.

+
+
+

Use a different minimization technique#

+

We only explored gradient descent. But there are improvements +to this (like momentum that we mentioned previously) as well +as alternate minimization techniques we could use (some of +which don’t need the gradient at all).

+
+
+

Different types of layers / connections#

+

We only considered a dense network: every node on one +layer was connected to every node on the adjacent layer. +But there are alternatives.

+

For example, a convolutional neural network performs a convolution on a layer with some kernel. This +helps identifying features.

+
+
+

More hidden layers#

+

There is no restriction on the number of hidden layers we +can use. Each additional hidden layer means an additional +matrix is added to our network. For our code, we’d simply need to backpropagate +the error to each hidden layer and compute the update to +the new matrix.

+
+
+

Auto-differentiation libraries#

+

At some point, with all of these options, doing all of the +differentiation / chain-rule by hand becomes burdensome and +prone to errors. For this reason, libraries often use +automatic differentiation libraries, like JAX which can take +the derivatives of our python functions themselves.

+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/12-extensions/extensions-example.html b/12-extensions/extensions-example.html new file mode 100644 index 00000000..ed09258d --- /dev/null +++ b/12-extensions/extensions-example.html @@ -0,0 +1,1364 @@ + + + + + + + + + + + Example Extension — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + +
+ +
+

Example Extension#

+

Let’s rewrite our Mandelbrot generator using different languages +to see how the performance differs.

+

Recall the Mandelbrot set is defined as the set such that \(z_{k+1} = z_k^2 + c\) +remains bounded, defined as \(|z_{k+1}| \le 2\), where \(c\) is a complex number, +\(c = x + iy\), in the complex plane, and \(z_0 = 0\) is the starting condition.

+

We’ll do a fixed number of iterations, and store the iteration for which \(|z_{k+1}|\) +first becomes larger than 2.

+
+

NumPy array syntax#

+

Here’s an example of a python implementation using NumPy array operations:

+
import numpy as np
+
+def mandelbrot(N,
+               xmin=-2.0, xmax=2.0,
+               ymin=-2.0, ymax=2.0,
+               max_iter=10):
+
+    x = np.linspace(xmin, xmax, N)
+    y = np.linspace(ymin, ymax, N)
+
+    xv, yv = np.meshgrid(x, y, indexing="ij")
+
+    c = xv + 1j * yv
+
+    z = np.zeros((N, N), dtype=np.complex128)
+
+    m = np.zeros((N, N), dtype=np.int32)
+
+    for i in range(1, max_iter+1):
+        z[m == 0] = z[m == 0]**2 + c[m == 0]
+
+        m[np.logical_and(np.abs(z) > 2, m == 0)] = i
+
+    return m
+
+
+

We can test this as:

+
import matplotlib.pyplot as plt
+import numpy as np
+
+import mandel
+
+import time
+
+start = time.time()
+
+xmin = -2.5
+xmax = 1.5
+ymin = -2.0
+ymax = 2.0
+
+m = mandel.mandelbrot(1024, xmin, xmax, ymin, ymax, max_iter=50)
+
+print(f"execution time = {time.time() - start}\n")
+
+fig, ax = plt.subplots()
+ax.imshow(np.transpose(m % 16), origin="lower",
+          extent=[xmin, xmax, ymin, ymax], cmap="viridis")
+
+fig.tight_layout()
+fig.savefig("test.png")
+
+
+

Here’s the resulting image

+An image of the Mandelbrot set +
+
+

Python with explicit loops#

+

Here’s a version where the loops are explicitly written out in python:

+
import numpy as np
+
+def mandelbrot(N,
+               xmin=-2.0, xmax=2.0,
+               ymin=-2.0, ymax=2.0,
+               max_iter=10):
+
+    x = np.linspace(xmin, xmax, N)
+    y = np.linspace(ymin, ymax, N)
+
+    c = np.zeros((N, N), dtype=np.complex128)
+
+    for i in range(N):
+        for j in range(N):
+            c[i, j] = x[i] + 1j * y[j]
+
+    z = np.zeros((N, N), dtype=np.complex128)
+
+    # note: we need to use a numba type here
+    m = np.zeros((N, N), dtype=np.int32)
+
+    for n in range(1, max_iter+1):
+
+        for i in range(N):
+            for j in range(N):
+                if m[i, j] == 0:
+                    z[i, j] = z[i, j] * z[i, j] + c[i, j]
+
+                    if np.abs(z[i,j]) > 2:
+                        m[i, j] = n
+
+    return m
+
+
+

This can be run using the same driver as the numpy vectorized version.

+
+
+

Numba version#

+

We can install Numba simply by doing:

+
pip install numba
+
+
+

To get a Numba optimized version of the python with explicit loops we just add:

+
from numba import njit
+
+
+

and then right before the function definition:

+
@njit()
+
+
+

Here’s the full code:

+
import numpy as np
+
+from numba import njit
+
+
+@njit(nopython=True)
+def mandelbrot(N,
+               xmin=-2.0, xmax=2.0,
+               ymin=-2.0, ymax=2.0,
+               max_iter=10):
+
+    x = np.linspace(xmin, xmax, N)
+    y = np.linspace(ymin, ymax, N)
+
+    c = np.zeros((N, N), dtype=np.complex128)
+
+    for i in range(N):
+        for j in range(N):
+            c[i, j] = x[i] + 1j * y[j]
+
+    z = np.zeros((N, N), dtype=np.complex128)
+
+    # note: we need to use a numba type here
+    m = np.zeros((N, N), dtype=np.int32)
+
+    for n in range(1, max_iter+1):
+
+        for i in range(N):
+            for j in range(N):
+                if m[i, j] == 0:
+                    z[i, j] = z[i, j] * z[i, j] + c[i, j]
+
+                    if np.abs(z[i, j]) > 2:
+                        m[i, j] = n
+
+    return m
+
+
+

Again, this uses the same driver.

+
+

Note

+

We didn’t need to do anything special to compile the numba code. +This is done for us when we first encounter it.

+
+
+

Tip

+

We run it twice in our driver, since the first call will have the overhead +of the JIT compilation.

+
+
import matplotlib.pyplot as plt
+import numpy as np
+
+import mandel
+
+import time
+
+start = time.time()
+
+xmin = -2.5
+xmax = 1.5
+ymin = -2.0
+ymax = 2.0
+
+m = mandel.mandelbrot(1024, xmin, xmax, ymin, ymax, max_iter=50)
+
+print(f"execution time (including jit) = {time.time() - start}\n")
+
+start = time.time()
+
+m = mandel.mandelbrot(1024, xmin, xmax, ymin, ymax, max_iter=50)
+
+print(f"second run time = {time.time() - start}\n")
+
+fig, ax = plt.subplots()
+ax.imshow(np.transpose(m % 16), origin="lower",
+          extent=[xmin, xmax, ymin, ymax], cmap="viridis")
+
+fig.tight_layout()
+fig.savefig("test.png")
+
+
+
+

Tip

+

We can get even better performance if we let numba do things in parallel, with

+
@njit(parallel=True)
+
+
+
+
+
+

Cython version#

+

We can install Cython by doing

+
pip install Cython
+
+
+

For Cython, we mainly need to specify the datatypes of the different +variables. We use the extension .pyx for a cython file.

+

Here’s the full code:

+
import cython
+import numpy as np
+cimport numpy as np
+
+@cython.boundscheck(False)
+@cython.wraparound(False)
+@cython.returns(np.ndarray)
+def mandelbrot(int N,
+               double xmin=-2.0, double xmax=2.0,
+               double ymin=-2.0, double ymax=2.0,
+               int max_iter=10):
+
+    cdef np.ndarray[np.float64_t, ndim=1] x = np.linspace(xmin, xmax, N, dtype=np.float64)
+    cdef np.ndarray[np.float64_t, ndim=1] y = np.linspace(ymin, ymax, N, dtype=np.float64)
+
+    cdef np.ndarray[np.complex128_t, ndim=2] c = np.zeros((N, N), dtype=np.complex128)
+
+    cdef unsigned int i, j
+    for i in range(N):
+        for j in range(N):
+            c[i, j] = x[i] + 1j * y[j]
+
+    cdef np.ndarray[np.complex128_t, ndim=2] z = np.zeros((N, N), dtype=np.complex128)
+
+    cdef np.ndarray[np.int32_t, ndim=2] m = np.zeros((N, N), dtype=np.int32)
+
+    cdef unsigned int n
+    for n in range(1, max_iter+1):
+
+        for i in range(N):
+            for j in range(N):
+                if m[i, j] == 0:
+                    z[i, j] = z[i, j] * z[i, j] + c[i, j]
+
+                    if abs(z[i,j]) > 2:
+                        m[i, j] = n
+
+    return m
+
+
+

To build it, we can use a setup.py file:

+
from setuptools import setup
+from Cython.Build import cythonize
+
+setup(name="mandel",
+      ext_modules=cythonize("mandel.pyx"),
+      zip_safe=False)
+
+
+

and make the extension as:

+
python setup.py build_ext --inplace
+
+
+
+

Note

+

This build process will likely change in the near future, as +the community is transitioning away from setup.py, but the +docs don’t seem to be fully up to date on the new way to build.

+
+
+

Tip

+

To help understand where the slow parts of your Cython code are, you +can do

+
cythonize -a mandel.pyx
+
+
+

This will produce an HTML file with the parts of the code that interact +with python highlighted. (Make sure there are no .c files hanging around). +These highlighted lines are places you should try to optimize.

+

For our example, if we do

+
np.abs(z[i,j])
+
+
+

instead of

+
abs(z[i,j])
+
+
+

we get a dramatic slowdown!

+

Thanks to Eric Johnson for pointing this out.

+
+
+
+

Fortran implementation#

+

If we want to write the code in Fortran, we need to compile it into a shared +object library that python can import.
+This is where f2py comes in—it is part of the numpy project, so you probably +already have it installed.

+
+

Note

+

Support for this is in transition at the moment. The old official way to do this +was to use distutils, but this is removed in python 3.12.

+

Instead, we will use the meson build system.

+
+

We need to install meson and ninja:

+
pip install meson ninja
+
+
+

Here’s our Fortran implementation for the Mandelbrot generator:

+
subroutine mandelbrot(N, xmin, xmax, ymin, ymax, max_iter, m)
+
+  implicit none
+
+  integer, intent(in) :: N
+  double precision, intent(in) :: xmin, xmax, ymin, ymax
+  integer, intent(in) :: max_iter
+
+  integer, intent(out) :: m(N, N)
+
+  double complex, parameter :: i_unit = (0, 1)
+
+!f2py depend(N) :: m
+!f2py intent(out) :: m
+
+  integer :: i, j, niter
+  double precision :: x(N), y(N)
+  double precision :: dx, dy
+  double complex, allocatable :: c(:, :)
+  double complex, allocatable :: z(:, :)
+
+  ! compute coordinates
+  dx = (xmax - xmin) / (N - 1)
+  dy = (ymax - ymin) / (N - 1)
+
+  do i = 1, N
+     x(i) = xmin + (i-1) * dx
+     y(i) = ymin + (i-1) * dy
+  enddo
+
+  allocate(c(N, N))
+
+  do j = 1, N
+     do i = 1, N
+        c(i, j) = x(i) + i_unit * y(j)
+     enddo
+  enddo
+
+  m(:, :) = 0
+
+  allocate(z(N, N))
+  z(:, :) = 0.0
+
+  do niter = 1, max_iter
+
+     do j = 1, N
+        do i = 1, N
+
+           if (m(i, j) == 0) then
+              z(i, j) = z(i, j) * z(i, j) + c(i, j)
+
+              if (abs(z(i,j)) > 2) then
+                 m(i, j) = niter
+              endif
+           endif
+
+        enddo
+     enddo
+
+  enddo
+
+end subroutine mandelbrot
+
+
+

To build the extension, we can do:

+
f2py -c mandel.f90 -m mandel_f2py
+
+
+
+

Note

+

If the f2py command-line tool is not available, you can try running it as a module instead:

+
python -m numpy.f2py -c mandel.f90 -m mandel_f2py
+
+
+
+
+

Tip

+

The build doesn’t show you the compilation commands used to make the library. But if you look +at the output, it will say something like:

+
The Meson build system
+Version: 1.4.0
+Source dir: /tmp/tmp0sbl86zt
+Build dir: /tmp/tmp0sbl86zt/bbdir
+Build type: native build
+Project name: mandel_f2py
+
+
+

If you then look in the build directory, there will be a file compile_commands.json that +lists the commands that meson + f2py use to compile the extension. In our case, +it is using the optimization flag -O3.

+
+

This will create a library (on my machine, it is called mandel_f2py.cpython-312-x86_64-linux-gnu.so) +which we can import as import mandel_f2py.

+

Here’s a driver:

+
import matplotlib.pyplot as plt
+import numpy as np
+
+import mandel_f2py
+
+import time
+
+start = time.time()
+
+xmin = -2.5
+xmax = 1.5
+ymin = -2.0
+ymax = 2.0
+
+max_iter = 50
+
+m = mandel_f2py.mandelbrot(1024, xmin, xmax, ymin, ymax, max_iter)
+
+print(f"execution time = {time.time() - start}\n")
+
+fig, ax = plt.subplots()
+ax.imshow(np.transpose(m), origin="lower",
+          extent=[xmin, xmax, ymin, ymax])
+
+fig.tight_layout()
+fig.savefig("test.png")
+
+
+
+

Note

+

Even though our Fortran subroutine takes the array m as an +argument, since it is marked as intent(out), the python module +will use this as the return value.

+
+
+

Note

+

The numpy array returned to python will have Fortran ordering (column-major) instead +of the usual row-major ordering (take a look at the .flags attributes).

+
+
+
+

C++ / pybind11 implementation#

+

pybind11 allows you to construct a numpy-compatible array in C++ +and return it. There are different constructors for this—here +we use on that allows us to specify the shape and stride.

+

We can install pybind11 via pip:

+
pip install pybind11
+
+
+

Inside of the mandelbrot() function, we need temporary +two-dimensional arrays to store \(z\) and \(c\). With C++23 +we could use std::mdspan to give us nice multidimensional +indexing. For now, we need to do something different. +Our first attempt will use std::vector<std::vector<std::complex<double>>>.

+

Here’s the implementation of our Mandelbrot generator:

+
#include <iostream>
+#include <cmath>
+#include <complex>
+#include <vector>
+
+#include <pybind11/pybind11.h>
+#include <pybind11/numpy.h>
+
+
+namespace py = pybind11;
+
+using cmplx_arr = std::vector<std::vector<std::complex<double>>>;
+
+using namespace std::complex_literals;
+
+
+py::array_t<int> mandelbrot(int N,
+                            double xmin, double xmax,
+                            double ymin, double ymax, int max_iter) {
+
+    // construct the numpy array we will return
+    // we need to specify the strides manually
+
+    constexpr std::size_t elsize = sizeof(int);
+    std::size_t shape[2]{N, N};
+    std::size_t strides[2]{N * elsize, elsize};
+    auto m = py::array_t<int>(shape, strides);
+    auto m_view = m.mutable_unchecked<2>();
+
+    // for the other arrays used only here, we can
+    // do whatever we want.  Since we can't yet rely
+    // on C++23 mdspan, we'll just do a vector of vectors
+
+    std::vector<double> x(N, 0.0);
+    std::vector<double> y(N, 0.0);
+
+    double dx = (xmax - xmin) / static_cast<double>(N - 1);
+    double dy = (ymax - ymin) / static_cast<double>(N - 1);
+
+    for (int i = 0; i < N; ++i) {
+        x[i] = xmin + static_cast<double>(i) * dx;
+        y[i] = ymin + static_cast<double>(i) * dy;
+    }
+
+    cmplx_arr c(N, std::vector<std::complex<double>>(N, 0.0));
+    cmplx_arr z(N, std::vector<std::complex<double>>(N, 0.0));
+
+    // initialize c;
+
+    for (int i = 0; i < N; ++i) {
+        for (int j = 0; j < N; ++j) {
+            c[i][j] = x[i] + 1i * y[j];
+        }
+    }
+
+    // zero out the output array
+
+    for (int i = 0; i < m.shape(0); ++i) {
+        for (int j = 0; j < m.shape(1); ++j) {
+            m_view(i, j) = 0;
+        }
+    }
+
+    for (int niter = 1; niter <= max_iter; ++niter) {
+
+        for (int i = 0; i < m.shape(0); ++i) {
+            for (int j = 0; j < m.shape(1); ++j) {
+
+                if (m_view(i, j) == 0) {
+                    z[i][j] = z[i][j] * z[i][j] + c[i][j];
+
+                    if (std::abs(z[i][j]) > 2) {
+                        m_view(i, j) = niter;
+                    }
+                }
+            }
+        }
+    }
+
+    return m;
+}
+
+
+PYBIND11_MODULE(mandel, m) {
+    m.doc() = "C++ Mandelbrot example";
+    m.def("mandelbrot", &mandelbrot, "generate the Mandelbrot set of size N");
+}
+
+
+

We build the shared library as:

+
g++ -DNDEBUG -O3  -Wall -Wextra -shared -std=c++17 -fPIC $(python3 -m pybind11 --includes) mandel.cpp -o mandel$(python3-config --extension-suffix)
+
+
+

Our driver is essentially the same as the Fortran one.

+
import matplotlib.pyplot as plt
+import numpy as np
+
+import mandel
+
+import time
+
+start = time.time()
+
+xmin = -2.5
+xmax = 1.5
+ymin = -2.0
+ymax = 2.0
+
+max_iter = 50
+
+m = mandel.mandelbrot(1024, xmin, xmax, ymin, ymax, max_iter)
+
+print(f"execution time = {time.time() - start}\n")
+
+
+fig, ax = plt.subplots()
+ax.imshow(np.transpose(m), origin="lower",
+          extent=[xmin, xmax, ymin, ymax])
+
+fig.tight_layout()
+fig.savefig("test.png")
+
+
+

A slightly more complicated version that creates a contiguous Array class +that can be indexed with () runs faster. That code is here:

+
#include <cstddef>
+#include <cmath>
+#include <complex>
+
+#include <pybind11/pybind11.h>
+#include <pybind11/numpy.h>
+
+
+namespace py = pybind11;
+
+using namespace std::complex_literals;
+
+class Array {
+
+    int N;
+    std::vector<std::complex<double>> _data;
+
+public:
+
+    Array(int N_in)
+        : N(N_in), _data(N_in * N_in, 0.0) {}
+
+    inline std::complex<double>& operator() (int row, int col) {
+        return _data[row * N + col];
+    }
+};
+
+py::array_t<int> mandelbrot(int N,
+                            double xmin, double xmax,
+                            double ymin, double ymax, int max_iter) {
+
+    // construct the numpy array we will return
+    // we need to specify the strides manually
+
+    constexpr std::size_t elsize = sizeof(int);
+    std::size_t shape[2]{N, N};
+    std::size_t strides[2]{N * elsize, elsize};
+    auto m = py::array_t<int>(shape, strides);
+    auto m_view = m.mutable_unchecked<2>();
+
+    // we'll use a simple contiguous array here.  When
+    // C++23 mdspan is available, that will be preferred.
+
+    std::vector<double> x(N, 0.0);
+    std::vector<double> y(N, 0.0);
+
+    double dx = (xmax - xmin) / static_cast<double>(N - 1);
+    double dy = (ymax - ymin) / static_cast<double>(N - 1);
+
+    for (int i = 0; i < N; ++i) {
+        x[i] = xmin + static_cast<double>(i) * dx;
+        y[i] = ymin + static_cast<double>(i) * dy;
+    }
+
+    Array c(N);
+    Array z(N);
+
+    // initialize c;
+
+    for (int i = 0; i < N; ++i) {
+        for (int j = 0; j < N; ++j) {
+            c(i, j) = x[i] + 1i * y[j];
+        }
+    }
+
+    // zero out the output array
+
+    for (int i = 0; i < m.shape(0); ++i) {
+        for (int j = 0; j < m.shape(1); ++j) {
+            m_view(i, j) = 0;
+        }
+    }
+
+    for (int niter = 1; niter <= max_iter; ++niter) {
+
+        for (int i = 0; i < m.shape(0); ++i) {
+            for (int j = 0; j < m.shape(1); ++j) {
+
+                if (m_view(i, j) == 0) {
+                    z(i, j) = z(i, j) * z(i, j) + c(i, j);
+
+                    if (std::abs(z(i, j)) > 2) {
+                        m_view(i, j) = niter;
+                    }
+                }
+            }
+        }
+    }
+
+    return m;
+}
+
+
+PYBIND11_MODULE(mandel, m) {
+    m.doc() = "C++ Mandelbrot example";
+    m.def("mandelbrot", &mandelbrot, "generate the Mandelbrot set of size N");
+}
+
+
+

It uses the same driver.

+
+
+

Timings#

+

On my machine, (python 3.13, numpy 2.2.5, Cython 3.0.12, GCC 15, numba +0.61.2, pybind11 2.13.6) here are some timings (average of 3 runs):

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

technique

timings (s)

python / numpy

0.218

python w/ explicit loops

17.4

Numba(*)

0.0922

Cython

0.0866

Fortran + f2py

0.0860

C++ + pybind11 (vector of vector)

0.120

C++ + pybind11 (contiguous Array)

0.108

+
+

(*) timing for the second invocation, which excludes JIT overhead.

+

We see that Numba, Cython, and Fortran are all quite close in +performance, with C++ contiguous only slightly slower, and all of +these much faster than the other implementations. It may be possible +to further optimize the numpy version, but it is so much easier to +just use Numba in this situation.

+
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/12-extensions/extensions-overview.html b/12-extensions/extensions-overview.html new file mode 100644 index 00000000..53d06144 --- /dev/null +++ b/12-extensions/extensions-overview.html @@ -0,0 +1,703 @@ + + + + + + + + + + + Extensions — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

Extensions

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

Extensions#

+

Python code can be slow, so we sometimes turn to extension modules to +get performance in critical parts of our algorithms There are a number +of ways to write extension modules in python – these can be in +another language like C or Fortran or use a library that converts +python into compiled code (with some restrictions) like Cython or +Numba.

+

We’ll look at some examples of these and talk about their strengths +and weaknesses.

+
+

Methods#

+
    +
  • C++

    +
      +
    • pybind11 : this is a +header-only library that allows you to call C++ functions directly +from python.

      +

      A related library is nanobind, +which has similar syntax but may be more efficient.

      +
    • +
    +
  • +
  • C

    +
      +
    • C-API : the +standard python interpreter (cpython) is written in C, so it is +natural that we can write C code to interact with our python code. +This is the python C-API. Since NumPy is also written in C, we +can work with NumPy arrays in C code as well.

      +

      This will give us the performance of C compiled code, but the +downside is that we lose a lot of what makes python great. We +need to pass data into C as pointers and cast them into types that +represent the arrays we use. This means writing a lot of +boilerplate code just to deal with some simple operations.

      +

      This underlies many of the techniques that we’ll see here.

      +
      +

      Note

      +

      These days, there are better methods for most applications, +and you should probably not use the C-API directly.

      +
      +
    • +
    • ctypes : this +is a module that allows you to call functions in shared libraries. +This is part of standard python.

      +

      With ctypes, you don’t need to modify your C code – you just need to +define an interface to the C function in python. However, the calling +mechanism can be slow.

      +

      There is support for NumPy through numpy.ctypeslib.

      +
    • +
    +
  • +
  • Fortran

    +
      +
    • f2py : this is part of +NumPy. It allows for easy calling of Fortran from python.

      +

      You essentially just need to add some comments to your Fortran +code to allow f2py to build an interface. f2py understands the +different orderings of indices between C and Fortran arrays.

      +
    • +
    +
  • +
  • python

    +
      +
    • Cython : this is a superset of python that can convert python into +compiled C code.

      +

      The advantage here is that the code looks like python, with some +declarations of the variable types with cdef. Performance can be +really great when you need to explicitly write out loops over +NumPy array indices.

      +
    • +
    • Numba : this is a just-in-time +compiler. It just requires a simple decorator and then it will +compile a python function the first time it is encountered.

    • +
    +
  • +
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/Introduction.html b/Introduction.html new file mode 100644 index 00000000..a15dd944 --- /dev/null +++ b/Introduction.html @@ -0,0 +1,687 @@ + + + + + + + + + + + PHY 546: Python for Scientific Computing — PHY 546: Python for Scientific Computing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + + + +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+

PHY 546: Python for Scientific Computing

+ +
+
+ +
+

Contents

+
+ +
+
+
+ + + + +
+ +
+

PHY 546: Python for Scientific Computing#

+

xkcd python cartoon

+

(from https://xkcd.com)

+
+

Why python?#

+
    +
  • Python is a very high-level language

    +
      +
    • it provides many complex data-structures (lists, dictionaries, …)

    • +
    • your code is shorter than a comparable algorithm in a compiled language

    • +
    +
  • +
  • Many powerful libraries to perform complex tasks

    +
      +
    • Parse structured inputs files

    • +
    • send e-mail

    • +
    • interact with the operating system

    • +
    • make plots

    • +
    • make GUIs

    • +
    • do scientific computations

    • +
    • +
    +
  • +
  • Python makes it easy to prototype new tools

  • +
  • Python is cross-platform and Free

  • +
+
+
+

Language Features#

+

Some of the language features are:

+
    +
  • Dynamical typing

  • +
  • Object-oriented foundation

  • +
  • Extensible (easy to call Fortran, C/C++, …)

  • +
  • Automatic memory management (garbage collection)

  • +
  • Ease of readability (whitespace matters)

  • +
+
+
+

Scientific python#

+

Perhaps most importantly, and why we are here:

+
+

Python has been widely adopted in the scientific community.

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + +
+ + + + + + + + +
+ + + + +
+ + +
+
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_images/05b93724c7fe07ec65ef5c1c21c6785ef2c54d0a8ded3cc3d404a1d49e57ead3.png b/_images/05b93724c7fe07ec65ef5c1c21c6785ef2c54d0a8ded3cc3d404a1d49e57ead3.png new file mode 100644 index 0000000000000000000000000000000000000000..5dbe2679c1757f9a77a0c77864d0fed5a774edbc GIT binary patch literal 27827 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 literal 0 HcmV?d00001 diff --git a/_images/082b81005f9f3f3c1ddd51267a746c559a66654295459a31d56575abd4226096.png b/_images/082b81005f9f3f3c1ddd51267a746c559a66654295459a31d56575abd4226096.png new file mode 100644 index 0000000000000000000000000000000000000000..9923b04fe6d399d374b42fdd230470cc08d319fb GIT binary patch literal 179710 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

gM1mm8~lEzdvDdl+@DS}iJyA_YVO6cW2IV#mr( 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 OzgaWpPET?6*zsR7j+8h6 literal 0 HcmV?d00001 diff --git a/_images/0e2cd18fde8a96eb62e54a21f4cddab1053d71c343dfc1c7c1df90e2eb02cf76.png b/_images/0e2cd18fde8a96eb62e54a21f4cddab1053d71c343dfc1c7c1df90e2eb02cf76.png new file mode 100644 index 0000000000000000000000000000000000000000..ca45ba5a8a25eccd5b6e938ba6aaa84157e7106e GIT binary patch literal 7448 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 caH~(c?w%8VG_?)>OiszjE_`0Oj=Y3jhEB literal 0 HcmV?d00001 diff --git a/_images/12158ea15c928d43a30e2afd5fbc1c6444e47d37058e3182be60f71a63af8fa5.png b/_images/12158ea15c928d43a30e2afd5fbc1c6444e47d37058e3182be60f71a63af8fa5.png new file mode 100644 index 0000000000000000000000000000000000000000..d90ebeb5e8be4e862b728111f7a4c2c5bfa7e857 GIT binary patch literal 37656 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 literal 0 HcmV?d00001 diff --git a/_images/14a55c4b37454039b454f3503c5f615e6dccb3f245e259af494d20119331fbe7.png b/_images/14a55c4b37454039b454f3503c5f615e6dccb3f245e259af494d20119331fbe7.png new file mode 100644 index 0000000000000000000000000000000000000000..d944e1cd103f4a92b129bfa1a162901a70d52abc GIT binary patch literal 106806 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 literal 0 HcmV?d00001 diff --git a/_images/18bb87f4a28b0bb4588355d6203a23546722fd40585a27f99318e66e528bee84.png b/_images/18bb87f4a28b0bb4588355d6203a23546722fd40585a27f99318e66e528bee84.png new file mode 100644 index 0000000000000000000000000000000000000000..5b13eb74f6b36ce7998b67bf3504d6f86af68860 GIT binary patch literal 29318 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 z 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 literal 0 HcmV?d00001 diff --git a/_images/227f2d3a8e3db865c48a39a8063f17dbfc53956e8dd10a2759234d6ca2e0c629.png b/_images/227f2d3a8e3db865c48a39a8063f17dbfc53956e8dd10a2759234d6ca2e0c629.png new file mode 100644 index 0000000000000000000000000000000000000000..09fc9d6341f4657f893974ae4f349fa690023bc0 GIT binary patch literal 7239 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 literal 0 HcmV?d00001 diff --git a/_images/2ec66a83b5fc1eaac44c83d4f5d35f09293d9aaa6a3271a014463bd53c77613f.png b/_images/2ec66a83b5fc1eaac44c83d4f5d35f09293d9aaa6a3271a014463bd53c77613f.png new file mode 100644 index 0000000000000000000000000000000000000000..90fcaf65ee47a2b52172a13ffcf7383e41c235e4 GIT binary patch literal 134747 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 z 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