├── .drone.sec ├── .drone.yml ├── .gitignore ├── .jupyter └── jupyter_notebook_config.py ├── .travis.yml ├── Dockerfile ├── LICENSE ├── MANIFEST.in ├── README.rst ├── appveyor.yml ├── bin ├── circleci_dependencies.sh ├── host-jupyter-using-docker.sh ├── prepare_deploy.sh ├── preprocess_notebook_exercise_magic.py ├── render_index.sh └── render_notebooks.sh ├── circle.yml ├── conda-recipe └── meta.yaml ├── environment-gcc.yml ├── environment.yml ├── environment └── Dockerfile ├── github_deploy_key.enc ├── index.ipynb ├── intro-slides ├── abstraction.png ├── asmeurer.jpeg ├── bjodah.jpeg ├── codegen1.png ├── codegen2.png ├── intro-slides.html ├── ixjlyons.jpg ├── moorepants.jpeg ├── remark-latest.min.js └── sympy-notext.svg ├── notebooks ├── 01-intro-sympy.ipynb ├── 02-code-printers.ipynb ├── 07-the-hard-way.ipynb ├── 08-cythonizing.ipynb ├── 20-ordinary-differential-equations.ipynb ├── 22-lambdify.ipynb ├── 23-lambdify-Tc99m.ipynb ├── 25-chemical-kinetics-intro.ipynb ├── 32-chemical-kinetics-symbolic-construction.ipynb ├── 40-chemical-kinetics-cython.ipynb ├── 45-chemical-kinetics-cython-vode.ipynb ├── 50-chemical-kinetics-C.ipynb ├── 55-C-calling-3rd-party-lib.ipynb ├── 60-chemical-kinetics-reaction-diffusion.ipynb ├── _30-chemical-kinetics-lambdify.ipynb ├── _35-chemical-kinetics-lambdify-deserialize.ipynb ├── _37-chemical-kinetics-numba.ipynb ├── _38-chemical-kinetics-symengine.ipynb ├── cython-examples.ipynb ├── exercise_Tc99.py ├── exercise_jac_func.py ├── exercise_lambdify.py ├── exercise_lambdify_expr.py ├── exercise_lambdify_jac.py ├── exercise_odeint.py └── exercise_symbolic.py ├── scipy2017codegen ├── __init__.py ├── cfib │ ├── cfib.c │ └── cfib.h ├── chem.py ├── data │ └── radiolysis_300_Gy_s.json ├── exercise.py ├── fastapprox │ ├── cast.h │ ├── fastexp.h │ ├── fastlog.h │ ├── fastpow.h │ └── sse.h ├── magic.py ├── odesys.py ├── odesys_cvode.py ├── odesys_cython.py ├── odesys_diffusion.py ├── odesys_vode.py ├── plotting.py ├── sundials_templates │ ├── _integrate_serial.pyx │ └── integrate_serial.c ├── template.pyxbld ├── templates.py └── tests │ ├── __init__.py │ └── test_cvode.py ├── setup.cfg ├── setup.py ├── test_installation.py └── tutorial.md /.drone.sec: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.P2dnMQm7NRUlQjhQ6Yj4LDHxuaytRGJbPI0dO_JzZds9tD2t1hdBp00A65HUTXH9FNnCj1e6A74mwHGXqCbI9XCYT10Sd2wspiJR05svFL0RV8dxdBwg8YjycN4wHPeHkA8jV0MYnkgygJXpC7xaNGpKUkxK8YrEUVN5tXNL_vgWOo045qmBGhiUui1ptMb6nsuidiosVcqUw0G7zpmGv8oDkrUkqbNDOZHik-SRQT_27aOgZ9SCINU0sQ1K81LXAYF5i6ipisHiKcDk9sosLhjhB2C5OygYr_MERl78g58DpDM1uMRos9nIhX8FCcb4JMt9nlQOwMWaV7Zj1xsHcg.nI446a__34ayJx5C.m1CHNIsjNIuxGcFdy-34hlGZebv6K2O898YWyjsKrkglx4_Z7YzZe4N7EGFPseA5BvHGtXptLkwZIG1wrOg_TjXoHc-JBQ.PLqrCa6xyX3bijziRsCd4g -------------------------------------------------------------------------------- /.drone.yml: -------------------------------------------------------------------------------- 1 | build: 2 | image: bjodah/scipy-2017-codegen-tutorial 3 | environment: 4 | - CONDA_BLD_PATH=/root/conda-bld 5 | - ANACONDA_TOKEN=$$ANACONDA_TOKEN 6 | commands: 7 | - sed -i.bak '$ d' environment.yml 8 | - conda env create --quiet -f environment.yml 9 | - sed -i 's/conda-root-py/python3/' notebooks/*.ipynb 10 | - bin/render_notebooks.sh 11 | - bin/prepare_deploy.sh 12 | - mkdir $CONDA_BLD_PATH 13 | - conda build --python 35 conda-recipe/ 14 | - conda build --python 36 conda-recipe/ 15 | - conda convert -p all --output $CONDA_BLD_PATH $CONDA_BLD_PATH/linux-64/*.tar.bz2 16 | - cp -r $CONDA_BLD_PATH/linux-* $CONDA_BLD_PATH/win-* $CONDA_BLD_PATH/osx-* deploy/ 17 | - anaconda -t ${ANACONDA_TOKEN} upload --no-progress -u sympy --force $CONDA_BLD_PATH/*/*.tar.bz2 18 | 19 | deploy: 20 | 21 | rsync: 22 | host: hera.physchem.kth.se 23 | user: scipy-2017-codegen-tutorial 24 | port: 22 25 | source: deploy/ 26 | target: ~/public_html 27 | recursive: true 28 | delete: false 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .ipynb_checkpoints/ 3 | ode_cython_*.pyx 4 | ode_cython_*.pyxbld 5 | ode_cython_*.c 6 | integrate_serial_*.c 7 | ode_c_*.pyx* 8 | ode_c_*.c 9 | .cache/ 10 | *.egg-info/ 11 | deploy/ 12 | build/ 13 | dist/ 14 | .conda/ 15 | .ipython/ 16 | .local/ 17 | .jupyter/migrated 18 | .pyxbld/ 19 | .theano/ 20 | notebooks/*.h 21 | notebooks/*.c 22 | notebooks/*.pyx 23 | notebooks/*.pyxbld 24 | notebooks/autowraptmp*/ 25 | -------------------------------------------------------------------------------- /.jupyter/jupyter_notebook_config.py: -------------------------------------------------------------------------------- 1 | def scrub_output_pre_save(model, **kwargs): 2 | """scrub output before saving notebooks""" 3 | # only run on notebooks 4 | if model['type'] != 'notebook': 5 | return 6 | # only run on nbformat v4 7 | if model['content']['nbformat'] != 4: 8 | return 9 | 10 | for cell in model['content']['cells']: 11 | if cell['cell_type'] != 'code': 12 | continue 13 | cell['outputs'] = [] 14 | cell['metadata'] = {} 15 | cell['execution_count'] = None 16 | 17 | c.FileContentsManager.pre_save_hook = scrub_output_pre_save 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | os: 3 | - osx 4 | - linux 5 | osx_image: beta-xcode6.1 6 | 7 | env: 8 | global: 9 | - secure: "StZigC8FRTt+d3mMM7EzCFhZ1a0p66/C1fxvtKiMEbaKNKyPl4Z8oQtQdu+sF5BqNH7PO/g/em3sNTOLxhsQTls8ZjT/JUYma36THZ9Pl/dR7zGBZO3xTTZh8N7OtqXRFHqGBpmijy+nI/Hg2vucU7oDpP1vKb1pdDPqADW7bVJaK/AagmO75Vwpa13gYP2kLxuGhgUTIgVrAxPn3299AHA6kjLLU6OLh+/uJwPuEDBNam6cFSLH0U81+k10Fdbmhce0NFD+Q+yg8haFL76XZFw5buQgkbtuAwhE3N0m7rw+cJehkqKknuZshOHr/Pz9Grj+d+13WzN3mDlI32NjzsSmsYM8uCgHpZhNSwnRnZG4xPem132swGl7yOKRfCJQpEvogSVMuAXIYaQc+37JwqczA0g5S+2NYcZobf+BUEgAgb9OIPuueM13U+HDfiB0PRv4c4Yyx8hboNRoRoWCM8rrkpCw6Xtkmk+id+FxGgDEfbvMvdijujYLNG3ltCUx0nAGKC1Iznz8OJvGPOMFUFWMsh4LUaupLs1TzXlG74BcUVJhC1ezH8KT8XLdckhGlcBUBgAoO8RGTV10KHe1e6I/EG+WKYqoayQ3rKFG1SgMAbgDQ4F5tMdXr5xYzh9bABfZDf9vEHNgL0OTaLRHw7NN+9TgXk5IPIs2Pww+8JM=" 10 | - secure: "PBKqObFCBCWKUEaZpp2+QL00SqUUgMLxtrpQus9iv5cfmU7m/c06wTNrNXV8RyiWZVB+B6/GyYZdF4Q7CnE5AtIKAx7RvTlMDlpIQNyFDEyyZA3yR3lLHbhHLrP/9l6Ln9ZS1MjSINmIvPCTxBNTQ9c4nsYeWlUllK6XTpS4zH1Gs65SW4kMpANnCF4CsQmp8dsSQpVCVe07mMdVbZ2pvmO5WC1LiJDO/Pxy65bFSVqKXtbA/T7nOySJhmaGR+RBMQXktrnZyRA8AAb0+PDi088oyhhYZcdi41T5Vxl8CDEknQjUkrWyG1T86Q9/fWsguWuCazr0g/XmxjJzeBgcTFugylSkn+A4glRA35Iuz9am48irKPgn5jYv9Mjpa4aK6CEWUrIuw9hu6Z4PB5jAnoriv3hoPOj38LkyD2VQaVcMXYK3bUOW50JFm6FMGh2wCLwoCjfOeluVbBTlnAquqAXtR2XlTFJjb3RVDg+0LqE+oj2/IPoWaZJh7TwXKIkWdtboEN6671P77ZO/NeypSHEi/u4srx/HrV8CUxaac/1v6wvRtyG4VlBLLfA+wNbC0hDU+jjNqTImBNHz2a5So0mPIYqH5v+Soqf4LTSYdzkP0mNk1YY7qqV/YfJAoKrfPJIbedqcQ0+/Cjg85pa0u9rJA/MJEdh/Feh3IchInYg=" 11 | 12 | before_install: 13 | # Remove homebrew. 14 | - | 15 | if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then 16 | brew remove --force $(brew list) 17 | brew cleanup -s 18 | rm -rf $(brew --cache) 19 | fi 20 | 21 | install: 22 | - | 23 | MINICONDA_URL="https://repo.continuum.io/miniconda" 24 | if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then 25 | MINICONDA_FILE="Miniconda3-latest-MacOSX-x86_64.sh" 26 | else 27 | MINICONDA_FILE="Miniconda3-latest-Linux-x86_64.sh" 28 | fi 29 | curl -L -O "${MINICONDA_URL}/${MINICONDA_FILE}" 30 | bash $MINICONDA_FILE -b 31 | 32 | source ~/miniconda3/bin/activate root 33 | conda config --add channels conda-forge 34 | conda config --set show_channel_urls true 35 | conda config --set always_yes yes 36 | sed -i.bak -e '$ d' environment.yml 37 | conda env create -f environment.yml 38 | conda install doctr 39 | 40 | script: 41 | - set -e 42 | - sed -i.bak 's/conda-root-py/python3/' notebooks/*.ipynb 43 | - bin/render_notebooks.sh 44 | - bash -c "source activate codegen17; python test_installation.py" 45 | - bin/prepare_deploy.sh 46 | # Clear changes to tracked files so doctr can checkout gh-pages 47 | - git reset --hard 48 | - rm index.html 49 | - git clean -Xdf notebooks/ 50 | - git clean -xdf notebooks/ 51 | - if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then 52 | doctr deploy . --built-docs deploy/; 53 | fi 54 | 55 | notifications: 56 | email: false 57 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM andrewosh/binder-base 2 | 3 | MAINTAINER SymPy devlopment team 4 | 5 | USER root 6 | 7 | # This dockerfile is solely designed to run on binder (mybinder.org) 8 | RUN apt-get update && \ 9 | apt-get --quiet --assume-yes install wget git g++ gfortran libgmp-dev binutils-dev bzip2 make sudo && \ 10 | apt-get clean 11 | 12 | USER main 13 | 14 | COPY environment.yml /tmp/environment.yml 15 | RUN sed 's/codegen17/binder/' /tmp/environment.yml > /tmp/binder.yml && \ 16 | conda update -y conda && \ 17 | conda env create -f /tmp/binder.yml && \ 18 | echo "export PATH=/home/main/anaconda2/envs/binder/bin/:/home/main/anaconda3/envs/binder/bin/:$PATH" >> ~/.binder_start && \ 19 | /bin/bash -c "source activate binder && jupyter kernelspec install-self --user" && \ 20 | mkdir $HOME/.jupyter && \ 21 | echo "c.NotebookApp.token = ''" >> $HOME/.jupyter/jupyter_notebook_config.py && \ 22 | echo "c.NotebookApp.password=''" >> $HOME/.jupyter/jupyter_notebook_config.py && \ 23 | echo "c.NotebookApp.password_required=False" >> $HOME/.jupyter/jupyter_notebook_config.py 24 | ENV CONDA_PREFIX /home/main/anaconda2/envs/binder 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 SymPy Development Team 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | a. Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | b. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | c. Neither the name of SymPy nor the names of its contributors 14 | may be used to endorse or promote products derived from this software 15 | without specific prior written permission. 16 | 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 28 | DAMAGE. 29 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft scipy2017codegen/sundials_templates 2 | include scipy2017codegen/template.pyxbld 3 | graft scipy2017codegen/data 4 | graft scipy2017codegen/fastapprox 5 | graft scipy2017codegen/cfib 6 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | SciPy 2017 Codegen Tutorial 3 | =========================== 4 | 5 | Introduction 6 | ============ 7 | 8 | This repository contains all of the source code and Jupyter notebooks for the 9 | SciPy 2017 tutorial "Automatic Code Generation with SymPy". 10 | 11 | The original proposal for this tutorial can be found on the `SymPy Wiki`_. 12 | 13 | .. _SymPy Wiki: https://github.com/sympy/sympy/wiki/SciPy-2017-Tutorial-Proposal:-Automatic-Code-Generation-with-SymPy 14 | 15 | The statically rendered Jupyter notebooks in this repository can be viewed at 16 | `sympy.org/scipy-2017-codegen-tutorial `_ 17 | or on NBViewer_. 18 | 19 | .. _NBViewer: http://nbviewer.jupyter.org/github/sympy/scipy-2017-codegen-tutorial/blob/master/index.ipynb 20 | 21 | Software Installation 22 | ===================== 23 | 24 | We leverage the Conda package manager for installation of the necessary 25 | software on the three most popular platforms. Please install either Anaconda_ 26 | or Miniconda_ using the instructions provided at the download links. 27 | 28 | .. _Anaconda: https://www.continuum.io/downloads 29 | .. _Miniconda: https://conda.io/miniconda.html 30 | 31 | If you are using Windows, please also install the `Visual C++ Build Tools`_ for 32 | proper Cython compilation with Python 3.5+. **Install this before you arrive 33 | at the tutorial, as it takes some time.** 34 | 35 | .. _Visual C++ Build Tools: http://landinghub.visualstudio.com/visual-cpp-build-tools 36 | 37 | 38 | You will need to download_ and unzip or clone_ this repository with Git so that 39 | the files are available on your computer. For example:: 40 | 41 | > wget https://github.com/sympy/scipy-2017-codegen-tutorial/archive/master.zip 42 | > unzip master.zip 43 | 44 | or:: 45 | 46 | > git clone https://github.com/sympy/scipy-2017-codegen-tutorial.git 47 | 48 | .. _download: https://github.com/sympy/scipy-2017-codegen-tutorial/archive/master.zip 49 | .. _clone: https://github.com/sympy/scipy-2017-codegen-tutorial.git 50 | 51 | At the command line, change into the repository directory:: 52 | 53 | > cd /path/to/scipy-2017-codegen-tutorial 54 | 55 | Creating a conda environment from ``environment.yml`` 56 | ----------------------------------------------------- 57 | 58 | Once you have conda installed, you can choose from one of our environment 59 | files that specifies our conda environment (named ``codegen17``): 60 | 61 | - ``environment.yml`` (relies on your system compiler, e.g. gcc/clang/msvc on linux/osx/win) 62 | - ``environment-gcc.yml`` (installs conda's version of gcc) 63 | 64 | we strongly encourage user to use ``environment.yml``. At the command 65 | line, you can create this environment by executing e.g.:: 66 | 67 | > conda env create -f environment.yml 68 | 69 | **Run this command before you arrive at the tutorial, as it takes some time.** 70 | 71 | When installation is complete you may activate the environment by typing:: 72 | 73 | > activate codegen17 74 | 75 | on Windows or using Bash on Linux/Mac):: 76 | 77 | $ source activate codegen17 78 | 79 | To check to see if everything is installed correctly type:: 80 | 81 | (codegen17)> python test_installation.py 82 | 83 | If there are no errors or warnings you have installed the software correctly. 84 | 85 | To exit the environment you type:: 86 | 87 | (codegen17)> deactivate 88 | 89 | If you for some reason want to remove the environment you can do so after 90 | deactivating by typing:: 91 | 92 | > conda env remove --name codegen17 93 | 94 | on windows, and:: 95 | 96 | $ source deactivate 97 | 98 | on Linux/Mac (using bash). 99 | 100 | At this point you have everything installed to run the code in the tutorial. 101 | 102 | Running the notebooks 103 | ===================== 104 | 105 | After activating the `codegen17` environment start Jupyter in the `notebooks` 106 | directory:: 107 | 108 | (codegen17)> jupyter notebook index.ipynb 109 | 110 | A web interface should open in your web browser (default address 111 | http://localhost:8888). Note that Ctrl-C will stop the notebook 112 | server. 113 | 114 | Optional Installation/Run Methods 115 | ================================= 116 | 117 | Host a jupyter server using docker 118 | ---------------------------------- 119 | If `docker `_ is installed it is possible to simply launch 120 | a jupyter notebook running in the correct environment by writing:: 121 | 122 | $ bin/host-jupyter-using-docker.sh 123 | 124 | Note that it will download roughly ~1 GiB first time you run the command. Also note 125 | that you do not need to have conda installed on your machine to do this (conda is 126 | installed in the dockerimage). 127 | 128 | Run notebooks using binder 129 | -------------------------- 130 | Using only a web-browser (and an internet connection) it is possible to explore the 131 | notebooks here: (by the courtesy of the people behind mybinder) 132 | 133 | .. image:: http://mybinder.org/badge.svg 134 | :target: https://beta.mybinder.org/v2/gh/sympy/scipy-2017-codegen-tutorial/master?filepath=index.ipynb 135 | :alt: Binder 136 | 137 | Developing the notebooks 138 | ======================== 139 | Note that you should remove the last line of ``environment.yml`` (i.e. scipy2017codegen) if 140 | you intend to make changes to the ``scipy2017codegen`` package (do not commit that change however). 141 | Otherwise conda will pull the package from: 142 | https://anaconda.org/SymPy/scipy2017codegen 143 | 144 | It is recommended that you run ``python setup.py develop`` after having activated the 145 | ``codegen17`` environment lacking our above mentioned package. 146 | 147 | CI status 148 | --------- 149 | Below are the build status of the CI services set up to test the tutorial notebooks. 150 | 151 | Travis CI (OS X) 152 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 153 | .. image:: https://secure.travis-ci.org/sympy/scipy-2017-codegen-tutorial.svg?branch=master 154 | :target: http://travis-ci.org/sympy/scipy-2017-codegen-tutorial 155 | :alt: Travis status 156 | 157 | AppVeyor (Windows) 158 | ~~~~~~~~~~~~~~~~~~ 159 | .. image:: https://ci.appveyor.com/api/projects/status/txyb8gw675e3b055?svg=true 160 | :target: https://ci.appveyor.com/project/bjodah/scipy-2017-codegen-tutorial/branch/master 161 | :alt: AppVeyor status 162 | 163 | CircleCI (Linux - tests environment.yml) 164 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 165 | .. image:: https://circleci.com/gh/sympy/scipy-2017-codegen-tutorial.svg?style=shield 166 | :target: https://circleci.com/gh/sympy/scipy-2017-codegen-tutorial 167 | :alt: Circle CI status 168 | 169 | Drone (Dockerized Ubuntu 16.04 - tests environment-nogcc.yml) 170 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 171 | .. image:: http://hera.physchem.kth.se:9090/api/badges/sympy/scipy-2017-codegen-tutorial/status.svg 172 | :target: http://hera.physchem.kth.se:9090/sympy/scipy-2017-codegen-tutorial 173 | :alt: Drone status 174 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by conda-smithy. To update a component of this 2 | # file, make changes to conda-forge.yml and/or recipe/meta.yaml, and run 3 | # "conda smithy rerender". 4 | 5 | environment: 6 | 7 | matrix: 8 | - TARGET_ARCH: x86 9 | CONDA_PY: 35 10 | CONDA_INSTALL_LOCN: C:\\Miniconda35 11 | SCIPY2017CODEGEN_COMPILER: msvc 12 | 13 | - TARGET_ARCH: x64 14 | CONDA_PY: 35 15 | CONDA_INSTALL_LOCN: C:\\Miniconda35-x64 16 | SCIPY2017CODEGEN_COMPILER: msvc 17 | 18 | # We always use a 64-bit machine, but can build x86 distributions 19 | # with the TARGET_ARCH variable. 20 | platform: 21 | - x64 22 | 23 | install: 24 | # Cywing's git breaks conda-build. (See https://github.com/conda-forge/conda-smithy-feedstock/pull/2.) 25 | - cmd: rmdir C:\cygwin /s /q 26 | 27 | # Add path, activate `conda` and update conda. 28 | - cmd: call %CONDA_INSTALL_LOCN%\Scripts\activate.bat 29 | - cmd: conda update --yes --quiet conda 30 | 31 | - cmd: set PYTHONUNBUFFERED=1 32 | 33 | # Add our channels. 34 | - cmd: conda config --set show_channel_urls true 35 | - cmd: conda config --remove channels defaults 36 | - cmd: conda config --add channels defaults 37 | - cmd: conda config --add channels conda-forge 38 | 39 | # Skip .NET project specific build phase. 40 | build: off 41 | 42 | test_script: 43 | - "sed -i '$ d' environment.yml" 44 | - "conda env create --quiet -f environment.yml" 45 | - "activate codegen17" 46 | - "python -m pip install -e ." 47 | - "cd notebooks" 48 | - "sed -i 's/conda-root-py/python3/' *.ipynb" 49 | - "del _*.ipynb" 50 | - "python ../bin/preprocess_notebook_exercise_magic.py *.ipynb" 51 | - "jupyter nbconvert --debug --ExecutePreprocessor.enabled=True --ExecutePreprocessor.timeout=300 --to=html *.ipynb" 52 | - "cd .." 53 | - "python test_installation.py" 54 | -------------------------------------------------------------------------------- /bin/circleci_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [[ ! -d $HOME/miniconda ]]; then 3 | curl -L --silent -o miniconda.sh https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh 4 | bash miniconda.sh -b -p $HOME/miniconda 5 | rm miniconda.sh 6 | conda config --add channels conda-forge 7 | conda config --set always_yes yes 8 | conda update --quiet --all 9 | sed -i.bak '$ d' environment.yml 10 | conda env create --quiet -f environment-gcc.yml 11 | fi 12 | -------------------------------------------------------------------------------- /bin/host-jupyter-using-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MOUNT=${1:-.} 3 | PORT=${2:-8888} 4 | IMAGE="bjodah/scipy-2017-codegen-tutorial:latest" 5 | if [[ "$MOUNT" == .* ]]; then 6 | MOUNT="$(pwd)/$MOUNT" 7 | fi 8 | if [[ ! -z "$DISPLAY" ]]; then 9 | ( sleep 5; xdg-open "http://127.0.0.1:$PORT" ) & 10 | fi 11 | MYCMD="groupadd -f --gid \$HOST_GID \$HOST_WHOAMI; \ 12 | useradd --uid \$HOST_UID --gid \$HOST_GID --home /mount \$HOST_WHOAMI; \ 13 | sudo --login -u \$HOST_WHOAMI PATH=/opt/miniconda3/bin:$PATH conda env create -f environment.yml; \ 14 | sudo --login -u \$HOST_WHOAMI PATH=/opt/miniconda3/bin:$PATH bash -c 'source activate codegen17; PYTHONPATH=/mount jupyter notebook --no-browser --port 8888 --ip=*'" 15 | docker run --rm --name "codegen17_nb_$PORT" -p 127.0.0.1:$PORT:8888\ 16 | -e HOST_WHOAMI=$(whoami) -e HOST_UID=$(id -u) -e HOST_GID=$(id -g)\ 17 | -v $MOUNT:/mount -w /mount -it $IMAGE /bin/bash -c "$MYCMD" 18 | -------------------------------------------------------------------------------- /bin/prepare_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | source activate codegen17 4 | mkdir -p deploy/notebooks 5 | sed -i.bak0 's/ipynb/html/' index.ipynb 6 | sed -i.bak1 's/filepath=index.html/filepath=index.ipynb/' index.ipynb 7 | jupyter nbconvert --to=html index.ipynb 8 | mv index.ipynb.bak index.ipynb 9 | cp -R index.* intro-slides notebooks deploy/ 10 | ls -R deploy 11 | if [[ "$DRONE" == "true" ]]; then 12 | sed -i.bak "s/number: 0/number: ${DRONE_BUILD_NUMBER}/" conda-recipe/meta.yaml 13 | fi 14 | for f in deploy/notebooks/*.nbconvert.ipynb; do 15 | mv $f ${f%.nbconvert.ipynb}.ipynb 16 | done 17 | -------------------------------------------------------------------------------- /bin/preprocess_notebook_exercise_magic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import glob 5 | import itertools 6 | import sys 7 | import os 8 | 9 | def preprocess_ipynb(path): 10 | """ This expands cells containing a line like: 11 | 12 | %exercise exercise_spam.py 13 | 14 | where ``spam`` may be any arbitrary string. 15 | 16 | What is the purpose of this function? 17 | 18 | - The %exercise line magic loads the file with some parts replaced 19 | by ``??`` -- i.e. the user is supposed to write code. That is not 20 | the desired behaviour on the CI server though. So this function 21 | edits a ``.ipynb`` file inplace, by inserting the contents of the 22 | referenced file (and leaving the magic commented out, analogous to %load). 23 | 24 | """ 25 | lines = open(path, 'rt', encoding='utf-8').readlines() 26 | new_lines = [] 27 | for line in lines: 28 | if line.lstrip().startswith('"%exercise'): 29 | if not '"%exercise exercise_' in line or not line.endswith('.py"\n'): 30 | raise ValueError("Expected the file to be named exercise_*.py") 31 | new_lines.append(line.replace('"%exercise ', '"# %exercise ').rstrip('"\n') + '\\n",\n') 32 | _, fname_and_rest = line.split('%exercise ') 33 | src = os.path.join(os.path.dirname(path), fname_and_rest[:-2]) 34 | src_lines = open(src).readlines() 35 | for ln in src_lines[:-1]: 36 | new_lines.append(' "%s\\n",\n' % ln.rstrip('\n')) 37 | new_lines.append(' "%s"\n' % src_lines[-1].rstrip('\n')) 38 | else: 39 | new_lines.append(line) 40 | open(path, 'wt', encoding='utf-8').write(''.join(new_lines)) 41 | 42 | if __name__ == '__main__': 43 | if len(sys.argv) == 1: 44 | print("Please specify notebook files as arguments.", file=sys.stderr) 45 | sys.exit(1) 46 | for arg in itertools.chain(*map(glob.glob, sys.argv[1:])): 47 | if not arg.endswith('.ipynb'): 48 | raise ValueError("Expected notebookfile (.ipynb), got: %s" % arg) 49 | preprocess_ipynb(arg) 50 | -------------------------------------------------------------------------------- /bin/render_index.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # 3 | # Usage (assuming: shopt -s extglob): 4 | # 5 | # $ cd examples/ && ../scripts/render_index.sh !(index).html 6 | # 7 | mkdir -p thumbs 8 | tmpdir=$(mktemp -d) 9 | trap "rm -r $tmpdir" INT TERM EXIT 10 | cat <index.html 11 | 12 | 13 | 14 | Notebook gallery 15 | 16 | 17 | EOF 18 | for f in $@; do 19 | if [[ $f == _* ]]; then 20 | continue # don't include notebooks starting with underscore 21 | fi 22 | img=$(basename $f .html).png 23 | QT_QPA_PLATFORM=offscreen phantomjs $(unset CDPATH && cd "$(dirname "$0")" && echo $PWD)/rasterize.js $f $tmpdir/$img 1200px*900px 24 | convert $tmpdir/$img -resize 400x300 thumbs/$img 25 | cat <>index.html 26 |

27 | 28 |
29 | $f 30 |

31 | EOF 32 | done 33 | cat <>index.html 34 | 35 | 36 | EOF 37 | -------------------------------------------------------------------------------- /bin/render_notebooks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | source activate codegen17 3 | python setup.py develop 4 | for f in notebooks/*.ipynb; do 5 | if [[ $f == notebooks/_* ]]; then 6 | continue 7 | fi 8 | python bin/preprocess_notebook_exercise_magic.py $f 9 | jupyter nbconvert --debug --ExecutePreprocessor.enabled=True --ExecutePreprocessor.timeout=300 --to=html $f 10 | jupyter nbconvert --debug --ExecutePreprocessor.enabled=True --ExecutePreprocessor.timeout=300 --to=notebook $f 11 | done 12 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | general: 2 | branches: 3 | only: 4 | - master 5 | 6 | machine: 7 | environment: 8 | PATH: "$HOME/miniconda/bin:${PATH}" 9 | 10 | dependencies: 11 | pre: 12 | - ./bin/circleci_dependencies.sh 13 | cache_directories: 14 | - "~/miniconda" 15 | 16 | test: 17 | override: 18 | - sudo apt-get remove gcc 19 | - cd && rm -rf ~/.pyenv && rm -rf ~/virtualenvs 20 | - sed -i 's/conda-root-py/python3/' notebooks/*.ipynb 21 | - ./bin/render_notebooks.sh 22 | - bash -c "source activate codegen17; python test_installation.py" 23 | -------------------------------------------------------------------------------- /conda-recipe/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: scipy2017codegen 3 | version: 2017.1 4 | 5 | source: 6 | git_url: ../ 7 | 8 | build: 9 | number: 0 10 | script: python setup.py install --single-version-externally-managed --record record.txt 11 | 12 | requirements: 13 | build: 14 | - python 15 | run: 16 | - python 17 | - notebook 18 | - nbconvert 19 | - numpy 20 | - matplotlib 21 | - sympy 22 | - scipy 23 | - cython 24 | - sundials 25 | - python-symengine 26 | - numba 27 | 28 | test: 29 | imports: 30 | - scipy2017codegen 31 | 32 | about: 33 | home: https://github.com/sympy/scipy-2017-codegen-tutorial 34 | summary: 'Python package for the SciPy 2017 tutorial on code-generation' 35 | -------------------------------------------------------------------------------- /environment-gcc.yml: -------------------------------------------------------------------------------- 1 | name: codegen17 2 | channels: 3 | - sympy 4 | - symengine 5 | - conda-forge 6 | - defaults 7 | dependencies: 8 | - python >=3.5 9 | - setuptools 10 | - notebook 11 | - nbconvert 12 | - numpy 13 | - matplotlib 14 | - scipy 15 | - cython 16 | - sympy 17 | - sundials <3.0.0 18 | - python-symengine 19 | - numba 20 | - gcc 21 | - scipy2017codegen 22 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: codegen17 2 | channels: 3 | - sympy 4 | - symengine 5 | - conda-forge 6 | - defaults 7 | dependencies: 8 | - python >=3.5 9 | - setuptools 10 | - notebook 11 | - nbconvert 12 | - numpy 13 | - matplotlib 14 | - scipy 15 | - cython 16 | - sympy =1.1 17 | - sundials <3.0.0 18 | - python-symengine 19 | - numba 20 | - scipy2017codegen 21 | -------------------------------------------------------------------------------- /environment/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:xenial 2 | ENV LANG en_US.UTF-8 3 | ENV LANGUAGE en_US:en 4 | ENV LC_ALL en_US.UTF-8 5 | ENV DEBIAN_FRONTEND noninteractive 6 | ENV DEBCONF_NONINTERACTIVE_SEEN true 7 | ENV PATH /opt/miniconda3/bin:$PATH 8 | RUN apt-get update && \ 9 | apt-get --quiet --assume-yes install locales && \ 10 | locale-gen en_US.UTF-8 && \ 11 | echo "path-exclude /usr/share/doc/*" >/etc/dpkg/dpkg.cfg.d/01_nodoc && \ 12 | echo "path-include /usr/share/doc/*/copyright" >>/etc/dpkg/dpkg.cfg.d/01_nodoc && \ 13 | apt-get update && \ 14 | apt-get --quiet --assume-yes --no-install-recommends install wget git g++ gfortran libgmp-dev binutils-dev bzip2 make sudo && \ 15 | apt-get clean && \ 16 | wget --no-check-certificate --no-verbose "https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh" -O miniconda3.sh && \ 17 | bash miniconda3.sh -b -p /opt/miniconda3 && \ 18 | rm miniconda3.sh && \ 19 | conda config --set always_yes yes --set changeps1 no && \ 20 | conda config --add channels conda-forge --force && \ 21 | conda install python notebook numpy matplotlib scipy cython sundials numba theano conda-build anaconda-client && \ 22 | conda config --set anaconda_upload no && \ 23 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 24 | -------------------------------------------------------------------------------- /github_deploy_key.enc: -------------------------------------------------------------------------------- 1 | gAAAAABZX_iQL8cJMygWNWbkaz5HAKrQr_CNX8Z0vCeoeU-bf46i8xT2MJeRJjKC2NMt3V1A7BiQ6Lf3Rf0rKlK7it4c-BR9mR3LP0ki0kI3LbKyc0WAl8FrcX5LqQBrhP9xSfGTDUtr3JBbC787s3psvziX_7AY_4HSZpj7cSD1oMIeJrAT7AIYhN_0NFrcZkoP2wc-Nwj7_VWG8QowjzbZmv_IzpGY626cO88P_7SU4VNW8JYRJR0MHvEw-jjBLavn7StyZ0lBmdP7NMI9LdTPK6u2hjdw36dubSKtg-tCuArT_YpFVefzF_YEkAyTGQRicUIcAXSnWgToUnNE79P6Mp-o8-ezFJw9Wf3ttjWjH6gNeSD-h7WxWSasIZe2ngLdSQUgFPBHScbP3Mjzu5gJE2oz4GAMiad2heA5yuNbcP21Pb1UlkoJGlHureXtXPqfVzPmAht_D4M52l0e7Ca7wy0ovbTB1QuZF9y-HUB3UCCLRZfqWt20cSKTA4DKXI_rdpjTfKFfNcVHxfssNz7uWGu73N_1YfaftxcoDUXYOOZ0aFuTyvjW81MR24V2yIG28Xr212d5-rAxIbM_tjmd8PxpgN3m9GXI3mMF3AA-7mDkvm06_521V2BQrY6hxYbgb5VaB_wsTzOhGVwAlRS53ZtElWh4hx9TSolLXOQLEbgt1orwSHT-WyNq2Y7oSHK15HtJ-eVOemQQXNnYG5S43nSk1Z8qzDV4CG1VhHpfPN_bLbfJmALe12QDYJkW0s93Rlk38-cr2D_GeSeYz7YnfaCl4geh63BZ4W52tqRBZqDCeh-vgeMV-x3bZLyEwNJg4fp6iyV33iD8LA0WVZrKbzO7ATUcEp_m5YTgcJ_xy7dEfKz3RY2SkhxruK3PwqSLKhoHxhgpHDfxbAjMJ7vuJeXa1dQ2iflrV6d7VjP90nGjiROZ1IpohwVgT6-YfscR8u82aKC-xVEhMLEYmI-j8q1iuZdXkbvXGiKLMznq5O3giUhFbhh0dUkrJ47loh1epjTbhU2rUBNJP0sG8XRXE_CoiBRfVFmTZXaLi2RRVowLn61HgzBuid7a7-YV5TOzR5TSOCNmr0xvEnmYFGt2KcXR5sbfmnlwKJO7Nbn71lYTsy4giLpuk9_fRE0dZArGYQ_akFlvUCQgyWTs6qBpIdjlYjivjYE9xZKWtZVIgPYtJZxnM707Mu7Xhb7jylb-dX2jRyYoGr2NXKp5Bp-zSO_ODl7mCZ6nuP2RkA38ho_TJOM7dqdWDkUXOsb0NppOnpCClz7IWGjZzZzx1br9w69bGihSPaDLnUDH0xdbKJQWgY5YnvQg_3EnbQXNDIl94W2xj8BVAwu0S9ybeNzjlhEgFWPpwd5PC8HFyVuVHSOlkNO-HRE9szEiWis_V2V2KJ6BReUaVV9-uBfoWrOYtzfDGAHgQrONPs85tvSwB-buDzdNABFztWg966YCv7SnFosPs-d4EfwmvFFPYY0Enw4GjkrsDFKSP167EMN5R_EBfnjvIh5My0yjAmXYs_ZTnKRYARNCVjFqWxBfJzfjjdorhSfAZPBvoWfyVZFKHDSETSHLjCIzwsGAzWAvTMucZntAF2QB-jq3gosPL3tKQdmUc4ooPAti4EdTkVVGiWkH5QRpcQy_eeuSy7WysvI1aroaGPXjfBRN0OoEJfRum7E4rAy4vS2jFR1J0eBW1ChBnf8AGYDo7UAkCQXZxKvDwDg6l_VBIS8434qP1oLL4gfljWSuPkfwvBnXbGcg8twe3PcqL9hGNqidjMIFTw5DLaYjxiV1lSwHgB3UaNt4asxwsry101DU2xp7fRX9OoNW6f9CtA0jzukXWloFx8yF8EGIkWVyT5Olr-qZFXsbsfahnjwaGeewVgQtChJxXSFr1FEe8t4HiZtqd6SoM7JfI7zZlkn06LHyncE-DkVnAp6FbNHZVcRPxXefrMlnUXxRdx4sxJhWbNbv0NUdWbCcXl3Rc60TkyZ7Hpk-JonavFr-qpA7vWgQ1Rkf1ec6ajQfKdGfm5s0OTc86OTl04WH8lWMBM2Iuq0nGmtG4Rbx5HXcdrjEGZLPt63hUKF91TRn9NOAIMI2-L-HACaOW3ov2OTNNvxkXHsCuLFoTef3SdMApuRE_aWfwSFYKqog8Orrs41ijf9WZlD_skLu2QHW2755RBVuTdVhRULt6VQUhBbrM6LQKKE-oZS-t_8osGFWZtZYfRwuJUvJ7K5xyPqY9UTuc98kR-Z1S--g2IHnx3ehvtczhQEweD3fty7OyiXfqYsMTLmNDvxvraUyfZkefuBk_B7O_pEdA_LgXKXGG-2PncSfwQrYwrxtu__u1JRWnz6vUwmleBEYBw04uNPMZenKUqr1fTH7S8QmPm5ZxptomZCM3XBoiBqpZABlrunFd1gIRle84buOIOco683o8d22o9pm2EU-gd5poiVfmEeRxJ0_drnrpKygywUP4S_P8DgoFg9JCdqMN-yofMCWdn7p63V7cn4Y20ftrOsZukM3KVSrVrxN6BGK5zKGAersgIvI7J-Jsq_QUvnhsXe3-TRNgWxjhXmq4XA4bGL44Lxj6EpkwkwHysq6hC5n6ULjW7b1Blyzh7hV3AvSKZaQLFNQscEa1wLYqPQLIV8HtROZ9bmnj0bUMH7KuDr1WdWd6JsNEOB0RLQYsYDDQ3Y4fSn7fEGq-Igw0Eam9XSOVT1yGP9Qe1W4FBE9X5UP_Ym3OXlHyjRMsi6qe-Np_s5m4TJZZb3khUd3JnUncscdhb1CmmHGYuxTqQPpd_6DaA8mUTrQeYupVzXOgvOgWvhhnPWzIAl4HJgmlVg1fTosijit4kNzbIKPnACgCQrJGv5aNTo18Eupxb4bvPJh_mtBJ0HSfhBEdUx2rpQO4_jtBTmCnmt2WJQHWSJwKyZHLdAETRNaO4Zg7OWqkGnMmAdvqZeKfXAQ-1-KAB627TZZdTayv7ZVOEzzWT2KOdVoi3ZCrIU9Th2gGG-EPUCXNT-rYDDEFVE5Gp6Ohm8M4Jtik31FMTf20yZ_A_CtYsAlr3BszqNS9i5wd-SsrXU2dYq9tSyX6h7xg53mNYOLPqWx3zv5r8q2uwHulFAKhzbKOHnWyECecR5AD4OZ2c9WNjqvj8R4hpfmac91QRTyE0GzBfpTfdOZcBJOjExnVPpwZHZKSWhXYFAhtx7wwNxnts0DMX-dFPFjWE_tjTQft0yGZCx6dmXK5_2KTqUNFMnGEHmSIvIAfPvXIBkdWF5U2RRSw4AhyhwZM-SDZQ0xymOiDpRzBuCEh9PE53B0IgltqdGw1czOkk5Tcr-kKoOzgE4LvC9T6jf642m0W45pjK33u21TFjH2nBjcvX5w7qhRJFPxXLytDZhuyB43Yvm_KdJs_nImq7qD41cc7apiDIEPQy8a11_kJOiERr8hD4oCefntjjtiHOot6pexowKR8FBQpee81vWneaxA-1RKBwu57zv6P7Rkpfz11RKB8nwtyKOqXDAyY7bM2Mb-c6mIZidgDcT728qlusTzkwzL-ww3dvSYf5cTlm7vGBdD_wrc5Zapfg99OezDFMvg1Bc_gvbYJeCPwebF-Zew_NS6rZxrOXhGtoB3GDkfaZvpuW5mKIuWIpIXpmpJA5kuzsYnttQaHg-VrWYeNpaoaCnoV39P2dkEIL0oYEiDr10OVzJ0R57S5EKZ7leLBCfipuqIhSr8HZb8wUqyvwqNz6L94bXn_5V7uUTMnP0tqhCbW9-aEFqyl0jOmspCmRmziw_IdnC6jK0XzKWKLNxwfad9j-4n5tlnrmpO1wFmmglup8eZvctWvxKiv79DUlgbZ_0SrEUphYUqatFheOJFRE9wm4LCYhTVOBwx93dlhRayzHake6CXAevPBNkSNIAfLVgEtHv8EeOzO63ss9cD9n4srC7RJrYFJBCPFVA0jdP7zeYTYCRUijkmx9-tbRkYywJkd76TVNKYmiisoFWsuK3fb9uU_TeFjmRYhacyFLpfvCuZ9D8gNieCn7vPeXP-7W0rJMwsNNi6z8hCh7SIRwQoLPpN0lFRibH_fzd9mIRDrJjdqr120YpqqLBRV2h0vK9mVHRqGgzak2VOsXCMPVdFxmMPg41XUt0Ku6JYeSyMKrVvMV9GgKrlP2ANCcPtC9-2P5laU_NfiNv2xF_mGqU3JeAkG0llWfGhHTbhFlR5BxMx7dKfjA0IUXPJBoKdJQsIujORjDMyWeeILOJuBELVmOyqr1YYbBCq85bEzajghhha-nx1kGQ7w_dEoXZRIlI1QDYf-9UzK6iAcFqTx9i7NvBXA9U_lcNRZFXsjtYQvVyioDW6kCrOwCZJ4UICduBTg7gE773nEsKnQnYmS1UkNOWFx43ayxZY4l7nWn_B6oCHnz74_vezgPM= -------------------------------------------------------------------------------- /intro-slides/abstraction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sympy/scipy-2017-codegen-tutorial/4bd0cdb1bdbdc796bb90c08114a00e390b3d3026/intro-slides/abstraction.png -------------------------------------------------------------------------------- /intro-slides/asmeurer.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sympy/scipy-2017-codegen-tutorial/4bd0cdb1bdbdc796bb90c08114a00e390b3d3026/intro-slides/asmeurer.jpeg -------------------------------------------------------------------------------- /intro-slides/bjodah.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sympy/scipy-2017-codegen-tutorial/4bd0cdb1bdbdc796bb90c08114a00e390b3d3026/intro-slides/bjodah.jpeg -------------------------------------------------------------------------------- /intro-slides/codegen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sympy/scipy-2017-codegen-tutorial/4bd0cdb1bdbdc796bb90c08114a00e390b3d3026/intro-slides/codegen1.png -------------------------------------------------------------------------------- /intro-slides/codegen2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sympy/scipy-2017-codegen-tutorial/4bd0cdb1bdbdc796bb90c08114a00e390b3d3026/intro-slides/codegen2.png -------------------------------------------------------------------------------- /intro-slides/intro-slides.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Title 5 | 6 | 8 | 38 | 39 | 40 | 238 | 240 | 243 | 244 | 245 | -------------------------------------------------------------------------------- /intro-slides/ixjlyons.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sympy/scipy-2017-codegen-tutorial/4bd0cdb1bdbdc796bb90c08114a00e390b3d3026/intro-slides/ixjlyons.jpg -------------------------------------------------------------------------------- /intro-slides/moorepants.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sympy/scipy-2017-codegen-tutorial/4bd0cdb1bdbdc796bb90c08114a00e390b3d3026/intro-slides/moorepants.jpeg -------------------------------------------------------------------------------- /notebooks/22-lambdify.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Creating a function from a symbolic expression\n", 8 | "In SymPy there is a function to create a Python function which evaluates (usually numerically) an expression. SymPy allows the user to define the signature of this function (which is convenient when working with e.g. a numerical solver in ``scipy``)." 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "metadata": {}, 15 | "outputs": [], 16 | "source": [ 17 | "import sympy as sym\n", 18 | "sym.init_printing()" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "We will look at an arbitrary expression $f(x, y)$:\n", 26 | "\n", 27 | "$$\n", 28 | "f(x, y) = 3 x^{2} + \\log{\\left (x^{2} + y^{2} + 1 \\right )}\n", 29 | "$$" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [ 38 | "x, y = sym.symbols('x y')\n", 39 | "expr = 3*x**2 + sym.log(x**2 + y**2 + 1)\n", 40 | "expr" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "metadata": {}, 46 | "source": [ 47 | "One way to evaluate above expression numerically is to invoke the ``subs`` method followed by the ``evalf`` method:" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": null, 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "expr.subs({x: 17, y: 42}).evalf()" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": {}, 62 | "source": [ 63 | "However, if we need to do this repeatedly it can be quite slow:" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": null, 69 | "metadata": {}, 70 | "outputs": [], 71 | "source": [ 72 | "%timeit expr.subs({x: 17, y: 42}).evalf()" 73 | ] 74 | }, 75 | { 76 | "cell_type": "markdown", 77 | "metadata": {}, 78 | "source": [ 79 | "even compared to a simple lambda function:" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": null, 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "import math\n", 89 | "f = lambda x, y: 3*x**2 + math.log(x**2 + y**2 + 1)\n", 90 | "f(17, 42)" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": null, 96 | "metadata": {}, 97 | "outputs": [], 98 | "source": [ 99 | "%timeit f(17, 42)" 100 | ] 101 | }, 102 | { 103 | "cell_type": "markdown", 104 | "metadata": {}, 105 | "source": [ 106 | "SymPy can also create a function analogous to f above. The function for doing so is called ``lambdify``:" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "metadata": {}, 113 | "outputs": [], 114 | "source": [ 115 | "g = sym.lambdify([x, y], expr, modules=['math'])\n", 116 | "g(17, 42)" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": null, 122 | "metadata": {}, 123 | "outputs": [], 124 | "source": [ 125 | "%timeit g(17, 42)" 126 | ] 127 | }, 128 | { 129 | "cell_type": "markdown", 130 | "metadata": {}, 131 | "source": [ 132 | "Note how we specified ``modules`` above: it tells ``lambdify`` to use ``math.log``, if we don't specify modules SymPy will (since v1.1) use ``numpy`` by default. This can be useful when dealing with arrays in the input:" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": null, 138 | "metadata": {}, 139 | "outputs": [], 140 | "source": [ 141 | "import numpy as np\n", 142 | "xarr = np.linspace(17, 18, 5)\n", 143 | "h = sym.lambdify([x, y], expr)\n", 144 | "out = h(xarr, 42)\n", 145 | "out.shape" 146 | ] 147 | }, 148 | { 149 | "cell_type": "markdown", 150 | "metadata": {}, 151 | "source": [ 152 | "NumPy's [broadcasting](https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html) (handling of different shapes) then works as expected:" 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": null, 158 | "metadata": {}, 159 | "outputs": [], 160 | "source": [ 161 | "yarr = np.linspace(42, 43, 7).reshape((1, 7))\n", 162 | "out2 = h(xarr.reshape((5, 1)), yarr) # if we would try to use g() here, it would fail\n", 163 | "out2.shape" 164 | ] 165 | }, 166 | { 167 | "cell_type": "markdown", 168 | "metadata": {}, 169 | "source": [ 170 | "Behind the scenes ``lambdify`` constructs a string representation of the Python code and uses Python's ``eval`` function to compile the function.\n", 171 | "\n", 172 | "Let's now look at how we can get a specific function signature from ``lambdify``:" 173 | ] 174 | }, 175 | { 176 | "cell_type": "code", 177 | "execution_count": null, 178 | "metadata": {}, 179 | "outputs": [], 180 | "source": [ 181 | "z = z1, z2, z3 = sym.symbols('z:3')\n", 182 | "expr2 = x*y*(z1 + z2 + z3)\n", 183 | "func2 = sym.lambdify([x, y, z], expr2)\n", 184 | "func2(1, 2, (3, 4, 5))" 185 | ] 186 | }, 187 | { 188 | "cell_type": "markdown", 189 | "metadata": {}, 190 | "source": [ 191 | "## Exercise: Create a function from a symbolic expression\n", 192 | "Plot $z(x, y) = \\frac{\\partial^2 f(x,y)}{\\partial x \\partial y}$ from above ($f(x, y)$ is available as ``expr``) as a surface plot for $-5 < x < 5, -5 < y < 5$." 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": null, 198 | "metadata": {}, 199 | "outputs": [], 200 | "source": [ 201 | "xplot = np.outer(np.linspace(-5, 5, 100), np.ones(100))\n", 202 | "yplot = xplot.T" 203 | ] 204 | }, 205 | { 206 | "cell_type": "code", 207 | "execution_count": null, 208 | "metadata": {}, 209 | "outputs": [], 210 | "source": [ 211 | "%load_ext scipy2017codegen.exercise" 212 | ] 213 | }, 214 | { 215 | "cell_type": "markdown", 216 | "metadata": {}, 217 | "source": [ 218 | "*Use either the *``%exercise``* or *``%load``* magic to get the exercise / solution respecitvely*" 219 | ] 220 | }, 221 | { 222 | "cell_type": "code", 223 | "execution_count": null, 224 | "metadata": {}, 225 | "outputs": [], 226 | "source": [ 227 | "%exercise exercise_lambdify_expr.py" 228 | ] 229 | }, 230 | { 231 | "cell_type": "markdown", 232 | "metadata": {}, 233 | "source": [ 234 | "Replace **???** with the correct expression above." 235 | ] 236 | }, 237 | { 238 | "cell_type": "code", 239 | "execution_count": null, 240 | "metadata": {}, 241 | "outputs": [], 242 | "source": [ 243 | "from mpl_toolkits.mplot3d import Axes3D\n", 244 | "import matplotlib.pyplot as plt\n", 245 | "%matplotlib inline" 246 | ] 247 | }, 248 | { 249 | "cell_type": "code", 250 | "execution_count": null, 251 | "metadata": {}, 252 | "outputs": [], 253 | "source": [ 254 | "fig = plt.figure(figsize=(15, 13))\n", 255 | "ax = plt.axes(projection='3d')\n", 256 | "ax.plot_surface(xplot, yplot, zplot, cmap=plt.cm.coolwarm)\n", 257 | "ax.set_xlabel('x')\n", 258 | "ax.set_ylabel('y')\n", 259 | "ax.set_zlabel('$%s$' % sym.latex(d2fdxdy))" 260 | ] 261 | }, 262 | { 263 | "cell_type": "code", 264 | "execution_count": null, 265 | "metadata": {}, 266 | "outputs": [], 267 | "source": [] 268 | } 269 | ], 270 | "metadata": { 271 | "kernelspec": { 272 | "display_name": "Python 3", 273 | "language": "python", 274 | "name": "python3" 275 | }, 276 | "language_info": { 277 | "codemirror_mode": { 278 | "name": "ipython", 279 | "version": 3 280 | }, 281 | "file_extension": ".py", 282 | "mimetype": "text/x-python", 283 | "name": "python", 284 | "nbconvert_exporter": "python", 285 | "pygments_lexer": "ipython3", 286 | "version": "3.6.1" 287 | } 288 | }, 289 | "nbformat": 4, 290 | "nbformat_minor": 2 291 | } 292 | -------------------------------------------------------------------------------- /notebooks/23-lambdify-Tc99m.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Using lambdify for plotting expressions\n", 8 | "The syntethic isotope Technetium-99m is used in medical diagnostics ([scintigraphy](https://en.wikipedia.org/wiki/Nuclear_medicine)):\n", 9 | "$$\n", 10 | "^{99m}Tc \\overset{\\lambda_1}{\\longrightarrow} \\,^{99}Tc \\overset{\\lambda_2}{\\longrightarrow} \\,^{99}Ru \\\\\n", 11 | "\\lambda_1 = 3.2\\cdot 10^{-5}\\,s^{-1} \\\\\n", 12 | "\\lambda_2 = 1.04 \\cdot 10^{-13}\\,s^{-1} \\\\\n", 13 | "$$\n", 14 | "SymPy can solve the differential equations describing the amounts versus time analytically.\n", 15 | "Let's denote the concentrations of each isotope $x(t),\\ y(t)\\ \\&\\ z(t)$ respectively." 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "metadata": { 22 | "collapsed": true 23 | }, 24 | "outputs": [], 25 | "source": [ 26 | "import sympy as sym\n", 27 | "sym.init_printing()" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": { 34 | "collapsed": true 35 | }, 36 | "outputs": [], 37 | "source": [ 38 | "symbs = t, l1, l2, x0, y0, z0 = sym.symbols('t lambda_1 lambda_2 x0 y0 z0', real=True, nonnegative=True)\n", 39 | "funcs = x, y, z = [sym.Function(s)(t) for s in 'xyz']\n", 40 | "inits = [f.subs(t, 0) for f in funcs]\n", 41 | "diffs = [f.diff(t) for f in funcs]\n", 42 | "exprs = -l1*x, l1*x - l2*y, l2*y\n", 43 | "eqs = [sym.Eq(diff, expr) for diff, expr in zip(diffs, exprs)]\n", 44 | "eqs" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": null, 50 | "metadata": { 51 | "collapsed": true 52 | }, 53 | "outputs": [], 54 | "source": [ 55 | "solutions = sym.dsolve(eqs)\n", 56 | "solutions" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": {}, 62 | "source": [ 63 | "now we need to determine the integration constants from the intial conditions:" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": null, 69 | "metadata": { 70 | "collapsed": true 71 | }, 72 | "outputs": [], 73 | "source": [ 74 | "integration_constants = set.union(*[sol.free_symbols for sol in solutions]) - set(symbs)\n", 75 | "integration_constants" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": null, 81 | "metadata": { 82 | "collapsed": true 83 | }, 84 | "outputs": [], 85 | "source": [ 86 | "initial_values = [sol.subs(t, 0) for sol in solutions]\n", 87 | "initial_values" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": null, 93 | "metadata": { 94 | "collapsed": true 95 | }, 96 | "outputs": [], 97 | "source": [ 98 | "const_exprs = sym.solve(initial_values, integration_constants)\n", 99 | "const_exprs" 100 | ] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "execution_count": null, 105 | "metadata": { 106 | "collapsed": true 107 | }, 108 | "outputs": [], 109 | "source": [ 110 | "analytic = [sol.subs(const_exprs) for sol in solutions]\n", 111 | "analytic" 112 | ] 113 | }, 114 | { 115 | "cell_type": "markdown", 116 | "metadata": {}, 117 | "source": [ 118 | "## Exercise: Create a function from a symbolic expression\n", 119 | "We want to plot the time evolution of x, y & z from the above analytic expression (called ``analytic`` above):" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": null, 125 | "metadata": { 126 | "collapsed": true 127 | }, 128 | "outputs": [], 129 | "source": [ 130 | "from math import log10\n", 131 | "import numpy as np\n", 132 | "year_s = 365.25*24*3600\n", 133 | "tout = np.logspace(0, log10(3e6*year_s), 500) # 1 s to 3 million years" 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": null, 139 | "metadata": { 140 | "collapsed": true 141 | }, 142 | "outputs": [], 143 | "source": [ 144 | "%load_ext scipy2017codegen.exercise" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "metadata": {}, 150 | "source": [ 151 | "*Use either the *``%exercise``* or *``%load``* magic to get the exercise / solution respecitvely:*\n", 152 | "\n", 153 | "Replace **???** so that `f(t)` evaluates $x(t),\\ y(t)\\ \\&\\ z(t)$. Hint: use the right hand side of the equations in ``analytic`` (use the attribute ``rhs`` of the items in ``anayltic``):" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": null, 159 | "metadata": { 160 | "collapsed": true 161 | }, 162 | "outputs": [], 163 | "source": [ 164 | "# %exercise exercise_Tc99.py\n", 165 | "xyz_num = sym.lambdify([t, l1, l2, *inits], [eq.rhs for eq in analytic])\n", 166 | "yout = xyz_num(tout, 3.2e-5, 1.04e-13, 1, 0, 0)" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": null, 172 | "metadata": { 173 | "collapsed": true 174 | }, 175 | "outputs": [], 176 | "source": [ 177 | "import matplotlib.pyplot as plt\n", 178 | "%matplotlib inline\n", 179 | "fig, ax = plt.subplots(1, 1, figsize=(14, 4))\n", 180 | "ax.loglog(tout.reshape((tout.size, 1)), np.array(yout).T)\n", 181 | "ax.legend(['$^{99m}Tc$', '$^{99}Tc$', '$^{99}Ru$'])\n", 182 | "ax.set_xlabel('Time / s')\n", 183 | "ax.set_ylabel('Concentration / a.u.')\n", 184 | "_ = ax.set_ylim([1e-11, 2])" 185 | ] 186 | } 187 | ], 188 | "metadata": { 189 | "anaconda-cloud": {}, 190 | "kernelspec": { 191 | "display_name": "Python [default]", 192 | "language": "python", 193 | "name": "python3" 194 | }, 195 | "language_info": { 196 | "codemirror_mode": { 197 | "name": "ipython", 198 | "version": 3 199 | }, 200 | "file_extension": ".py", 201 | "mimetype": "text/x-python", 202 | "name": "python", 203 | "nbconvert_exporter": "python", 204 | "pygments_lexer": "ipython3", 205 | "version": "3.5.3" 206 | } 207 | }, 208 | "nbformat": 4, 209 | "nbformat_minor": 2 210 | } 211 | -------------------------------------------------------------------------------- /notebooks/25-chemical-kinetics-intro.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Chemical kinetics\n", 8 | "In chemistry one is often interested in how fast a chemical process proceeds. Chemical reactions (when viewed as single events on a molecular scale) are probabilitic. However, most reactive systems of interest involve very large numbers of molecules (a few grams of a simple substance containts on the order of $10^{23}$ molecules. The sheer number allows us to describe this inherently stochastic process deterministically.\n", 9 | "\n", 10 | "### Law of mass action\n", 11 | "In order to describe chemical reactions as as system of ODEs in terms of concentrations ($c_i$) and time ($t$), one can use the [law of mass action](https://en.wikipedia.org/wiki/Law_of_mass_action):\n", 12 | "\n", 13 | "$$\n", 14 | "\\frac{dc_i}{dt} = \\sum_j S_{ij} r_j\n", 15 | "$$\n", 16 | "where $r_j$ is given by:\n", 17 | "$$\n", 18 | "r_j = k_j\\prod_l c_l^{R_{jl}}\n", 19 | "$$\n", 20 | "\n", 21 | "and $S$ is a matrix with the overall net stoichiometric coefficients (positive for net production, negative for net consumption), and $R$ is a matrix with the multiplicities of each reactant for each equation.\n", 22 | "\n", 23 | "### Example: Nitrosylbromide\n", 24 | "We will now look at the following (bi-directional) chemical reaction:\n", 25 | "\n", 26 | "$$\n", 27 | "\\mathrm{2\\,NO + Br_2 \\leftrightarrow 2\\,NOBr}\n", 28 | "$$\n", 29 | "\n", 30 | "which describes the equilibrium between nitrogen monoxide (NO) and bromine (Br$_2$) and nitrosyl bromide (NOBr). It can be represented as a set of two uni-directional reactions (**f**orward and **b**ackward):\n", 31 | "\n", 32 | "$$\n", 33 | "\\mathrm{2\\,NO + Br_2 \\overset{k_f}{\\rightarrow} 2\\,NOBr} \\\\ \n", 34 | "\\mathrm{2\\,NOBr \\overset{k_b}{\\rightarrow} 2\\,NO + Br_2}\n", 35 | "$$\n", 36 | "\n", 37 | "The law of mass action tells us that the rate of the first process (forward) is proportional to the concentration Br$_2$ and the square of the concentration of NO. The rate of the second reaction (the backward process) is in analogy proportional to the square of the concentration of NOBr. Using the proportionality constants $k_f$ and $k_b$ we can formulate our system of nonlinear ordinary differential equations as follows:\n", 38 | "\n", 39 | "$$\n", 40 | "\\frac{dc_1}{dt} = 2(k_b c_3^2 - k_f c_2 c_1^2) \\\\\n", 41 | "\\frac{dc_2}{dt} = k_b c_3^2 - k_f c_2 c_1^2 \\\\\n", 42 | "\\frac{dc_3}{dt} = 2(k_f c_2 c_1^2 - k_b c_3^2)\n", 43 | "$$\n", 44 | "\n", 45 | "where we have denoted the concentration of NO, Br$_2$, NOBr with $c_1,\\ c_2,\\ c_3$ respectively.\n", 46 | "\n", 47 | "This ODE system corresponds to the following two matrices:\n", 48 | "\n", 49 | "$$\n", 50 | "S = \\begin{bmatrix}\n", 51 | "-2 & 2 \\\\\n", 52 | "-1 & 1 \\\\\n", 53 | "2 & -2\n", 54 | "\\end{bmatrix}\n", 55 | "$$\n", 56 | "\n", 57 | "$$\n", 58 | "R = \\begin{bmatrix}\n", 59 | "2 & 1 & 0 \\\\\n", 60 | "0 & 0 & 2 \n", 61 | "\\end{bmatrix}\n", 62 | "$$\n" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "metadata": {}, 68 | "source": [ 69 | "### Solving the initial value problem numerically\n", 70 | "We will now integrate this system of ordinary differential equations numerically as an initial value problem (IVP) using the ``odeint`` solver provided by ``scipy``:" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": null, 76 | "metadata": {}, 77 | "outputs": [], 78 | "source": [ 79 | "import numpy as np\n", 80 | "from scipy.integrate import odeint" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "metadata": {}, 86 | "source": [ 87 | "By looking at the [documentation](https://docs.scipy.org/doc/scipy-0.19.0/reference/generated/scipy.integrate.odeint.html) of odeint we see that we need to provide a function which computes a vector of derivatives ($\\dot{\\mathbf{y}} = [\\frac{dy_1}{dt}, \\frac{dy_2}{dt}, \\frac{dy_3}{dt}]$). The expected signature of this function is:\n", 88 | "\n", 89 | " f(y: array[float64], t: float64, *args: arbitrary constants) -> dydt: array[float64]\n", 90 | " \n", 91 | "in our case we can write it as:" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": null, 97 | "metadata": {}, 98 | "outputs": [], 99 | "source": [ 100 | "def rhs(y, t, kf, kb):\n", 101 | " rf = kf * y[0]**2 * y[1]\n", 102 | " rb = kb * y[2]**2\n", 103 | " return [2*(rb - rf), rb - rf, 2*(rf - rb)]" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": null, 109 | "metadata": {}, 110 | "outputs": [], 111 | "source": [ 112 | "%load_ext scipy2017codegen.exercise" 113 | ] 114 | }, 115 | { 116 | "cell_type": "markdown", 117 | "metadata": {}, 118 | "source": [ 119 | "Replace **???** by the proper arguments for ``odeint``, you can write ``odeint?`` to read its documentaiton." 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": null, 125 | "metadata": {}, 126 | "outputs": [], 127 | "source": [ 128 | "%exercise exercise_odeint.py" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": null, 134 | "metadata": {}, 135 | "outputs": [], 136 | "source": [ 137 | "import matplotlib.pyplot as plt\n", 138 | "%matplotlib inline" 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": null, 144 | "metadata": {}, 145 | "outputs": [], 146 | "source": [ 147 | "plt.plot(tout, yout)\n", 148 | "_ = plt.legend(['NO', 'Br$_2$', 'NOBr'])" 149 | ] 150 | }, 151 | { 152 | "cell_type": "markdown", 153 | "metadata": {}, 154 | "source": [ 155 | "Writing the ``rhs`` function by hand for larger reaction systems quickly becomes tedious. Ideally we would like to construct it from a symbolic representation (having a symbolic representation of the problem opens up many possibilities as we will soon see). But at the same time, we need the ``rhs`` function to be fast. Which means that we want to produce a fast function from our symbolic representation. Generating a function from our symbolic representation is achieved through *code generation*. \n", 156 | "\n", 157 | "In summary we will need to:\n", 158 | "\n", 159 | "1. Construct a symbolic representation from some domain specific representation using SymPy.\n", 160 | "2. Have SymPy generate a function with an appropriate signature (or multiple thereof), which we pass on to the solver.\n", 161 | "\n", 162 | "We will achieve (1) by using SymPy symbols (and functions if needed). For (2) we will use a function in SymPy called ``lambdify``―it takes a symbolic expressions and returns a function. In a later notebook, we will look at (1), for now we will just use ``rhs`` which we've already written:" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": null, 168 | "metadata": {}, 169 | "outputs": [], 170 | "source": [ 171 | "import sympy as sym\n", 172 | "sym.init_printing()" 173 | ] 174 | }, 175 | { 176 | "cell_type": "code", 177 | "execution_count": null, 178 | "metadata": {}, 179 | "outputs": [], 180 | "source": [ 181 | "y, k = sym.symbols('y:3'), sym.symbols('kf kb')\n", 182 | "ydot = rhs(y, None, *k)\n", 183 | "ydot" 184 | ] 185 | }, 186 | { 187 | "cell_type": "markdown", 188 | "metadata": {}, 189 | "source": [ 190 | "## Exercise\n", 191 | "Now assume that we had constructed ``ydot`` above by applying the more general law of mass action, instead of hard-coding the rate expressions in ``rhs``. Then we could have created a function corresponding to ``rhs`` using ``lambdify``:" 192 | ] 193 | }, 194 | { 195 | "cell_type": "code", 196 | "execution_count": null, 197 | "metadata": {}, 198 | "outputs": [], 199 | "source": [ 200 | "%exercise exercise_lambdify.py" 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": null, 206 | "metadata": {}, 207 | "outputs": [], 208 | "source": [ 209 | "plt.plot(tout, odeint(f, y0, tout, k_vals))\n", 210 | "_ = plt.legend(['NO', 'Br$_2$', 'NOBr'])" 211 | ] 212 | }, 213 | { 214 | "cell_type": "markdown", 215 | "metadata": {}, 216 | "source": [ 217 | "In this example the gains of using a symbolic representation are arguably limited. However, it is quite common that the numerical solver will need another function which calculates the [Jacobian](https://en.wikipedia.org/wiki/Jacobian_matrix_and_determinant) of $\\dot{\\mathbf{y}}$ (given as Dfun in the case of ``odeint``). Writing that by hand is both tedious and error prone. But SymPy solves both of those issues:" 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": null, 223 | "metadata": {}, 224 | "outputs": [], 225 | "source": [ 226 | "sym.Matrix(ydot).jacobian(y)" 227 | ] 228 | }, 229 | { 230 | "cell_type": "markdown", 231 | "metadata": {}, 232 | "source": [ 233 | "In the next notebook we will look at an example where providing this as a function is beneficial for performance." 234 | ] 235 | } 236 | ], 237 | "metadata": { 238 | "kernelspec": { 239 | "display_name": "Python 3", 240 | "language": "python", 241 | "name": "python3" 242 | }, 243 | "language_info": { 244 | "codemirror_mode": { 245 | "name": "ipython", 246 | "version": 3 247 | }, 248 | "file_extension": ".py", 249 | "mimetype": "text/x-python", 250 | "name": "python", 251 | "nbconvert_exporter": "python", 252 | "pygments_lexer": "ipython3", 253 | "version": "3.6.1" 254 | } 255 | }, 256 | "nbformat": 4, 257 | "nbformat_minor": 2 258 | } 259 | -------------------------------------------------------------------------------- /notebooks/32-chemical-kinetics-symbolic-construction.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Symbolic representation of chemical kinetics\n", 8 | "As we saw in the previous notebook, the rate of chemical reactions can be described by ordinary differential equations.\n", 9 | "\n", 10 | "In this notebook we will look at a classic example in chemical kinetics:\n", 11 | "[Robertson's example](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.53.8603).\n", 12 | "It is a 3-species reaction system:\n", 13 | "\n", 14 | "$$\n", 15 | "A \\overset{k_1}{\\rightarrow} B \\\\\n", 16 | "B + C \\overset{k_2}{\\rightarrow} A + C \\\\\n", 17 | "2 B \\overset{k_3}{\\rightarrow} B + C\n", 18 | "$$\n", 19 | "\n", 20 | "where A, B and C represent three different chemical species (e.g. reactive molecules dissolved in water). The system is interesting from a numerical point of view because the stepping needs to be performed using an implicit method (requiring the Jacobian) when solving for large times.\n", 21 | "\n", 22 | "The rate of each process follows the [law of mass action](https://en.wikipedia.org/wiki/Law_of_mass_action), i.e. the rate is proportional to the concentration of each reacting species (to the power of their multiplicity). The proportionality constant is known as the rate constant of the reaction ($k_1,\\ k_2\\ \\&\\ k_3$ in our case). If we denote the rate of each reaction:\n", 23 | "\n", 24 | "$$\n", 25 | "r_1 = k_1[A] \\\\\n", 26 | "r_2 = k_2[B][C] \\\\\n", 27 | "r_3 = k_3[B]^2\n", 28 | "$$\n", 29 | "\n", 30 | "$[A],\\ [B],\\ [C]$ denotes the concentration of respective species. We can now formulate a system of ordinary differential equations describing how the concentrations evolve over time:\n", 31 | "\n", 32 | "$$\n", 33 | "\\frac{d[A]}{dt} = r_2 - r_1 \\\\\n", 34 | "\\frac{d[B]}{dt} = r_1 - r_2 - r_3 \\\\\n", 35 | "\\frac{d[C]}{dt} = r_3\n", 36 | "$$\n", 37 | "\n", 38 | "We will now express these differential equations (and their Jacobian) symbolically using SymPy. We saw in the previous notebook how we can use matrices with integer coefficients to describe the system of ODEs. In practice, those matrices, will consist of mostly zeros. So dictionaries are a better suited data structure (using dictionaries is one way to represent sparse matrices).\n", 39 | "\n", 40 | "Let us therefore write a function, which creactes SymPy expressions from dictionaries mapping strings (names) to integers (coefficients). Here is one possible representation of Robertson's example:" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "metadata": {}, 47 | "outputs": [], 48 | "source": [ 49 | "reactions = [\n", 50 | " # (coeff, r_stoich, net_stoich)\n", 51 | " ('k1', {'A': 1}, {'B': 1, 'A': -1}),\n", 52 | " ('k2', {'B': 1, 'C': 1}, {'A': 1, 'B': -1}),\n", 53 | " ('k3', {'B': 2}, {'B': -1, 'C': 1})\n", 54 | "]\n", 55 | "names = 'A B C'.split()" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "## Exercise: Create SymPy expressions from simple data structures\n", 63 | "You will need to complete ``mk_exprs_symbs`` (replace **???** with valid expression) which constructs symbolic expressions following the law of mass action." 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": null, 69 | "metadata": {}, 70 | "outputs": [], 71 | "source": [ 72 | "%load_ext scipy2017codegen.exercise" 73 | ] 74 | }, 75 | { 76 | "cell_type": "markdown", 77 | "metadata": {}, 78 | "source": [ 79 | "Use either the ``%exercise`` or ``%load`` magic to get the exercise / solution respectively:" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": null, 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "%exercise exercise_symbolic.py" 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "metadata": {}, 94 | "source": [ 95 | "To complete the above exercise you may want to remember what the law of mass action looks like from the previous notebook:\n", 96 | "\n", 97 | "$$\n", 98 | "\\frac{dc_i}{dt} = \\sum_j S_{ij} r_j \\\\\n", 99 | "r_j = k_j\\prod_l c_l^{R_{jl}}\n", 100 | "$$\n", 101 | "\n", 102 | "where $k_j$, S and R corresponds to ``coeff``, ``net_stoich`` and ``r_stoich`` respectively." 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": null, 108 | "metadata": {}, 109 | "outputs": [], 110 | "source": [ 111 | "sym.init_printing()\n", 112 | "ydot, y, k = mk_exprs_symbs(reactions, names)\n", 113 | "ydot" 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "metadata": {}, 119 | "source": [ 120 | "$\\dot{\\mathbf{y}}$ now represent our ODE system, where $\\mathbf{y}$ is our state vector (concentrations). We will need a callback to evaluate $\\dot{\\mathbf{y}}$ when we integrate this ODE system numerically (using ``scipy.integrate.odeint``). As we have seen SymPy can provide us with this callback:" 121 | ] 122 | }, 123 | { 124 | "cell_type": "code", 125 | "execution_count": null, 126 | "metadata": {}, 127 | "outputs": [], 128 | "source": [ 129 | "t = sym.symbols('t') # not used in this case.\n", 130 | "f = sym.lambdify((y, t) + k, ydot)" 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": null, 136 | "metadata": {}, 137 | "outputs": [], 138 | "source": [ 139 | "import numpy as np\n", 140 | "from scipy.integrate import odeint" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": null, 146 | "metadata": {}, 147 | "outputs": [], 148 | "source": [ 149 | "tout = np.logspace(-6, 6)\n", 150 | "k_vals = (0.04, 1e4, 3e7) # from the literature\n", 151 | "y0 = [1, 0, 0]\n", 152 | "yout, info = odeint(f, y0, tout, k_vals, full_output=True)" 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": null, 158 | "metadata": {}, 159 | "outputs": [], 160 | "source": [ 161 | "import matplotlib.pyplot as plt\n", 162 | "%matplotlib inline" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": null, 168 | "metadata": {}, 169 | "outputs": [], 170 | "source": [ 171 | "plt.loglog(tout, yout)\n", 172 | "plt.legend(names)\n", 173 | "print(\"The Jacobian was evaluated %d times.\" % info['nje'][-1])" 174 | ] 175 | }, 176 | { 177 | "cell_type": "markdown", 178 | "metadata": {}, 179 | "source": [ 180 | "\n", 181 | "\n", 182 | "If we look closer at the info-dictionary we will see that odeint (or rather LSODA which is the unerlying package) switched method from an explicit Adams method to an implicit Backward Differentiation Formula (BDF). It is common for chemical kinetics problems that the problem becomes stiff.\n", 183 | "\n", 184 | "By default, the solver will approximate the elements in the Jacobian matrix by taking finite differences of $\\mathbf{f}$. This is often works quite satisfactorily, but for larger systems it sometimes fails. A more robust (and faster) approach is to provide a callback which evaluates an analytic Jacobian. Using SymPy we can do this quite effortlessly:" 185 | ] 186 | }, 187 | { 188 | "cell_type": "markdown", 189 | "metadata": {}, 190 | "source": [ 191 | "## Exercise: Derive a Jacobian symbolically and generate a function evaluating it\n", 192 | "You may want to consult ``help(odeint)`` for the requested function signature of ``Dfun``." 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": null, 198 | "metadata": {}, 199 | "outputs": [], 200 | "source": [ 201 | "%exercise exercise_lambdify_jac.py" 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": null, 207 | "metadata": {}, 208 | "outputs": [], 209 | "source": [ 210 | "yout, info = odeint(f, y0, tout, k_vals, full_output=True, Dfun=J_cb)" 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": null, 216 | "metadata": {}, 217 | "outputs": [], 218 | "source": [ 219 | "plt.loglog(tout, yout)\n", 220 | "plt.legend(names)\n", 221 | "print(\"The Jacobian was evaluated %d times.\" % info['nje'][-1])" 222 | ] 223 | }, 224 | { 225 | "cell_type": "markdown", 226 | "metadata": {}, 227 | "source": [ 228 | "We see that the solver needed to evaluate the Jacobian fewer times (due to it being essentially exact this time around). For larger systems the impact of an analytic Jacobian is often even greater (being the difference between a failed and successful integration). \n", 229 | "\n", 230 | "Benchmarking with and without the analytic Jacobian callback:" 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": null, 236 | "metadata": {}, 237 | "outputs": [], 238 | "source": [ 239 | "%timeit odeint(f, y0, tout, k_vals)" 240 | ] 241 | }, 242 | { 243 | "cell_type": "code", 244 | "execution_count": null, 245 | "metadata": {}, 246 | "outputs": [], 247 | "source": [ 248 | "%timeit odeint(f, y0, tout, k_vals, Dfun=J_cb)" 249 | ] 250 | } 251 | ], 252 | "metadata": { 253 | "kernelspec": { 254 | "display_name": "Python 3", 255 | "language": "python", 256 | "name": "python3" 257 | }, 258 | "language_info": { 259 | "codemirror_mode": { 260 | "name": "ipython", 261 | "version": 3 262 | }, 263 | "file_extension": ".py", 264 | "mimetype": "text/x-python", 265 | "name": "python", 266 | "nbconvert_exporter": "python", 267 | "pygments_lexer": "ipython3", 268 | "version": "3.6.1" 269 | } 270 | }, 271 | "nbformat": 4, 272 | "nbformat_minor": 2 273 | } 274 | -------------------------------------------------------------------------------- /notebooks/40-chemical-kinetics-cython.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "In this notebook we will look how we can use Cython to generate a faster callback and hopefully shave off some running time from our integration." 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": { 14 | "collapsed": true 15 | }, 16 | "outputs": [], 17 | "source": [ 18 | "import json\n", 19 | "import numpy as np\n", 20 | "from scipy2017codegen.odesys import ODEsys\n", 21 | "from scipy2017codegen.chem import mk_rsys" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | "The `ODEsys` class and convenience functions from previous notebook (35) has been put in two modules for easy importing. Recapping what we did last:" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": null, 34 | "metadata": { 35 | "collapsed": true 36 | }, 37 | "outputs": [], 38 | "source": [ 39 | "watrad_data = json.load(open('../scipy2017codegen/data/radiolysis_300_Gy_s.json'))\n", 40 | "watrad = mk_rsys(ODEsys, **watrad_data)\n", 41 | "tout = np.logspace(-6, 3, 200) # close to one hour of operation\n", 42 | "c0 = {'H2O': 55.4e3, 'H+': 1e-4, 'OH-': 1e-4}\n", 43 | "y0 = [c0.get(symb.name, 0) for symb in watrad.y]" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": null, 49 | "metadata": { 50 | "collapsed": true 51 | }, 52 | "outputs": [], 53 | "source": [ 54 | "%timeit yout, info = watrad.integrate_odeint(tout, y0)" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": {}, 60 | "source": [ 61 | "so that is the benchmark to beat, we will export our expressions as Cython code. We then subclass `ODEsys` to have it render, compile and import the code:" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": null, 67 | "metadata": { 68 | "collapsed": true 69 | }, 70 | "outputs": [], 71 | "source": [ 72 | "# %load ../scipy2017codegen/odesys_cython.py\n", 73 | "import uuid\n", 74 | "import numpy as np\n", 75 | "import sympy as sym\n", 76 | "import setuptools\n", 77 | "import pyximport\n", 78 | "from scipy2017codegen import templates\n", 79 | "from scipy2017codegen.odesys import ODEsys\n", 80 | "\n", 81 | "pyximport.install()\n", 82 | "\n", 83 | "cython_template = \"\"\"\n", 84 | "cimport numpy as cnp\n", 85 | "import numpy as np\n", 86 | "\n", 87 | "def f(cnp.ndarray[cnp.float64_t, ndim=1] y, double t, %(args)s):\n", 88 | " cdef cnp.ndarray[cnp.float64_t, ndim=1] out = np.empty(y.size)\n", 89 | " %(f_exprs)s\n", 90 | " return out\n", 91 | "\n", 92 | "def j(cnp.ndarray[cnp.float64_t, ndim=1] y, double t, %(args)s):\n", 93 | " cdef cnp.ndarray[cnp.float64_t, ndim=2] out = np.empty((y.size, y.size))\n", 94 | " %(j_exprs)s\n", 95 | " return out\n", 96 | "\n", 97 | "\"\"\"\n", 98 | "\n", 99 | "class CythonODEsys(ODEsys):\n", 100 | "\n", 101 | " def setup(self):\n", 102 | " self.mod_name = 'ode_cython_%s' % uuid.uuid4().hex[:10]\n", 103 | " idxs = list(range(len(self.f)))\n", 104 | " subs = {s: sym.Symbol('y[%d]' % i) for i, s in enumerate(self.y)}\n", 105 | " f_exprs = ['out[%d] = %s' % (i, str(self.f[i].xreplace(subs))) for i in idxs]\n", 106 | " j_exprs = ['out[%d, %d] = %s' % (ri, ci, self.j[ri, ci].xreplace(subs)) for ri in idxs for ci in idxs]\n", 107 | " ctx = dict(\n", 108 | " args=', '.join(map(str, self.p)),\n", 109 | " f_exprs = '\\n '.join(f_exprs),\n", 110 | " j_exprs = '\\n '.join(j_exprs),\n", 111 | " )\n", 112 | " open('%s.pyx' % self.mod_name, 'wt').write(cython_template % ctx)\n", 113 | " open('%s.pyxbld' % self.mod_name, 'wt').write(templates.pyxbld % dict(\n", 114 | " sources=[], include_dirs=[np.get_include()],\n", 115 | " library_dirs=[], libraries=[], extra_compile_args=[], extra_link_args=[]\n", 116 | " ))\n", 117 | " mod = __import__(self.mod_name)\n", 118 | " self.f_eval = mod.f\n", 119 | " self.j_eval = mod.j\n" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": null, 125 | "metadata": { 126 | "collapsed": true 127 | }, 128 | "outputs": [], 129 | "source": [ 130 | "cython_sys = mk_rsys(CythonODEsys, **watrad_data)" 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": null, 136 | "metadata": { 137 | "collapsed": true 138 | }, 139 | "outputs": [], 140 | "source": [ 141 | "%timeit cython_sys.integrate(tout, y0)" 142 | ] 143 | }, 144 | { 145 | "cell_type": "markdown", 146 | "metadata": {}, 147 | "source": [ 148 | "That is a considerable speed up from before. But the solver still has to\n", 149 | "allocate memory for creating new arrays at each call, and each evaluation\n", 150 | "has to pass the python layer which is now the bottleneck for the integration.\n", 151 | "\n", 152 | "In order to speed up integration further we need to make sure the solver can evaluate the function and Jacobian without calling into Python." 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": null, 158 | "metadata": { 159 | "collapsed": true 160 | }, 161 | "outputs": [], 162 | "source": [ 163 | "import matplotlib.pyplot as plt\n", 164 | "%matplotlib inline" 165 | ] 166 | }, 167 | { 168 | "cell_type": "markdown", 169 | "metadata": {}, 170 | "source": [ 171 | "Just to see that everything looks alright:" 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": null, 177 | "metadata": { 178 | "collapsed": true 179 | }, 180 | "outputs": [], 181 | "source": [ 182 | "fig, ax = plt.subplots(1, 1, figsize=(14, 6))\n", 183 | "cython_sys.plot_result(tout, *cython_sys.integrate_odeint(tout, y0), ax=ax)\n", 184 | "ax.set_xscale('log')\n", 185 | "ax.set_yscale('log')" 186 | ] 187 | } 188 | ], 189 | "metadata": { 190 | "kernelspec": { 191 | "display_name": "Python 3", 192 | "language": "python", 193 | "name": "python3" 194 | }, 195 | "language_info": { 196 | "codemirror_mode": { 197 | "name": "ipython", 198 | "version": 3 199 | }, 200 | "file_extension": ".py", 201 | "mimetype": "text/x-python", 202 | "name": "python", 203 | "nbconvert_exporter": "python", 204 | "pygments_lexer": "ipython3", 205 | "version": "3.6.1" 206 | } 207 | }, 208 | "nbformat": 4, 209 | "nbformat_minor": 2 210 | } 211 | -------------------------------------------------------------------------------- /notebooks/45-chemical-kinetics-cython-vode.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "In this notebook we will use the same Cython code as in the last notebook. However, this time we will use the `Vode` integrator from `ODEPACK` (available in SciPy in `scipy.integrate.ode`). The reason for this is that it will be a fairer comparison against our upcoming example using `CVode`." 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "import json\n", 17 | "import numpy as np" 18 | ] 19 | }, 20 | { 21 | "cell_type": "markdown", 22 | "metadata": {}, 23 | "source": [ 24 | "Subclassing `ODEsys` and providing a new method using `scipy.integrate.ode`:" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "# %load ../scipy2017codegen/odesys_vode.py\n", 34 | "import numpy as np\n", 35 | "from scipy.integrate import ode\n", 36 | "from scipy2017codegen.odesys import ODEsys\n", 37 | "\n", 38 | "class VODEsys(ODEsys):\n", 39 | " default_integrator = 'vode'\n", 40 | "\n", 41 | " def integrate_vode(self, tout, y0, params=(), method='bdf', rtol=1e-8, atol=1e-8, **kwargs):\n", 42 | " def f(t, y, *args):\n", 43 | " f.ncall +=1\n", 44 | " return np.asarray(self.f_eval(y, t, *args))\n", 45 | " f.ncall = 0\n", 46 | " def j(t, y, *args):\n", 47 | " j.ncall += 1\n", 48 | " return np.asarray(self.j_eval(y, t, *args))\n", 49 | " j.ncall = 0\n", 50 | " r = ode(f, j)\n", 51 | " r.set_integrator('vode', method=method, rtol=rtol, atol=atol, **kwargs)\n", 52 | " if params:\n", 53 | " r.set_f_params(params)\n", 54 | " r.set_jac_params(params)\n", 55 | " yout = np.zeros((len(tout), len(y0)))\n", 56 | " yout[0, :] = y0\n", 57 | " r.set_initial_value(yout[0, :], tout[0])\n", 58 | " for idx in range(1, len(tout)):\n", 59 | " r.integrate(tout[idx])\n", 60 | " assert r.successful(), \"Integration failed\"\n", 61 | " yout[idx, :] = r.y\n", 62 | " return yout, {'num_rhs': f.ncall, 'num_dls_jac_evals': j.ncall}\n" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "metadata": {}, 68 | "source": [ 69 | "Creating a new mixin class:" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "from scipy2017codegen.odesys_cython import CythonODEsys\n", 79 | "\n", 80 | "class CythonVODEsys(VODEsys, CythonODEsys):\n", 81 | " pass" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "metadata": {}, 87 | "source": [ 88 | "Same procedure as in the last notebook:" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": null, 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "from scipy2017codegen.chem import mk_rsys" 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": null, 103 | "metadata": {}, 104 | "outputs": [], 105 | "source": [ 106 | "watrad_data = json.load(open('../scipy2017codegen/data/radiolysis_300_Gy_s.json'))\n", 107 | "watrad = mk_rsys(ODEsys, **watrad_data)\n", 108 | "tout = np.logspace(-6, 3, 200) # close to one hour of operation\n", 109 | "c0 = {'H2O': 55.4e3, 'H+': 1e-4, 'OH-': 1e-4}\n", 110 | "y0 = [c0.get(symb.name, 0) for symb in watrad.y]" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": null, 116 | "metadata": {}, 117 | "outputs": [], 118 | "source": [ 119 | "cython_sys = mk_rsys(CythonVODEsys, **watrad_data)" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": null, 125 | "metadata": {}, 126 | "outputs": [], 127 | "source": [ 128 | "%timeit cython_sys.integrate(tout, y0)" 129 | ] 130 | }, 131 | { 132 | "cell_type": "markdown", 133 | "metadata": {}, 134 | "source": [ 135 | "That is a considerably slower than `odeint`. It is clear that it is the python wrapper (in scipy) that is the bottleneck. Especially since using `Vode` and choosing `BDF` for this stiff problem will avoid the method swaps LSODA attempts." 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": null, 141 | "metadata": {}, 142 | "outputs": [], 143 | "source": [ 144 | "import matplotlib.pyplot as plt\n", 145 | "%matplotlib inline" 146 | ] 147 | }, 148 | { 149 | "cell_type": "markdown", 150 | "metadata": {}, 151 | "source": [ 152 | "Just to see that everything looks alright:" 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": null, 158 | "metadata": {}, 159 | "outputs": [], 160 | "source": [ 161 | "fig, ax = plt.subplots(1, 1, figsize=(14, 6))\n", 162 | "cython_sys.plot_result(tout, *cython_sys.integrate_vode(tout, y0), ax=ax)\n", 163 | "ax.set_xscale('log')\n", 164 | "ax.set_yscale('log')" 165 | ] 166 | } 167 | ], 168 | "metadata": { 169 | "kernelspec": { 170 | "display_name": "Python 3", 171 | "language": "python", 172 | "name": "python3" 173 | }, 174 | "language_info": { 175 | "codemirror_mode": { 176 | "name": "ipython", 177 | "version": 3 178 | }, 179 | "file_extension": ".py", 180 | "mimetype": "text/x-python", 181 | "name": "python", 182 | "nbconvert_exporter": "python", 183 | "pygments_lexer": "ipython3", 184 | "version": "3.6.1" 185 | } 186 | }, 187 | "nbformat": 4, 188 | "nbformat_minor": 2 189 | } 190 | -------------------------------------------------------------------------------- /notebooks/50-chemical-kinetics-C.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "In this notebook we will generate C code and use CVode from the sundials suite to integrate the system of differential equations. Sundials is a well established and well tested open-source BSD-licensed library with excellent documentation. [Here is the documentation for CVode](https://computation.llnl.gov/sites/default/files/public/cv_guide.pdf).\n", 8 | "\n", 9 | "We will use a thin Cython wrapper which calls the integration routine and returns the solution as a numpy vector and a dictionary, along with information about the integration." 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "metadata": { 16 | "collapsed": true 17 | }, 18 | "outputs": [], 19 | "source": [ 20 | "import json\n", 21 | "import numpy as np\n", 22 | "from scipy2017codegen.odesys import ODEsys\n", 23 | "from scipy2017codegen.chem import mk_rsys" 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "Again, using the `ODEsys` convenience class from notebook \"35\":" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": null, 36 | "metadata": { 37 | "collapsed": true 38 | }, 39 | "outputs": [], 40 | "source": [ 41 | "watrad_data = json.load(open('../scipy2017codegen/data/radiolysis_300_Gy_s.json'))\n", 42 | "watrad = mk_rsys(ODEsys, **watrad_data)\n", 43 | "tout = np.logspace(-6, 3, 200) # close to one hour of operation\n", 44 | "c0 = {'H2O': 55.4e3, 'H+': 1e-4, 'OH-': 1e-4}\n", 45 | "y0 = [c0.get(symb.name, 0) for symb in watrad.y]" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": null, 51 | "metadata": { 52 | "collapsed": true 53 | }, 54 | "outputs": [], 55 | "source": [ 56 | "%timeit yout, info = watrad.integrate_odeint(tout, y0)" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": {}, 62 | "source": [ 63 | "that is still the benchmark to beat. Subclassing `ODEsys` to have it render, compile and import the code:" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": null, 69 | "metadata": { 70 | "collapsed": true 71 | }, 72 | "outputs": [], 73 | "source": [ 74 | "# %load ../scipy2017codegen/odesys_cvode.py\n", 75 | "import os\n", 76 | "import sys\n", 77 | "import uuid\n", 78 | "import sympy as sym\n", 79 | "import setuptools\n", 80 | "import numpy as np\n", 81 | "import setuptools\n", 82 | "import pyximport\n", 83 | "from scipy2017codegen import templates\n", 84 | "from scipy2017codegen.odesys import ODEsys\n", 85 | "\n", 86 | "pyximport.install()\n", 87 | "\n", 88 | "kw = {\n", 89 | " 'sources': [],\n", 90 | " 'include_dirs': [os.getcwd(), np.get_include()],\n", 91 | " 'libraries': ['sundials_cvode', 'sundials_nvecserial'],\n", 92 | " 'library_dirs': [],\n", 93 | " 'extra_compile_args': [],\n", 94 | " 'extra_link_args': []\n", 95 | "}\n", 96 | "\n", 97 | "osx = sys.platform.lower() == 'darwin'\n", 98 | "win = os.name == 'nt'\n", 99 | "posix = os.name == 'posix'\n", 100 | "\n", 101 | "if not win:\n", 102 | " kw['libraries'] += ['m']\n", 103 | "\n", 104 | "if posix:\n", 105 | " kw['libraries'] += ['openblas']\n", 106 | "\n", 107 | "\n", 108 | "class ODEcvode(ODEsys):\n", 109 | "\n", 110 | " default_integrator = 'cvode'\n", 111 | "\n", 112 | " def setup(self):\n", 113 | " self.uid = uuid.uuid4().hex[:10]\n", 114 | " self.mod_name = 'ode_c_%s' % self.uid\n", 115 | " idxs = list(range(len(self.f)))\n", 116 | " subs = {s: sym.Symbol('y[%d]' % i) for i, s in enumerate(self.y)}\n", 117 | " f_exprs = ['out[%d] = %s;' % (i, sym.ccode(self.f[i].xreplace(subs)))\n", 118 | " for i in idxs]\n", 119 | " j_col_defs = ['realtype * const col_%d = DENSE_COL(J, %d);' % (ci, ci)\n", 120 | " for ci in idxs]\n", 121 | " j_exprs = ['col_%d[%d] = %s;' % (ci, ri, self.j[ri, ci].xreplace(subs))\n", 122 | " for ci in idxs for ri in idxs if self.j[ri, ci] != 0]\n", 123 | " ctx = dict(\n", 124 | " func = '\\n '.join(f_exprs + ['return 0;']),\n", 125 | " dense_jac = '\\n '.join(j_col_defs + j_exprs + ['return 0;']),\n", 126 | " band_jac = 'return -1;'\n", 127 | " )\n", 128 | " open('integrate_serial_%s.c' % self.uid, 'wt').write(templates.sundials['integrate_serial.c'] % ctx)\n", 129 | " open('%s.pyx' % self.mod_name, 'wt').write(templates.sundials['_integrate_serial.pyx'] % {'uid': self.uid})\n", 130 | " open('%s.pyxbld' % self.mod_name, 'wt').write(templates.pyxbld % kw)\n", 131 | " self.mod = __import__(self.mod_name)\n", 132 | " self.integrate_odeint = None\n", 133 | "\n", 134 | " def integrate_cvode(self, tout, y0, params=(), rtol=1e-8, atol=1e-8, **kwargs):\n", 135 | " return self.mod._integrate(np.asarray(tout, dtype=np.float64),\n", 136 | " np.asarray(y0, dtype=np.float64),\n", 137 | " np.atleast_1d(np.asarray(params, dtype=np.float64)),\n", 138 | " abstol=np.atleast_1d(np.asarray(atol, dtype=np.float64)),\n", 139 | " reltol=rtol,\n", 140 | " **kwargs)\n" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": null, 146 | "metadata": { 147 | "collapsed": true 148 | }, 149 | "outputs": [], 150 | "source": [ 151 | "cvode_sys = mk_rsys(ODEcvode, **watrad_data)" 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": null, 157 | "metadata": { 158 | "collapsed": true 159 | }, 160 | "outputs": [], 161 | "source": [ 162 | "%timeit cvode_sys.integrate(tout, y0)" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "metadata": {}, 168 | "source": [ 169 | "Now we are getting close to optimal speed. There is no interaction with the Python interpreter during integration." 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": null, 175 | "metadata": { 176 | "collapsed": true 177 | }, 178 | "outputs": [], 179 | "source": [ 180 | "import matplotlib.pyplot as plt\n", 181 | "%matplotlib inline" 182 | ] 183 | }, 184 | { 185 | "cell_type": "markdown", 186 | "metadata": {}, 187 | "source": [ 188 | "Just to see that everything looks alright:" 189 | ] 190 | }, 191 | { 192 | "cell_type": "code", 193 | "execution_count": null, 194 | "metadata": { 195 | "collapsed": true 196 | }, 197 | "outputs": [], 198 | "source": [ 199 | "fig, ax = plt.subplots(1, 1, figsize=(14, 6))\n", 200 | "cvode_sys.plot_result(tout, *cvode_sys.integrate(tout, y0), ax=ax)\n", 201 | "ax.set_xscale('log')\n", 202 | "ax.set_yscale('log')" 203 | ] 204 | } 205 | ], 206 | "metadata": { 207 | "kernelspec": { 208 | "display_name": "Python 3", 209 | "language": "python", 210 | "name": "python3" 211 | }, 212 | "language_info": { 213 | "codemirror_mode": { 214 | "name": "ipython", 215 | "version": 3 216 | }, 217 | "file_extension": ".py", 218 | "mimetype": "text/x-python", 219 | "name": "python", 220 | "nbconvert_exporter": "python", 221 | "pygments_lexer": "ipython3", 222 | "version": "3.6.1" 223 | } 224 | }, 225 | "nbformat": 4, 226 | "nbformat_minor": 2 227 | } 228 | -------------------------------------------------------------------------------- /notebooks/55-C-calling-3rd-party-lib.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Generating C code calling a third-party library\n", 8 | "In this notebook we will look at how we can customize the `CCodePrinter` to generate C code which calls functions provided by a 3rd party library. For this example we will use [fastapprox](https://github.com/pmineiro/fastapprox) which is an open source [header only](https://en.wikipedia.org/wiki/Header-only) library containing fast approximate versions of transcendental functions. Will will look into using fastexp instead of exp." 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "metadata": {}, 15 | "outputs": [], 16 | "source": [ 17 | "import sympy as sym\n", 18 | "sym.init_printing()" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": null, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "args = x, y, z = sym.symbols('x y z')\n", 28 | "expr = x + sym.exp(2*y + x) + sym.exp(3*z)\n", 29 | "expr" 30 | ] 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "metadata": {}, 35 | "source": [ 36 | "say that we need to evaluate this expression for vectors of values of x, y & z:" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": null, 42 | "metadata": {}, 43 | "outputs": [], 44 | "source": [ 45 | "import numpy as np\n", 46 | "N = 4096\n", 47 | "inp = np.linspace(10, 20, N), np.linspace(-20, -30, N), np.linspace(-9, -3, N)" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": null, 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "f = sym.lambdify(args, expr, modules=['numpy'])\n", 57 | "f(*inp)" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": null, 63 | "metadata": {}, 64 | "outputs": [], 65 | "source": [ 66 | "%timeit f(*inp)" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": null, 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "from sympy.utilities.autowrap import ufuncify" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": null, 81 | "metadata": {}, 82 | "outputs": [], 83 | "source": [ 84 | "uf = ufuncify(args, expr)" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": null, 90 | "metadata": {}, 91 | "outputs": [], 92 | "source": [ 93 | "uf(*inp)" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": null, 99 | "metadata": {}, 100 | "outputs": [], 101 | "source": [ 102 | "%timeit uf(*inp)" 103 | ] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "metadata": {}, 108 | "source": [ 109 | "The fact that lambdify is about as fast as our ufuncify tells us that we are bound by the time to evaluate expensive transcendental ``exp``. For those of you who know Cython you can see that in a Cython version as well:" 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": null, 115 | "metadata": {}, 116 | "outputs": [], 117 | "source": [ 118 | "%load_ext cython" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": null, 124 | "metadata": {}, 125 | "outputs": [], 126 | "source": [ 127 | "from scipy2017codegen.templates import render_pyxbld\n", 128 | "render_pyxbld('cy_f_mod', include_dirs=[np.get_include()])" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": null, 134 | "metadata": {}, 135 | "outputs": [], 136 | "source": [ 137 | "%%cython_pyximport cy_f_mod\n", 138 | "from libc.math cimport exp\n", 139 | "cimport numpy as cnp\n", 140 | "import numpy as np\n", 141 | "import cython\n", 142 | "\n", 143 | "def cy_f(\n", 144 | " cnp.ndarray[cnp.float64_t, ndim=1, mode='c'] x,\n", 145 | " cnp.ndarray[cnp.float64_t, ndim=1, mode='c'] y,\n", 146 | " cnp.ndarray[cnp.float64_t, ndim=1, mode='c'] z,\n", 147 | "):\n", 148 | " cdef cnp.ndarray[cnp.float64_t, ndim=1, mode='c'] out = np.empty(x.size)\n", 149 | " cdef double * _x = &x[0]\n", 150 | " cdef double * _y = &y[0]\n", 151 | " cdef double * _z = &z[0]\n", 152 | " cdef double * _out = &out[0]\n", 153 | " cdef int i\n", 154 | " if x.size != y.size or x.size != z.size:\n", 155 | " raise ValueError(\"Inconsistent length\")\n", 156 | " for i in range(x.size):\n", 157 | " _out[i] = _x[i] + exp(2*_y[i] + _x[i]) + exp(3*_z[i])\n", 158 | " return out" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": null, 164 | "metadata": {}, 165 | "outputs": [], 166 | "source": [ 167 | "cy_f(*inp)" 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": null, 173 | "metadata": {}, 174 | "outputs": [], 175 | "source": [ 176 | "%timeit cy_f(*inp)" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": {}, 182 | "source": [ 183 | "So let's try to use ``fastexp`` from ``fastapprox``:" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": null, 189 | "metadata": {}, 190 | "outputs": [], 191 | "source": [ 192 | "import os\n", 193 | "import scipy2017codegen\n", 194 | "fastapprox_dir = os.path.join(os.path.dirname(scipy2017codegen.__file__), 'fastapprox')\n", 195 | "print(''.join(open(os.path.join(fastapprox_dir, 'fastexp.h')).readlines()[62:67]))" 196 | ] 197 | }, 198 | { 199 | "cell_type": "code", 200 | "execution_count": null, 201 | "metadata": {}, 202 | "outputs": [], 203 | "source": [ 204 | "sym.ccode(expr, user_functions={'exp': 'fastexp'})" 205 | ] 206 | }, 207 | { 208 | "cell_type": "code", 209 | "execution_count": null, 210 | "metadata": {}, 211 | "outputs": [], 212 | "source": [ 213 | "render_pyxbld('cy_f_fastexp_mod', include_dirs=[np.get_include()])" 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": null, 219 | "metadata": {}, 220 | "outputs": [], 221 | "source": [ 222 | "%%cython_pyximport cy_f_fastexp_mod\n", 223 | "from libc.math cimport exp\n", 224 | "cimport numpy as cnp\n", 225 | "import numpy as np\n", 226 | "import cython\n", 227 | "\n", 228 | "cdef extern from \"fastapprox/fastexp.h\":\n", 229 | " float fastexp(float)\n", 230 | "\n", 231 | "def cy_f_fastexp(\n", 232 | " cnp.ndarray[cnp.float64_t, ndim=1, mode='c'] x,\n", 233 | " cnp.ndarray[cnp.float64_t, ndim=1, mode='c'] y,\n", 234 | " cnp.ndarray[cnp.float64_t, ndim=1, mode='c'] z,\n", 235 | "):\n", 236 | " cdef cnp.ndarray[cnp.float64_t, ndim=1, mode='c'] out = np.empty(x.size)\n", 237 | " cdef double * _x = &x[0]\n", 238 | " cdef double * _y = &y[0]\n", 239 | " cdef double * _z = &z[0]\n", 240 | " cdef double * _out = &out[0]\n", 241 | " cdef int i\n", 242 | " if x.size != y.size or x.size != z.size:\n", 243 | " raise ValueError(\"Inconsistent length\")\n", 244 | " for i in range(x.size):\n", 245 | " _out[i] = _x[i] + fastexp(2*_y[i] + _x[i]) + fastexp(3*_z[i])\n", 246 | " return out" 247 | ] 248 | }, 249 | { 250 | "cell_type": "code", 251 | "execution_count": null, 252 | "metadata": {}, 253 | "outputs": [], 254 | "source": [ 255 | "%timeit cy_f_fastexp" 256 | ] 257 | }, 258 | { 259 | "cell_type": "markdown", 260 | "metadata": {}, 261 | "source": [ 262 | "So that's a 10x speedup, of course at the cost of lost precision, but we are already assuming that was acceptable:" 263 | ] 264 | }, 265 | { 266 | "cell_type": "code", 267 | "execution_count": null, 268 | "metadata": {}, 269 | "outputs": [], 270 | "source": [ 271 | "cy_f_fastexp(*inp) - f(*inp)" 272 | ] 273 | } 274 | ], 275 | "metadata": { 276 | "kernelspec": { 277 | "display_name": "Python 3", 278 | "language": "python", 279 | "name": "python3" 280 | }, 281 | "language_info": { 282 | "codemirror_mode": { 283 | "name": "ipython", 284 | "version": 3 285 | }, 286 | "file_extension": ".py", 287 | "mimetype": "text/x-python", 288 | "name": "python", 289 | "nbconvert_exporter": "python", 290 | "pygments_lexer": "ipython3", 291 | "version": "3.6.1" 292 | } 293 | }, 294 | "nbformat": 4, 295 | "nbformat_minor": 2 296 | } 297 | -------------------------------------------------------------------------------- /notebooks/60-chemical-kinetics-reaction-diffusion.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "In this notebook we will add diffusion in addition to reactions. We will first study the simplest possible chemical reaction set:\n", 8 | "\n", 9 | "$$\n", 10 | "A \\overset{k}{\\rightarrow} B\n", 11 | "$$\n", 12 | "\n", 13 | "we will consider a flat geometry where we assume the concentration is constant in all directions except one (giving us a one dimensional system with respect to space). Following the serialization format we introduced earlier:" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "reactions = [\n", 23 | " ('k', {'A': 1}, {'B': 1, 'A': -1}),\n", 24 | "]\n", 25 | "names, params = 'A B'.split(), ['k']" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "The diffusion follows [Fick's law of diffusion](https://en.wikipedia.org/wiki/Fick%27s_laws_of_diffusion):\n", 33 | "\n", 34 | "$$\n", 35 | "\\frac{\\partial c_i}{\\partial t} = D \\frac{\\partial^2 c_i}{\\partial x}\n", 36 | "$$\n", 37 | "\n", 38 | "where $t$ is time, $c_i$ is the local concentration of species $i$, $x$ is the spatial variable and $D$ the diffusion constant. Note that a pure diffusion process is identical to the perhaps more well known [heat equation](https://en.wikipedia.org/wiki/Heat_equation). We will, however, also consider contributions ($r_i$) from chemical reactions:\n", 39 | "\n", 40 | "$$\n", 41 | "\\frac{\\partial c_i}{\\partial t} = D \\frac{\\partial^2 c_i}{\\partial x} + r_i\n", 42 | "$$\n", 43 | "\n", 44 | "We will set the diffusion constant ($m^2/s$ in SI units) equal for our two species in this example: " 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": null, 50 | "metadata": {}, 51 | "outputs": [], 52 | "source": [ 53 | "D = [8e-9, 8e-9] # He diffusion constant in water at room temperature" 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "metadata": {}, 59 | "source": [ 60 | "We will solve the [partial differential equation](https://en.wikipedia.org/wiki/Partial_differential_equation) (PDE) using [method of lines](https://en.wikipedia.org/wiki/Method_of_lines). We discretize space into a series of bins (lines), in each of these bins we calculate the contribution of chemical reactions to the rate of change, and then add the diffusion contribution based on a [finite difference](https://en.wikipedia.org/wiki/Finite_difference#Higher-order_differences) estimate of the second derivative.\n", 61 | "\n", 62 | "SymPy contains an algorithm to calculate finite difference weights:" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": null, 68 | "metadata": {}, 69 | "outputs": [], 70 | "source": [ 71 | "import sympy as sym\n", 72 | "x, h = sym.symbols('x h')\n", 73 | "d2fdx2 = sym.Function('f')(x).diff(x, 2)\n", 74 | "d2fdx2.as_finite_difference([x-h, x, x+h], x).factor()" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "metadata": {}, 80 | "source": [ 81 | "In this case, we are dealing with an equidistant grid and you may very well recognize this result from standard text books (it is actually quite easy to derive from the definition of the derivative).\n", 82 | "\n", 83 | "The number of dependent variables in our ODE system is then the number of species multiplied by the number of bins. There is no need to create that many symbols, instead we rely on writing an outer loop calculating the local reactions rates. We create a new subclass of our ``ODEsys`` class from earlier to do this:" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": null, 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "# %load ../scipy2017codegen/odesys_diffusion.py\n", 93 | "from itertools import chain\n", 94 | "import numpy as np\n", 95 | "import matplotlib.pyplot as plt\n", 96 | "from scipy2017codegen.odesys import ODEsys\n", 97 | "\n", 98 | "\n", 99 | "class MOLsys(ODEsys):\n", 100 | " \"\"\" System of ODEs based on method of lines on the interval x = [0, x_end] \"\"\"\n", 101 | "\n", 102 | " def __init__(self, *args, **kwargs):\n", 103 | " self.x_end = kwargs.pop('x_end')\n", 104 | " self.n_lines = kwargs.pop('n_lines')\n", 105 | " self.D = kwargs.pop('D')\n", 106 | " self.dx = self.x_end / self.n_lines\n", 107 | " super(MOLsys, self).__init__(*args, **kwargs)\n", 108 | "\n", 109 | " def f_eval(self, y, t, *params):\n", 110 | " f_out = np.empty(self.ny*self.n_lines)\n", 111 | " for i in range(self.n_lines):\n", 112 | " slc = slice(i*self.ny, (i+1)*self.ny)\n", 113 | " y_bis = self.second_derivatives_spatial(i, y, f_out[slc])\n", 114 | " f_out[slc] *= self.D\n", 115 | " f_out[slc] += self.lambdified_f(*chain(y[slc], params))\n", 116 | " return f_out\n", 117 | "\n", 118 | " def central_reference_bin(self, i):\n", 119 | " return np.clip(i, 1, self.ny - 2)\n", 120 | "\n", 121 | " def j_eval(self, y, t, *params):\n", 122 | " j_out = np.zeros((self.ny*self.n_lines, self.ny*self.n_lines)) # dense matrix\n", 123 | " for i in range(self.n_lines):\n", 124 | " slc = slice(i*self.ny, (i+1)*self.ny)\n", 125 | " j_out[slc, slc] = self.lambdified_j(*chain(y[slc], params))\n", 126 | " k = self.central_reference_bin(i)\n", 127 | " for j in range(self.ny):\n", 128 | " j_out[i*self.ny + j, (k-1)*self.ny + j] += self.D[j]/self.dx**2\n", 129 | " j_out[i*self.ny + j, (k )*self.ny + j] += -2*self.D[j]/self.dx**2\n", 130 | " j_out[i*self.ny + j, (k+1)*self.ny + j] += self.D[j]/self.dx**2\n", 131 | " return j_out\n", 132 | "\n", 133 | " def second_derivatives_spatial(self, i, y, out):\n", 134 | " k = self.central_reference_bin(i)\n", 135 | " for j in range(self.ny):\n", 136 | " left = y[(k-1)*self.ny + j]\n", 137 | " cent = y[(k )*self.ny + j]\n", 138 | " rght = y[(k+1)*self.ny + j]\n", 139 | " out[j] = (left - 2*cent + rght)/self.dx**2\n", 140 | "\n", 141 | " def integrate(self, tout, y0, params=(), **kwargs):\n", 142 | " y0 = np.array(np.vstack(y0).T.flat)\n", 143 | " yout, info = super(MOLsys, self).integrate(tout, y0, params, **kwargs)\n", 144 | " return yout.reshape((tout.size, self.n_lines, self.ny)).transpose((0, 2, 1)), info\n", 145 | "\n", 146 | " def x_centers(self):\n", 147 | " return np.linspace(self.dx/2, self.x_end - self.dx/2, self.n_lines)\n", 148 | "\n", 149 | " def plot_result(self, tout, yout, info=None, ax=None):\n", 150 | " ax = ax or plt.subplot(1, 1, 1)\n", 151 | " x_lines = self.x_centers()\n", 152 | " for i, t in enumerate(tout):\n", 153 | " for j in range(self.ny):\n", 154 | " c = [0.0, 0.0, 0.0]\n", 155 | " c[j] = t/tout[-1]\n", 156 | " plt.plot(x_lines, yout[i, j, :], color=c)\n", 157 | " self.print_info(info)\n" 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": null, 163 | "metadata": {}, 164 | "outputs": [], 165 | "source": [ 166 | "from scipy2017codegen.chem import mk_rsys\n", 167 | "molsys = mk_rsys(MOLsys, reactions, names, params, x_end=0.01, n_lines=50, D=D)\n", 168 | "xc = molsys.x_centers()\n", 169 | "xm = molsys.x_end/2\n", 170 | "A0 = np.exp(-1e6*(xc-xm)**2)\n", 171 | "B0 = np.zeros_like(A0)" 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": null, 177 | "metadata": {}, 178 | "outputs": [], 179 | "source": [ 180 | "tout = np.linspace(0, 30, 40)\n", 181 | "yout, info = molsys.integrate(tout, [A0, B0], [0.00123])\n", 182 | "yout.shape" 183 | ] 184 | }, 185 | { 186 | "cell_type": "code", 187 | "execution_count": null, 188 | "metadata": {}, 189 | "outputs": [], 190 | "source": [ 191 | "%matplotlib inline\n", 192 | "molsys.plot_result(tout[::10], yout[::10, ...], info)" 193 | ] 194 | } 195 | ], 196 | "metadata": { 197 | "kernelspec": { 198 | "display_name": "Python 3", 199 | "language": "python", 200 | "name": "python3" 201 | }, 202 | "language_info": { 203 | "codemirror_mode": { 204 | "name": "ipython", 205 | "version": 3 206 | }, 207 | "file_extension": ".py", 208 | "mimetype": "text/x-python", 209 | "name": "python", 210 | "nbconvert_exporter": "python", 211 | "pygments_lexer": "ipython3", 212 | "version": "3.6.1" 213 | } 214 | }, 215 | "nbformat": 4, 216 | "nbformat_minor": 2 217 | } 218 | -------------------------------------------------------------------------------- /notebooks/_30-chemical-kinetics-lambdify.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Chemical kinetics\n", 8 | "As we saw in the previous notebook, the rate of chemical reactions can be described by ordinary differential equations.\n", 9 | "\n", 10 | "In this notebook we will look at a classic example in chemical kinetics:\n", 11 | "[Robertson's example](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.53.8603).\n", 12 | "It is a 3-species reaction system:\n", 13 | "\n", 14 | "$$\n", 15 | "A \\overset{k_1}{\\rightarrow} B \\\\\n", 16 | "B + C \\overset{k_2}{\\rightarrow} A + C \\\\\n", 17 | "2 B \\overset{k_3}{\\rightarrow} B + C\n", 18 | "$$\n", 19 | "\n", 20 | "where A, B and C represent three different chemical species (e.g. reactive molecules dissolved in water). What is special about this system is that the numerical stepping needs to be performed using an implicit method (requiring the Jacobian) when solving for large time scales.\n", 21 | "\n", 22 | "The rate of each process follows the [law of mass action](https://en.wikipedia.org/wiki/Law_of_mass_action), i.e. the rate is proportional to the concentration of each reacting species (to the power of their multiplicity). The proportionality constant is known as the rate constant of the reaction ($k_1,\\ k_2\\ \\&\\ k_3$ in our case). If we denote the rate of each reaction:\n", 23 | "\n", 24 | "$$\n", 25 | "r_1 = k_1[A] \\\\\n", 26 | "r_2 = k_2[B][C] \\\\\n", 27 | "r_3 = k_3[B]^2\n", 28 | "$$\n", 29 | "\n", 30 | "$[A],\\ [B],\\ [C]$ denotes the concentration of respective species. We can now formulate a system of ordinary differential equations describing how the concentrations evolve over time:\n", 31 | "\n", 32 | "$$\n", 33 | "\\frac{d[A]}{dt} = r_2 - r_1 \\\\\n", 34 | "\\frac{d[B]}{dt} = r_1 - r_2 - r_3 \\\\\n", 35 | "\\frac{d[C]}{dt} = r_3\n", 36 | "$$\n", 37 | "\n", 38 | "We will now express these differential equations (and their Jacobian) symbolically using SymPy:" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": null, 44 | "metadata": {}, 45 | "outputs": [], 46 | "source": [ 47 | "import sympy as sym\n", 48 | "sym.init_printing()" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": null, 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "t = sym.symbols('t') # not used, only a placeholder\n", 58 | "c = cA, cB, cC = sym.symbols('[A] [B] [C]') # concentrations\n", 59 | "k = k1, k2, k3 = sym.symbols('k_1 k_2 k_3')\n", 60 | "r1, r2, r3 = k1*cA, k2*cB*cC, k3*cB**2\n", 61 | "ydot = r2 - r1, r1 - r2 - r3, r3\n", 62 | "ydot" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "metadata": {}, 68 | "source": [ 69 | "$\\dot{\\mathbf{y}}$ now represent our ODE system, where $\\mathbf{y}$ is our state vector (concentrations). We will need a callback to evaluate $\\dot{\\mathbf{y}}$ when we integrate this ODE system numerically (using ``scipy.integrate.odeint``). As we have seen SymPy can provide us with this callback:" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "import numpy as np\n", 79 | "from scipy.integrate import odeint" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": null, 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "f = sym.lambdify((c, t) + k, ydot)\n", 89 | "\n", 90 | "def integrate(tend=3600, t0=1e-6, **kwargs):\n", 91 | " tout = np.logspace(np.log10(t0), np.log10(tend))\n", 92 | " k_vals = (0.04, 1e4, 3e7) # from the literature, k1 has another unit than k2 & k3\n", 93 | " c0 = [1, 0, 0] # we start with unit concentration of A (number density)\n", 94 | " yout, info = odeint(f, c0, tout, args=k_vals, full_output=True, **kwargs)\n", 95 | " return tout, yout, info" 96 | ] 97 | }, 98 | { 99 | "cell_type": "markdown", 100 | "metadata": {}, 101 | "source": [ 102 | "The lambda function is needed to give f the signature asked for by ``odeint``. ``full_output=True`` makes odeint return a dictionary with information about the numerical integration." 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": null, 108 | "metadata": {}, 109 | "outputs": [], 110 | "source": [ 111 | "tout, yout, info = integrate(1e6, atol=1e-9, rtol=1e-9)" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": null, 117 | "metadata": {}, 118 | "outputs": [], 119 | "source": [ 120 | "import matplotlib.pyplot as plt\n", 121 | "%matplotlib inline" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": null, 127 | "metadata": {}, 128 | "outputs": [], 129 | "source": [ 130 | "def plot_result(tout, yout, info=None):\n", 131 | " fig, axes = plt.subplots(1, 2, figsize=(14, 4))\n", 132 | " for i, label in enumerate('ABC'):\n", 133 | " for ax in axes:\n", 134 | " ax.plot(tout, yout[:, i], label=label)\n", 135 | " axes[1].set_xscale('log')\n", 136 | " axes[1].set_yscale('log')\n", 137 | " for ax in axes:\n", 138 | " ax.set_ylabel('$\\mathrm{concentration / mol \\cdot dm^{-3}}$')\n", 139 | " ax.set_xlabel('$\\mathrm{time / s}$')\n", 140 | " ax.legend(loc='best')\n", 141 | " if info:\n", 142 | " print(\"The jacobian was evaluated %d times\" % info['nje'][-1])" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": null, 148 | "metadata": {}, 149 | "outputs": [], 150 | "source": [ 151 | "plot_result(tout, yout, info)" 152 | ] 153 | }, 154 | { 155 | "cell_type": "markdown", 156 | "metadata": {}, 157 | "source": [ 158 | "\n", 159 | "\n", 160 | "If we look closer at the info-dictionary we will see that odeint (or rather LSODA which is the underlying package) switched method from an explicit Adams method to an implicit Backward Differentiation Formula (BDF). It is common for chemical kinetics problems that the problem becomes stiff. The larger number of species, the bigger is the Jacobian matrix.\n", 161 | "\n", 162 | "By default the solver will approximate the elements in the Jacobian matrix by taking finite differences of $\\mathbf{f}$. This is often works quite satisfactorily but for larger systems it sometimes fails. A more robust (and faster) approach is to provide a callback which evaluates an analytic Jacobian. Using SymPy we can do this quite effortlessly:" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": null, 168 | "metadata": {}, 169 | "outputs": [], 170 | "source": [ 171 | "J = sym.Matrix(ydot).jacobian(c)\n", 172 | "J" 173 | ] 174 | }, 175 | { 176 | "cell_type": "code", 177 | "execution_count": null, 178 | "metadata": {}, 179 | "outputs": [], 180 | "source": [ 181 | "J_cb = sym.lambdify((c, t) + k, J)" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": null, 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [ 190 | "tout, yout, info = integrate(1e6, Dfun=J_cb)" 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": null, 196 | "metadata": {}, 197 | "outputs": [], 198 | "source": [ 199 | "plot_result(tout, yout, info)" 200 | ] 201 | }, 202 | { 203 | "cell_type": "markdown", 204 | "metadata": {}, 205 | "source": [ 206 | "We see that the solver needed to evaluate the Jacobian fewer times (due to it being essentially exact this time around). For larger systems the impact of an analytic Jacobian is often even greater (being the difference between a failed and successful integration). \n", 207 | "\n", 208 | "Benchmarking with and without the analytic Jacobian callback:" 209 | ] 210 | }, 211 | { 212 | "cell_type": "code", 213 | "execution_count": null, 214 | "metadata": {}, 215 | "outputs": [], 216 | "source": [ 217 | "%timeit integrate(1e6)" 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": null, 223 | "metadata": {}, 224 | "outputs": [], 225 | "source": [ 226 | "%timeit integrate(1e6, Dfun=J_cb)" 227 | ] 228 | } 229 | ], 230 | "metadata": { 231 | "kernelspec": { 232 | "display_name": "Python 3", 233 | "language": "python", 234 | "name": "python3" 235 | }, 236 | "language_info": { 237 | "codemirror_mode": { 238 | "name": "ipython", 239 | "version": 3 240 | }, 241 | "file_extension": ".py", 242 | "mimetype": "text/x-python", 243 | "name": "python", 244 | "nbconvert_exporter": "python", 245 | "pygments_lexer": "ipython3", 246 | "version": "3.6.1" 247 | } 248 | }, 249 | "nbformat": 4, 250 | "nbformat_minor": 2 251 | } 252 | -------------------------------------------------------------------------------- /notebooks/_35-chemical-kinetics-lambdify-deserialize.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Generating symbolic expressions\n", 8 | "For larger reaction systems it is preferable to generate the system of ordinary differential equations from some serialized format and then generate the callback using code generation.\n", 9 | "\n", 10 | "In this notebook we will define such a serialized format, and use it load a larger set of reactions. We represent a reaction as length 3 tuple of: `(rate_const, coeff_powers, net_effect)`. Representing Robertson's system this way looks like this:" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "reactions = [\n", 20 | " ('k1', {'A': 1}, {'B': 1, 'A': -1}),\n", 21 | " ('k2', {'B': 1, 'C': 1}, {'A': 1, 'B': -1}),\n", 22 | " ('k3', {'B': 2}, {'B': -1, 'C': 1})\n", 23 | "]\n", 24 | "names, params = 'A B C'.split(), 'k1 k2 k3'.split()\n", 25 | "tex_names = ['[%s]' % n for n in names]" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "the reaction system is still defined as:\n", 33 | "$$\n", 34 | "A \\overset{k_1}{\\rightarrow} B \\\\\n", 35 | "B + C \\overset{k_2}{\\rightarrow} A + C \\\\\n", 36 | "2 B \\overset{k_3}{\\rightarrow} B + C\n", 37 | "$$\n", 38 | "\n", 39 | "We will now write a small convenience function which takes the above representation and creates symbolic expressions for the ODE system:" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": null, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "# %load ../scipy2017codegen/chem.py\n", 49 | "from operator import mul\n", 50 | "from functools import reduce\n", 51 | "import sympy as sym\n", 52 | "\n", 53 | "def prod(seq):\n", 54 | " return reduce(mul, seq) if seq else 1\n", 55 | "\n", 56 | "def mk_exprs_symbs(rxns, names):\n", 57 | " concs = sym.symbols(names, real=True, nonnegative=True)\n", 58 | " c_dict = dict(zip(names, concs))\n", 59 | " f = {n: 0 for n in names}\n", 60 | " for coeff, r_stoich, net_stoich in rxns:\n", 61 | " r = sym.S(coeff)*prod([c_dict[rk]**p for rk, p in r_stoich.items()])\n", 62 | " for nk, nm in net_stoich.items():\n", 63 | " f[nk] += nm*r\n", 64 | " return [f[n] for n in names], concs\n", 65 | "\n", 66 | "\n", 67 | "def mk_rsys(ODEcls, reactions, names, params=(), **kwargs):\n", 68 | " f, symbs = mk_exprs_symbs(reactions, names)\n", 69 | " return ODEcls(f, symbs, params=map(sym.S, params), **kwargs)\n" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "sym.init_printing()\n", 79 | "f, symbs = mk_exprs_symbs(reactions, names)\n", 80 | "f, symbs" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "metadata": {}, 86 | "source": [ 87 | "We create a helper class to represent to ODE system." 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": null, 93 | "metadata": {}, 94 | "outputs": [], 95 | "source": [ 96 | "# %load ../scipy2017codegen/odesys.py\n", 97 | "from itertools import chain # Py 2.7 does not support func(*args1, *args2)\n", 98 | "import sympy as sym\n", 99 | "from scipy.integrate import odeint\n", 100 | "\n", 101 | "class ODEsys(object):\n", 102 | "\n", 103 | " default_integrator = 'odeint'\n", 104 | "\n", 105 | " def __init__(self, f, y, t=None, params=(), tex_names=None, lambdify=None):\n", 106 | " assert len(f) == len(y), 'f is dy/dt'\n", 107 | " self.f = tuple(f)\n", 108 | " self.y = tuple(y)\n", 109 | " self.t = t\n", 110 | " self.p = tuple(params)\n", 111 | " self.tex_names = tex_names\n", 112 | " self.j = sym.Matrix(self.ny, 1, f).jacobian(y)\n", 113 | " self.lambdify = lambdify or sym.lambdify\n", 114 | " self.setup()\n", 115 | "\n", 116 | " @property\n", 117 | " def ny(self):\n", 118 | " return len(self.y)\n", 119 | "\n", 120 | " def setup(self):\n", 121 | " self.lambdified_f = self.lambdify(self.y + self.p, self.f)\n", 122 | " self.lambdified_j = self.lambdify(self.y + self.p, self.j)\n", 123 | "\n", 124 | " def f_eval(self, y, t, *params):\n", 125 | " return self.lambdified_f(*chain(y, params))\n", 126 | "\n", 127 | " def j_eval(self, y, t, *params):\n", 128 | " return self.lambdified_j(*chain(y, params))\n", 129 | "\n", 130 | " def integrate(self, *args, **kwargs):\n", 131 | " integrator = kwargs.pop('integrator', self.default_integrator)\n", 132 | " return getattr(self, 'integrate_%s' % integrator)(*args, **kwargs)\n", 133 | "\n", 134 | " def integrate_odeint(self, tout, y0, params=(), rtol=1e-8, atol=1e-8, **kwargs):\n", 135 | " return odeint(self.f_eval, y0, tout, args=tuple(params), full_output=True,\n", 136 | " Dfun=self.j_eval, rtol=rtol, atol=atol, **kwargs)\n", 137 | "\n", 138 | " def print_info(self, info):\n", 139 | " if info is None:\n", 140 | " return\n", 141 | " nrhs = info.get('num_rhs')\n", 142 | " if not nrhs:\n", 143 | " nrhs = info['nfe'][-1]\n", 144 | " njac = info.get('num_dls_jac_evals')\n", 145 | " if not njac:\n", 146 | " njac = info['nje'][-1]\n", 147 | " print(\"The rhs was evaluated %d times and the Jacobian %d times\" % (nrhs, njac))\n", 148 | "\n", 149 | " def plot_result(self, tout, yout, info=None, ax=None):\n", 150 | " ax = ax or plt.subplot(1, 1, 1)\n", 151 | " for i, label in enumerate(self.tex_names):\n", 152 | " ax.plot(tout, yout[:, i], label='$%s$' % label)\n", 153 | " ax.set_ylabel('$\\mathrm{concentration\\ /\\ mol \\cdot dm^{-3}}$')\n", 154 | " ax.set_xlabel('$\\mathrm{time\\ /\\ s}$')\n", 155 | " ax.legend(loc='best')\n", 156 | " self.print_info(info)\n" 157 | ] 158 | }, 159 | { 160 | "cell_type": "code", 161 | "execution_count": null, 162 | "metadata": {}, 163 | "outputs": [], 164 | "source": [ 165 | "odesys = ODEsys(f, symbs, params=params, tex_names=tex_names)" 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": null, 171 | "metadata": {}, 172 | "outputs": [], 173 | "source": [ 174 | "import numpy as np\n", 175 | "tout = np.logspace(-6, 6)\n", 176 | "yout, info = odesys.integrate_odeint(tout, [1, 0, 0], [0.04, 1e4, 3e7], atol=1e-9, rtol=1e-9)" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": null, 182 | "metadata": {}, 183 | "outputs": [], 184 | "source": [ 185 | "import matplotlib.pyplot as plt\n", 186 | "%matplotlib inline" 187 | ] 188 | }, 189 | { 190 | "cell_type": "code", 191 | "execution_count": null, 192 | "metadata": {}, 193 | "outputs": [], 194 | "source": [ 195 | "fig, axes = plt.subplots(1, 2, figsize=(14, 4))\n", 196 | "odesys.plot_result(tout, yout, info, ax=axes[0])\n", 197 | "odesys.plot_result(tout, yout, ax=axes[1])\n", 198 | "axes[1].set_xscale('log')\n", 199 | "axes[1].set_yscale('log')" 200 | ] 201 | }, 202 | { 203 | "cell_type": "markdown", 204 | "metadata": {}, 205 | "source": [ 206 | "The reason for why we went through this trouble is to be able to create a `ODEsys` instance from conveniently serialized data. Here is a much larger set of reactions, describing water radiolysis at 298 K and a doserate of 300 Gy/s (which is a doserate not far from that of a nuclear reactor):" 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": null, 212 | "metadata": {}, 213 | "outputs": [], 214 | "source": [ 215 | "import json\n", 216 | "watrad_data = json.load(open('../scipy2017codegen/data/radiolysis_300_Gy_s.json'))\n", 217 | "watrad = mk_rsys(ODEsys, **watrad_data)\n", 218 | "print(len(watrad.f), watrad.y[0], watrad.f[0])" 219 | ] 220 | }, 221 | { 222 | "cell_type": "markdown", 223 | "metadata": {}, 224 | "source": [ 225 | "Values correspond to SI units, the concentration of water at 298 K is 55400 mol/m³. Neutral water contains [H+] = [HO-] = 10^-4 mol/m³:" 226 | ] 227 | }, 228 | { 229 | "cell_type": "code", 230 | "execution_count": null, 231 | "metadata": {}, 232 | "outputs": [], 233 | "source": [ 234 | "tout = np.logspace(-6, 3, 200) # close to one hour of operation\n", 235 | "c0 = {'H2O': 55.4e3, 'H+': 1e-4, 'OH-': 1e-4}\n", 236 | "y0 = [c0.get(symb.name, 0) for symb in watrad.y]" 237 | ] 238 | }, 239 | { 240 | "cell_type": "code", 241 | "execution_count": null, 242 | "metadata": {}, 243 | "outputs": [], 244 | "source": [ 245 | "%timeit watrad.integrate_odeint(tout, y0)" 246 | ] 247 | }, 248 | { 249 | "cell_type": "code", 250 | "execution_count": null, 251 | "metadata": {}, 252 | "outputs": [], 253 | "source": [ 254 | "fig, ax = plt.subplots(1, 1, figsize=(14, 6))\n", 255 | "watrad.plot_result(tout, *watrad.integrate_odeint(tout, y0), ax=ax)\n", 256 | "ax.set_xscale('log')\n", 257 | "ax.set_yscale('log')" 258 | ] 259 | } 260 | ], 261 | "metadata": { 262 | "kernelspec": { 263 | "display_name": "Python 3", 264 | "language": "python", 265 | "name": "python3" 266 | }, 267 | "language_info": { 268 | "codemirror_mode": { 269 | "name": "ipython", 270 | "version": 3 271 | }, 272 | "file_extension": ".py", 273 | "mimetype": "text/x-python", 274 | "name": "python", 275 | "nbconvert_exporter": "python", 276 | "pygments_lexer": "ipython3", 277 | "version": "3.6.1" 278 | } 279 | }, 280 | "nbformat": 4, 281 | "nbformat_minor": 2 282 | } 283 | -------------------------------------------------------------------------------- /notebooks/_37-chemical-kinetics-numba.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# NOTE\n", 8 | "This notebook doesn't work yet. I have previously written my own version of `lambdify` [here](https://github.com/bjodah/sym/blob/222d1c6a3d1db03c67e8467ff562d3ac0f124616/sym/_sympy_Lambdify.py#L214).\n", 9 | "Don't know if that's the path to go, or wait for [next release of numba](https://github.com/numba/numba/issues/2328#issuecomment-303382911)." 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "In this notebook we will use `numba` the increase the performance of our callbacks produced by lambdify in SymPy." 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "metadata": { 23 | "collapsed": true 24 | }, 25 | "outputs": [], 26 | "source": [ 27 | "import json\n", 28 | "import numpy as np\n", 29 | "import sympy as sym\n", 30 | "from scipy2017codegen.odesys import ODEsys\n", 31 | "from scipy2017codegen.chem import mk_rsys" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "The `ODEsys` class and convenience functions from previous notebook (35) has been put in two modules for easy importing. Recapping what we did last:" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": null, 44 | "metadata": { 45 | "collapsed": true 46 | }, 47 | "outputs": [], 48 | "source": [ 49 | "watrad_data = json.load(open('../scipy2017codegen/data/radiolysis_300_Gy_s.json'))\n", 50 | "watrad = mk_rsys(ODEsys, **watrad_data)\n", 51 | "tout = np.logspace(-6, 3, 200) # close to one hour of operation\n", 52 | "c0 = {'H2O': 55.4e3, 'H+': 1e-4, 'OH-': 1e-4}\n", 53 | "y0 = [c0.get(symb.name, 0) for symb in watrad.y]" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": { 60 | "collapsed": true 61 | }, 62 | "outputs": [], 63 | "source": [ 64 | "%timeit yout, info = watrad.integrate_odeint(tout, y0)" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "metadata": {}, 70 | "source": [ 71 | "so that is the benchmark to beat." 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": null, 77 | "metadata": { 78 | "collapsed": true 79 | }, 80 | "outputs": [], 81 | "source": [ 82 | "from numba import njit\n", 83 | "watrad_numba = mk_rsys(ODEsys, **watrad_data, lambdify=lambda *args: njit(sym.lambdify(*args, modules=\"numpy\")))\n", 84 | "watrad_numba.integrate_odeint(tout, y0)" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": null, 90 | "metadata": { 91 | "collapsed": true 92 | }, 93 | "outputs": [], 94 | "source": [ 95 | "%timeit watrad_numba.integrate_odeint(tout, y0)" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": null, 101 | "metadata": { 102 | "collapsed": true 103 | }, 104 | "outputs": [], 105 | "source": [ 106 | "import matplotlib.pyplot as plt\n", 107 | "%matplotlib inline" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": {}, 113 | "source": [ 114 | "Just to see that everything looks alright:" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "metadata": { 121 | "collapsed": true 122 | }, 123 | "outputs": [], 124 | "source": [ 125 | "fig, ax = plt.subplots(1, 1, figsize=(14, 6))\n", 126 | "watrad_numba.plot_result(tout, *watrad_numba.integrate_odeint(tout, y0), ax=ax)\n", 127 | "ax.set_xscale('log')\n", 128 | "ax.set_yscale('log')" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": null, 134 | "metadata": { 135 | "collapsed": true 136 | }, 137 | "outputs": [], 138 | "source": [] 139 | } 140 | ], 141 | "metadata": { 142 | "anaconda-cloud": {}, 143 | "kernelspec": { 144 | "display_name": "Python [default]", 145 | "language": "python", 146 | "name": "python3" 147 | }, 148 | "language_info": { 149 | "codemirror_mode": { 150 | "name": "ipython", 151 | "version": 3 152 | }, 153 | "file_extension": ".py", 154 | "mimetype": "text/x-python", 155 | "name": "python", 156 | "nbconvert_exporter": "python", 157 | "pygments_lexer": "ipython3", 158 | "version": "3.5.3" 159 | } 160 | }, 161 | "nbformat": 4, 162 | "nbformat_minor": 2 163 | } 164 | -------------------------------------------------------------------------------- /notebooks/_38-chemical-kinetics-symengine.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# NOTE\n", 8 | "This notebook will make more sense (provide speed-up) once the LLVM backend is exposed in the python wrappers for SymEngine. I need to get back working on that [here](https://github.com/symengine/symengine.py/pull/112)." 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "In this notebook we will use `symengine` the increase the performance of our callbacks produced by lambdify in SymPy." 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "import json\n", 25 | "import numpy as np\n", 26 | "from scipy2017codegen.odesys import ODEsys\n", 27 | "from scipy2017codegen.chem import mk_rsys" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": {}, 33 | "source": [ 34 | "The `ODEsys` class and convenience functions from previous notebook (35) has been put in two modules for easy importing. Recapping what we did last:" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "watrad_data = json.load(open('../scipy2017codegen/data/radiolysis_300_Gy_s.json'))\n", 44 | "watrad = mk_rsys(ODEsys, **watrad_data)\n", 45 | "tout = np.logspace(-6, 3, 200) # close to one hour of operation\n", 46 | "c0 = {'H2O': 55.4e3, 'H+': 1e-4, 'OH-': 1e-4}\n", 47 | "y0 = [c0.get(symb.name, 0) for symb in watrad.y]" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": null, 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "%timeit yout, info = watrad.integrate_odeint(tout, y0)" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": {}, 62 | "source": [ 63 | "so that is the benchmark to beat." 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": null, 69 | "metadata": {}, 70 | "outputs": [], 71 | "source": [ 72 | "import symengine as se\n", 73 | "def _lambdify(args, exprs):\n", 74 | " if isinstance(exprs, sym.MutableDenseMatrix):\n", 75 | " exprs = se.DenseMatrix(exprs.shape[0], exprs.shape[1], exprs.tolist())\n", 76 | " lmb = se.Lambdify(args, exprs)\n", 77 | " return lambda *args: lmb(args)\n", 78 | "\n", 79 | "watrad_symengine = mk_rsys(ODEsys, **watrad_data, lambdify=_lambdify)" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": null, 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "%timeit watrad_symengine.integrate_odeint(tout, y0)" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": null, 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "import matplotlib.pyplot as plt\n", 98 | "%matplotlib inline" 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "metadata": {}, 104 | "source": [ 105 | "Just to see that everything looks alright:" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": null, 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [ 114 | "fig, ax = plt.subplots(1, 1, figsize=(14, 6))\n", 115 | "watrad_symengine.plot_result(tout, *watrad_symengine.integrate_odeint(tout, y0), ax=ax)\n", 116 | "ax.set_xscale('log')\n", 117 | "ax.set_yscale('log')" 118 | ] 119 | } 120 | ], 121 | "metadata": { 122 | "kernelspec": { 123 | "display_name": "Python 3", 124 | "language": "python", 125 | "name": "python3" 126 | }, 127 | "language_info": { 128 | "codemirror_mode": { 129 | "name": "ipython", 130 | "version": 3 131 | }, 132 | "file_extension": ".py", 133 | "mimetype": "text/x-python", 134 | "name": "python", 135 | "nbconvert_exporter": "python", 136 | "pygments_lexer": "ipython3", 137 | "version": "3.6.1" 138 | } 139 | }, 140 | "nbformat": 4, 141 | "nbformat_minor": 2 142 | } 143 | -------------------------------------------------------------------------------- /notebooks/cython-examples.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Writing Cython\n", 8 | "\n", 9 | "In this notebook, we'll take a look at how to implement a simple function using Cython. The operation we'll implement is the [first-order diff](https://en.wikipedia.org/wiki/Finite_difference), which takes in an array of length $n$:\n", 10 | "\n", 11 | "$$\\mathbf{x} = \\begin{bmatrix} x_1 \\\\ x_2 \\\\ \\vdots \\\\ x_n\\end{bmatrix}$$\n", 12 | "\n", 13 | "and returns the following:\n", 14 | "\n", 15 | "$$\\mathbf{y} = \\begin{bmatrix} x_2 - x_1 \\\\ x_3 - x_2 \\\\ \\vdots \\\\ x_n - x_{n-1} \\end{bmatrix}$$\n", 16 | "\n", 17 | "First we'll import everything we'll need and generate some data to work with." 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": null, 23 | "metadata": { 24 | "collapsed": true 25 | }, 26 | "outputs": [], 27 | "source": [ 28 | "import numpy as np\n", 29 | "x = np.random.randn(10000)" 30 | ] 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "metadata": {}, 35 | "source": [ 36 | "Below is a simple implementation using pure Python (no NumPy). The `%timeit` magic command let's us see how long it takes the function to run on the 10,000-element array defined above." 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": null, 42 | "metadata": {}, 43 | "outputs": [], 44 | "source": [ 45 | "def py_diff(x):\n", 46 | " n = x.size\n", 47 | " y = np.zeros(n-1)\n", 48 | " for i in range(n-1):\n", 49 | " y[i] = x[i+1] - x[i]\n", 50 | " return y\n", 51 | "\n", 52 | "%timeit py_diff(x)" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "Now use the exact same function body but add the `%%cython` magic at the top of the code cell. How much of a difference does simply pre-compiling make?" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "metadata": { 66 | "collapsed": true 67 | }, 68 | "outputs": [], 69 | "source": [ 70 | "%load_ext cython" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": null, 76 | "metadata": { 77 | "collapsed": true 78 | }, 79 | "outputs": [], 80 | "source": [ 81 | "%%cython\n", 82 | "import numpy as np\n", 83 | "def cy_diff_naive(x):\n", 84 | " n = x.size\n", 85 | " y = np.zeros(n-1)\n", 86 | " for i in range(n-1):\n", 87 | " y[i] = x[i+1] - x[i]\n", 88 | " return y" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": null, 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "%timeit cy_diff_naive(x)" 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": {}, 103 | "source": [ 104 | "So it didn't make much of a difference. That's because Cython really shines when you specify data types. We do this by annotating the variables used in the function with `cdef ...`. Let's see how much this improves things.\n", 105 | "\n", 106 | "*Note:* array types (like for the input arg `x`) can be declared using the [memoryview](http://docs.cython.org/en/latest/src/userguide/memoryviews.html) syntax `double[::1]` or using `np.ndarray[cnp.float64_t, ndim=1]`." 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "metadata": { 113 | "collapsed": true 114 | }, 115 | "outputs": [], 116 | "source": [ 117 | "%%cython\n", 118 | "import numpy as np\n", 119 | "def cy_diff(double[::1] x):\n", 120 | " cdef int n = x.size\n", 121 | " cdef double[::1] y = np.zeros(n-1)\n", 122 | " cdef int i\n", 123 | " for i in range(n-1):\n", 124 | " y[i] = x[i+1] - x[i]\n", 125 | " return y" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": null, 131 | "metadata": {}, 132 | "outputs": [], 133 | "source": [ 134 | "%timeit cy_diff(x)" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "metadata": {}, 140 | "source": [ 141 | "That made a huge difference! There are a couple more things we can do to speed up our diff implementation, including [disabling some safety checks](https://cython.readthedocs.io/en/latest/src/reference/compilation.html?#compiler-directives). The combination of disabling bounds checking (making sure you don't try access an index of an array that doesn't exist) and disabling wraparound (disabling use of negative indices) can really improve things when we are sure neither condition will occur. Let's try that." 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": null, 147 | "metadata": { 148 | "collapsed": true 149 | }, 150 | "outputs": [], 151 | "source": [ 152 | "%%cython\n", 153 | "from cython import wraparound, boundscheck\n", 154 | "import numpy as np\n", 155 | "\n", 156 | "@boundscheck(False)\n", 157 | "@wraparound(False)\n", 158 | "def cy_diff2(double[::1] x):\n", 159 | " cdef int n = x.size\n", 160 | " cdef double[::1] y = np.zeros(n-1)\n", 161 | " cdef int i\n", 162 | " for i in range(n-1):\n", 163 | " y[i] = x[i+1] - x[i]\n", 164 | " return y" 165 | ] 166 | }, 167 | { 168 | "cell_type": "code", 169 | "execution_count": null, 170 | "metadata": {}, 171 | "outputs": [], 172 | "source": [ 173 | "%timeit cy_diff2(x)" 174 | ] 175 | }, 176 | { 177 | "cell_type": "markdown", 178 | "metadata": {}, 179 | "source": [ 180 | "Finally, let's see how NumPy's `diff` performs for comparison." 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": null, 186 | "metadata": {}, 187 | "outputs": [], 188 | "source": [ 189 | "def np_diff(x):\n", 190 | " return np.diff(x)\n", 191 | "\n", 192 | "%timeit np_diff(x)" 193 | ] 194 | }, 195 | { 196 | "cell_type": "markdown", 197 | "metadata": {}, 198 | "source": [ 199 | "Why is NumPy's `diff` implementation faster? Maybe use the `--annotate` flag to peek at the C code generated by our latest Cython implementation." 200 | ] 201 | } 202 | ], 203 | "metadata": { 204 | "kernelspec": { 205 | "display_name": "Python 3", 206 | "language": "python", 207 | "name": "python3" 208 | }, 209 | "language_info": { 210 | "codemirror_mode": { 211 | "name": "ipython", 212 | "version": 3 213 | }, 214 | "file_extension": ".py", 215 | "mimetype": "text/x-python", 216 | "name": "python", 217 | "nbconvert_exporter": "python", 218 | "pygments_lexer": "ipython3", 219 | "version": "3.6.1" 220 | } 221 | }, 222 | "nbformat": 4, 223 | "nbformat_minor": 2 224 | } 225 | -------------------------------------------------------------------------------- /notebooks/exercise_Tc99.py: -------------------------------------------------------------------------------- 1 | f = sym.lambdify([t, l1, l2, *inits], [eq.rhs for eq in analytic]) # EXERCISE: [eq.rhs for eq in analytic] 2 | yout = f(tout, 3.2e-5, 1.04e-13, 1, 0, 0) 3 | -------------------------------------------------------------------------------- /notebooks/exercise_jac_func.py: -------------------------------------------------------------------------------- 1 | def J_func(y, t, mu): 2 | return np.array([ 3 | [0, 1], 4 | [-1-2*mu*y[0]*y[1], mu*(1-y[0]**2)] # EXERCISE: -1-2*mu*y[0]*y[1] 5 | ]) 6 | -------------------------------------------------------------------------------- /notebooks/exercise_lambdify.py: -------------------------------------------------------------------------------- 1 | t = sym.symbols('t') # not used in this case. 2 | f = sym.lambdify((y, t) + k, ydot) # EXERCISE: (y, t) + k 3 | -------------------------------------------------------------------------------- /notebooks/exercise_lambdify_expr.py: -------------------------------------------------------------------------------- 1 | d2fdxdy = expr.diff(x, y) # EXERCISE: x, y 2 | func = sym.lambdify([x, y], d2fdxdy) # EXERCISE: lambdify 3 | zplot = func(xplot, yplot) 4 | -------------------------------------------------------------------------------- /notebooks/exercise_lambdify_jac.py: -------------------------------------------------------------------------------- 1 | J = sym.Matrix(ydot).jacobian(y) # EXERCISE: jacobian 2 | J_cb = sym.lambdify((y, t) + k, J) # EXERCISE: (y, t) + k 3 | -------------------------------------------------------------------------------- /notebooks/exercise_odeint.py: -------------------------------------------------------------------------------- 1 | tout = np.linspace(0, 10) 2 | k_vals = 0.42, 0.17 # arbitrary in this case 3 | y0 = [1, 1, 0] 4 | yout = odeint(rhs, y0, tout, k_vals) # EXERCISE: rhs, y0, tout, k_vals 5 | -------------------------------------------------------------------------------- /notebooks/exercise_symbolic.py: -------------------------------------------------------------------------------- 1 | from operator import mul 2 | from functools import reduce 3 | import sympy as sym 4 | 5 | 6 | def prod(seq): 7 | return reduce(mul, seq) if seq else 1 8 | 9 | 10 | def mk_exprs_symbs(rxns, names): 11 | # create symbols for reactants 12 | symbs = sym.symbols(names, real=True, nonnegative=True) 13 | # map between reactant symbols and keys in r_stoich, net_stoich 14 | c = dict(zip(names, symbs)) 15 | f = {n: 0 for n in names} 16 | k = [] 17 | for coeff, r_stoich, net_stoich in rxns: 18 | k.append(sym.S(coeff)) 19 | r = k[-1]*prod([c[rk]**p for rk, p in r_stoich.items()]) # EXERCISE: c[rk]**p 20 | for net_key, net_mult in net_stoich.items(): 21 | f[net_key] += net_mult*r # EXERCISE: net_mult*r 22 | return [f[n] for n in names], symbs, tuple(k) 23 | -------------------------------------------------------------------------------- /scipy2017codegen/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This python-package is collects the python modules used in the tutorial, 3 | it is not intended to be a library for reuse. 4 | """ 5 | -------------------------------------------------------------------------------- /scipy2017codegen/cfib/cfib.c: -------------------------------------------------------------------------------- 1 | void cfib(int n, double *x) { 2 | int i; 3 | x[0] = 0; 4 | x[1] = 1; 5 | for (i = 2; i < n; i++) { 6 | x[i] = x[i-1] + x[i-2]; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /scipy2017codegen/cfib/cfib.h: -------------------------------------------------------------------------------- 1 | #ifndef CFIB__H 2 | #define CFIB__H 3 | void cfib(int n, double *x); 4 | #endif 5 | -------------------------------------------------------------------------------- /scipy2017codegen/chem.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from operator import mul 4 | from functools import reduce 5 | import sympy as sym 6 | import matplotlib.pyplot as plt 7 | import numpy as np 8 | 9 | 10 | def prod(seq): 11 | return reduce(mul, seq) if seq else 1 12 | 13 | 14 | def mk_exprs_symbs(rxns, names): 15 | concs = sym.symbols(names, real=True, nonnegative=True) 16 | c_dict = dict(zip(names, concs)) 17 | f = {n: 0 for n in names} 18 | for coeff, r_stoich, net_stoich in rxns: 19 | r = sym.S(coeff)*prod([c_dict[rk]**p for rk, p in r_stoich.items()]) 20 | for nk, nm in net_stoich.items(): 21 | f[nk] += nm*r 22 | return [f[n] for n in names], concs 23 | 24 | 25 | def mk_rsys(ODEcls, reactions, names, params=(), **kwargs): 26 | f, symbs = mk_exprs_symbs(reactions, names) 27 | return ODEcls(f, symbs, params=map(sym.S, params), **kwargs) 28 | 29 | 30 | def load_watrad(): 31 | """Loads the water radiolysis system data. 32 | 33 | Returns 34 | ------- 35 | eqs : list 36 | List of SymPy equations representing the system of first-order 37 | differential equations. 38 | states : tuple 39 | Tuple of SymPy symbols representing the state variables. 40 | """ 41 | file_path = os.path.join(os.path.dirname(__file__), 'data', 42 | 'radiolysis_300_Gy_s.json') 43 | with open(file_path) as f: 44 | ode_def_dict = json.load(f) 45 | eqs, states = mk_exprs_symbs(ode_def_dict['reactions'], 46 | ode_def_dict['names']) 47 | return eqs, states 48 | 49 | 50 | def load_large_ode(): 51 | """Returns a SymPy column matrix with the right hand side of the ordinary 52 | differential equations, i.e. 14 expressions, and a column matrix of the 53 | state symbols.""" 54 | eqs, states = load_watrad() 55 | rhs_of_odes = sym.Matrix(eqs) 56 | simpler_states = sym.symbols('y:{}'.format(len(states))) 57 | state_map = {s: r for s, r in zip(states, simpler_states)} 58 | return rhs_of_odes.xreplace(state_map), sym.Matrix(simpler_states) 59 | 60 | 61 | def watrad_init(): 62 | """Returns initial conditions for the water radiolysis system. 63 | 64 | Returns 65 | ------- 66 | y0 : list 67 | List of initial conditions for each state variable. 68 | t : ndarray, shape (n,) 69 | Array of time values to integrate over appropriate for the system. 70 | """ 71 | eqs, states = load_watrad() 72 | t = np.logspace(-6, 3, 200) # close to one hour of operation 73 | c0 = {'H2O': 55.4e3, 'H+': 1e-4, 'OH-': 1e-4} 74 | y0 = [c0.get(symb.name, 0) for symb in states] 75 | return y0, t 76 | 77 | 78 | def watrad_plot(t, y): 79 | """Generates a plot of the water radiolysis data. 80 | 81 | Parameters 82 | ---------- 83 | t : ndarray, shape (n,) 84 | Time values at which the system was evaluated. 85 | y : ndarray, shape (n, n_states) 86 | State variables at each time point. 87 | """ 88 | eqs, states = load_watrad() 89 | fig, ax = plt.subplots(1, 1, figsize=(14, 6)) 90 | ax = ax or plt.subplot(1, 1, 1) 91 | for i, state in enumerate(states): 92 | ax.plot(t, y[:, i], label='$%s$' % state.name) 93 | ax.set_ylabel('$\mathrm{concentration\ /\ mol \cdot dm^{-3}}$') 94 | ax.set_xlabel('$\mathrm{time\ /\ s}$') 95 | ax.legend(loc='best') 96 | ax.set_xscale('log') 97 | ax.set_yscale('log') 98 | -------------------------------------------------------------------------------- /scipy2017codegen/data/radiolysis_300_Gy_s.json: -------------------------------------------------------------------------------- 1 | {"names": ["e-(aq)", "H2O", "OH-", "H2", "H", "OH", "H2O2", "O2", "O2-", "HO2", "HO2-", "H+", "O-", "O3-"], "tex_names": ["e^{-}(aq)", "H_{2}O", "OH^{-}", "H_{2}", "H", "OH", "H_{2}O_{2}", "O_{2}", "O_{2}^{-}", "HO_{2}", "HO_{2}^{-}", "H^{+}", "O^{-}", "O_{3}^{-}"], "params": [], "reactions": [["7.26e+6", {"e-(aq)": 2}, {"H2O": -2, "OH-": 2, "e-(aq)": -2, "H2": 1}], ["5.14e+6", {"H": 2}, {"H": -2, "H2": 1}], ["4.81e+6", {"OH": 2}, {"OH": -2, "H2O2": 1}], ["2.76e+7", {"H": 1, "e-(aq)": 1}, {"H2O": -1, "H": -1, "OH-": 1, "e-(aq)": -1, "H2": 1}], ["3.55e+7", {"OH": 1, "e-(aq)": 1}, {"OH": -1, "OH-": 1, "e-(aq)": -1}], ["1.09e+7", {"H": 1, "OH": 1}, {"H2O": 1, "H": -1, "OH": -1}], ["1.36e+7", {"e-(aq)": 1, "H2O2": 1}, {"OH": 1, "OH-": 1, "H2O2": -1, "e-(aq)": -1}], ["2.29e+7", {"e-(aq)": 1, "O2": 1}, {"O2": -1, "e-(aq)": -1, "O2-": 1}], ["1.30e+7", {"e-(aq)": 1, "O2-": 1}, {"H2O": -2, "OH-": 2, "H2O2": 1, "O2-": -1, "e-(aq)": -1}], ["1.30e+7", {"e-(aq)": 1, "HO2": 1}, {"HO2-": 1, "e-(aq)": -1, "HO2": -1}], ["3.65e+4", {"H": 1, "H2O2": 1}, {"H2O": 1, "H": -1, "OH": 1, "H2O2": -1}], ["1.31e+7", {"H": 1, "O2": 1}, {"H": -1, "O2": -1, "HO2": 1}], ["1.14e+7", {"H": 1, "HO2": 1}, {"H": -1, "OH": 2, "HO2": -1}], ["1.14e+7", {"H": 1, "O2-": 1}, {"HO2-": 1, "H": -1, "O2-": -1}], ["2.92e+4", {"OH": 1, "H2O2": 1}, {"H2O": 1, "OH": -1, "H2O2": -1, "HO2": 1}], ["1.10e+7", {"OH": 1, "O2-": 1}, {"OH": -1, "OH-": 1, "O2-": -1, "O2": 1}], ["8.84e+6", {"OH": 1, "HO2": 1}, {"H2O": 1, "OH": -1, "HO2": -1, "O2": 1}], ["840.", {"HO2": 2}, {"HO2": -2, "H2O2": 1, "O2": 1}], ["1.01e+5", {"O2-": 1, "HO2": 1}, {"OH-": 1, "O2-": -1, "HO2": -1, "H2O": -1, "H2O2": 1, "O2": 1}], ["1.30e-7", {"H2O2": 1}, {"H2O": 2, "H2O2": -2, "O2": 1}], ["4.07e+6", {"HO2-": 1, "OH": 1}, {"H2O": 1, "HO2-": -1, "OH": -1, "O2-": 1}], ["4.07e+6", {"H2O2": 1, "O-": 1}, {"O-": -1, "OH-": 1, "H2O2": -1, "HO2": 1}], ["7.86e+5", {"HO2-": 1, "O-": 1}, {"HO2-": -1, "OH-": 1, "O-": -1, "O2-": 1}], ["1.28e+5", {"O-": 1, "H2": 1}, {"H": 1, "OH-": 1, "H2": -1, "O-": -1}], ["2.12e-5", {"H2O": 1}, {"H2O": -1, "OH-": 1, "H+": 1}], ["1.18e+8", {"OH-": 1, "H+": 1}, {"H2O": 1, "OH-": -1, "H+": -1}], ["0.0943", {"H2O2": 1}, {"HO2-": 1, "H2O2": -1, "H+": 1}], ["5.02e+7", {"HO2-": 1, "H+": 1}, {"HO2-": -1, "H2O2": 1, "H+": -1}], ["1.33e+7", {"OH-": 1, "H2O2": 1}, {"H2O": 1, "HO2-": 1, "OH-": -1, "H2O2": -1}], ["1.27e+3", {"H2O": 1, "HO2-": 1}, {"H2O": -1, "HO2-": -1, "OH-": 1, "H2O2": 1}], ["0.0943", {"OH": 1}, {"OH": -1, "O-": 1, "H+": 1}], ["5.02e+7", {"O-": 1, "H+": 1}, {"OH": 1, "O-": -1, "H+": -1}], ["1.33e+7", {"OH": 1, "OH-": 1}, {"H2O": 1, "OH": -1, "OH-": -1, "O-": 1}], ["1.27e+3", {"H2O": 1, "O-": 1}, {"H2O": -1, "OH": 1, "OH-": 1, "O-": -1}], ["7.73e+5", {"HO2": 1}, {"HO2": -1, "O2-": 1, "H+": 1}], ["5.02e+7", {"O2-": 1, "H+": 1}, {"O2-": -1, "HO2": 1, "H+": -1}], ["1.33e+7", {"OH-": 1, "HO2": 1}, {"H2O": 1, "OH-": -1, "HO2": -1, "O2-": 1}], ["0.000155", {"H2O": 1, "O2-": 1}, {"H2O": -1, "OH-": 1, "O2-": -1, "HO2": 1}], ["5.83", {"H": 1}, {"H": -1, "e-(aq)": 1, "H+": 1}], ["2.09e+7", {"e-(aq)": 1, "H+": 1}, {"H": 1, "e-(aq)": -1, "H+": -1}], ["2.44e+4", {"H": 1, "OH-": 1}, {"H2O": 1, "H": -1, "OH-": -1, "e-(aq)": 1}], ["0.0158", {"H2O": 1, "e-(aq)": 1}, {"H2O": -1, "H": 1, "OH-": 1, "e-(aq)": -1}], ["4.58e-8", {"H2O": 1, "H": 1}, {"H2O": -1, "H": -1, "OH": 1, "H2": 1}], ["3.95e+4", {"OH": 1, "H2": 1}, {"H2O": 1, "H": 1, "OH": -1, "H2": -1}], ["3.75e+6", {"O-": 1, "O2": 1}, {"O3-": 1, "O2": -1, "O-": -1}], ["2.62e+3", {"O3-": 1}, {"O3-": -1, "O2": 1, "O-": 1}], ["0.0188", {}, {"H": 1}], ["0.0136", {}, {"H2": 1}], ["0.0854", {}, {"H+": 1}], ["-0.131", {}, {"H2O": 1}], ["0.0871", {}, {"OH": 1}], ["0.0854", {}, {"e-(aq)": 1}], ["8.19e-7", {}, {"O2": 1}], ["0.0221", {}, {"H2O2": 1}]]} 2 | -------------------------------------------------------------------------------- /scipy2017codegen/exercise.py: -------------------------------------------------------------------------------- 1 | import IPython.core.magic as ipym 2 | 3 | @ipym.magics_class 4 | class ExerciseMagic(ipym.Magics): 5 | 6 | @ipym.line_magic 7 | def exercise(self, line, cell=None): 8 | token = ' # EXERCISE: ' 9 | out_lst = [] 10 | for ln in open(line).readlines(): 11 | if token in ln: 12 | pre, post = ln.split(token) 13 | out_lst.append(pre.replace(post.rstrip('\n'), '???') + '\n') 14 | else: 15 | out_lst.append(ln) 16 | out_str = '# %exercise {0}\n{1}'.format(line, ''.join(out_lst)) 17 | self.shell.set_next_input(out_str, replace=True) 18 | 19 | def load_ipython_extension(ipython): 20 | ipython.register_magics(ExerciseMagic) 21 | 22 | def unload_ipython_extension(ipython): 23 | pass 24 | -------------------------------------------------------------------------------- /scipy2017codegen/fastapprox/cast.h: -------------------------------------------------------------------------------- 1 | /*=====================================================================* 2 | * Copyright (C) 2012 Paul Mineiro * 3 | * All rights reserved. * 4 | * * 5 | * Redistribution and use in source and binary forms, with * 6 | * or without modification, are permitted provided that the * 7 | * following conditions are met: * 8 | * * 9 | * * Redistributions of source code must retain the * 10 | * above copyright notice, this list of conditions and * 11 | * the following disclaimer. * 12 | * * 13 | * * Redistributions in binary form must reproduce the * 14 | * above copyright notice, this list of conditions and * 15 | * the following disclaimer in the documentation and/or * 16 | * other materials provided with the distribution. * 17 | * * 18 | * * Neither the name of Paul Mineiro nor the names * 19 | * of other contributors may be used to endorse or promote * 20 | * products derived from this software without specific * 21 | * prior written permission. * 22 | * * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * 24 | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * 25 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * 26 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * 27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * 28 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * 29 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * 30 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * 31 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * 32 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * 33 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * 34 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * 35 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * 36 | * POSSIBILITY OF SUCH DAMAGE. * 37 | * * 38 | * Contact: Paul Mineiro * 39 | *=====================================================================*/ 40 | 41 | #ifndef __CAST_H_ 42 | 43 | #ifdef __cplusplus 44 | #define cast_uint32_t static_cast 45 | #else 46 | #define cast_uint32_t (uint32_t) 47 | #endif 48 | 49 | #endif // __CAST_H_ 50 | -------------------------------------------------------------------------------- /scipy2017codegen/fastapprox/fastexp.h: -------------------------------------------------------------------------------- 1 | /*=====================================================================* 2 | * Copyright (C) 2011 Paul Mineiro * 3 | * All rights reserved. * 4 | * * 5 | * Redistribution and use in source and binary forms, with * 6 | * or without modification, are permitted provided that the * 7 | * following conditions are met: * 8 | * * 9 | * * Redistributions of source code must retain the * 10 | * above copyright notice, this list of conditions and * 11 | * the following disclaimer. * 12 | * * 13 | * * Redistributions in binary form must reproduce the * 14 | * above copyright notice, this list of conditions and * 15 | * the following disclaimer in the documentation and/or * 16 | * other materials provided with the distribution. * 17 | * * 18 | * * Neither the name of Paul Mineiro nor the names * 19 | * of other contributors may be used to endorse or promote * 20 | * products derived from this software without specific * 21 | * prior written permission. * 22 | * * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * 24 | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * 25 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * 26 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * 27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * 28 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * 29 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * 30 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * 31 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * 32 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * 33 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * 34 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * 35 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * 36 | * POSSIBILITY OF SUCH DAMAGE. * 37 | * * 38 | * Contact: Paul Mineiro * 39 | *=====================================================================*/ 40 | 41 | #ifndef __FAST_EXP_H_ 42 | #define __FAST_EXP_H_ 43 | 44 | #include 45 | #include "cast.h" 46 | #include "sse.h" 47 | 48 | // Underflow of exponential is common practice in numerical routines, 49 | // so handle it here. 50 | 51 | static inline float 52 | fastpow2 (float p) 53 | { 54 | float offset = (p < 0) ? 1.0f : 0.0f; 55 | float clipp = (p < -126) ? -126.0f : p; 56 | int w = clipp; 57 | float z = clipp - w + offset; 58 | union { uint32_t i; float f; } v = { cast_uint32_t ( (1 << 23) * (clipp + 121.2740575f + 27.7280233f / (4.84252568f - z) - 1.49012907f * z) ) }; 59 | 60 | return v.f; 61 | } 62 | 63 | static inline float 64 | fastexp (float p) 65 | { 66 | return fastpow2 (1.442695040f * p); 67 | } 68 | 69 | static inline float 70 | fasterpow2 (float p) 71 | { 72 | float clipp = (p < -126) ? -126.0f : p; 73 | union { uint32_t i; float f; } v = { cast_uint32_t ( (1 << 23) * (clipp + 126.94269504f) ) }; 74 | return v.f; 75 | } 76 | 77 | static inline float 78 | fasterexp (float p) 79 | { 80 | return fasterpow2 (1.442695040f * p); 81 | } 82 | 83 | #ifdef __SSE2__ 84 | 85 | static inline v4sf 86 | vfastpow2 (const v4sf p) 87 | { 88 | v4sf ltzero = _mm_cmplt_ps (p, v4sfl (0.0f)); 89 | v4sf offset = _mm_and_ps (ltzero, v4sfl (1.0f)); 90 | v4sf lt126 = _mm_cmplt_ps (p, v4sfl (-126.0f)); 91 | v4sf clipp = _mm_or_ps (_mm_andnot_ps (lt126, p), _mm_and_ps (lt126, v4sfl (-126.0f))); 92 | v4si w = v4sf_to_v4si (clipp); 93 | v4sf z = clipp - v4si_to_v4sf (w) + offset; 94 | 95 | const v4sf c_121_2740838 = v4sfl (121.2740575f); 96 | const v4sf c_27_7280233 = v4sfl (27.7280233f); 97 | const v4sf c_4_84252568 = v4sfl (4.84252568f); 98 | const v4sf c_1_49012907 = v4sfl (1.49012907f); 99 | union { v4si i; v4sf f; } v = { 100 | v4sf_to_v4si ( 101 | v4sfl (1 << 23) * 102 | (clipp + c_121_2740838 + c_27_7280233 / (c_4_84252568 - z) - c_1_49012907 * z) 103 | ) 104 | }; 105 | 106 | return v.f; 107 | } 108 | 109 | static inline v4sf 110 | vfastexp (const v4sf p) 111 | { 112 | const v4sf c_invlog_2 = v4sfl (1.442695040f); 113 | 114 | return vfastpow2 (c_invlog_2 * p); 115 | } 116 | 117 | static inline v4sf 118 | vfasterpow2 (const v4sf p) 119 | { 120 | const v4sf c_126_94269504 = v4sfl (126.94269504f); 121 | v4sf lt126 = _mm_cmplt_ps (p, v4sfl (-126.0f)); 122 | v4sf clipp = _mm_or_ps (_mm_andnot_ps (lt126, p), _mm_and_ps (lt126, v4sfl (-126.0f))); 123 | union { v4si i; v4sf f; } v = { v4sf_to_v4si (v4sfl (1 << 23) * (clipp + c_126_94269504)) }; 124 | return v.f; 125 | } 126 | 127 | static inline v4sf 128 | vfasterexp (const v4sf p) 129 | { 130 | const v4sf c_invlog_2 = v4sfl (1.442695040f); 131 | 132 | return vfasterpow2 (c_invlog_2 * p); 133 | } 134 | 135 | #endif //__SSE2__ 136 | 137 | #endif // __FAST_EXP_H_ 138 | -------------------------------------------------------------------------------- /scipy2017codegen/fastapprox/fastlog.h: -------------------------------------------------------------------------------- 1 | /*=====================================================================* 2 | * Copyright (C) 2011 Paul Mineiro * 3 | * All rights reserved. * 4 | * * 5 | * Redistribution and use in source and binary forms, with * 6 | * or without modification, are permitted provided that the * 7 | * following conditions are met: * 8 | * * 9 | * * Redistributions of source code must retain the * 10 | * above copyright notice, this list of conditions and * 11 | * the following disclaimer. * 12 | * * 13 | * * Redistributions in binary form must reproduce the * 14 | * above copyright notice, this list of conditions and * 15 | * the following disclaimer in the documentation and/or * 16 | * other materials provided with the distribution. * 17 | * * 18 | * * Neither the name of Paul Mineiro nor the names * 19 | * of other contributors may be used to endorse or promote * 20 | * products derived from this software without specific * 21 | * prior written permission. * 22 | * * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * 24 | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * 25 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * 26 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * 27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * 28 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * 29 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * 30 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * 31 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * 32 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * 33 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * 34 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * 35 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * 36 | * POSSIBILITY OF SUCH DAMAGE. * 37 | * * 38 | * Contact: Paul Mineiro * 39 | *=====================================================================*/ 40 | 41 | #ifndef __FAST_LOG_H_ 42 | #define __FAST_LOG_H_ 43 | 44 | #include 45 | #include "sse.h" 46 | 47 | static inline float 48 | fastlog2 (float x) 49 | { 50 | union { float f; uint32_t i; } vx = { x }; 51 | union { uint32_t i; float f; } mx = { (vx.i & 0x007FFFFF) | 0x3f000000 }; 52 | float y = vx.i; 53 | y *= 1.1920928955078125e-7f; 54 | 55 | return y - 124.22551499f 56 | - 1.498030302f * mx.f 57 | - 1.72587999f / (0.3520887068f + mx.f); 58 | } 59 | 60 | static inline float 61 | fastlog (float x) 62 | { 63 | return 0.69314718f * fastlog2 (x); 64 | } 65 | 66 | static inline float 67 | fasterlog2 (float x) 68 | { 69 | union { float f; uint32_t i; } vx = { x }; 70 | float y = vx.i; 71 | y *= 1.1920928955078125e-7f; 72 | return y - 126.94269504f; 73 | } 74 | 75 | static inline float 76 | fasterlog (float x) 77 | { 78 | // return 0.69314718f * fasterlog2 (x); 79 | 80 | union { float f; uint32_t i; } vx = { x }; 81 | float y = vx.i; 82 | y *= 8.2629582881927490e-8f; 83 | return y - 87.989971088f; 84 | } 85 | 86 | #ifdef __SSE2__ 87 | 88 | static inline v4sf 89 | vfastlog2 (v4sf x) 90 | { 91 | union { v4sf f; v4si i; } vx = { x }; 92 | union { v4si i; v4sf f; } mx; mx.i = (vx.i & v4sil (0x007FFFFF)) | v4sil (0x3f000000); 93 | v4sf y = v4si_to_v4sf (vx.i); 94 | y *= v4sfl (1.1920928955078125e-7f); 95 | 96 | const v4sf c_124_22551499 = v4sfl (124.22551499f); 97 | const v4sf c_1_498030302 = v4sfl (1.498030302f); 98 | const v4sf c_1_725877999 = v4sfl (1.72587999f); 99 | const v4sf c_0_3520087068 = v4sfl (0.3520887068f); 100 | 101 | return y - c_124_22551499 102 | - c_1_498030302 * mx.f 103 | - c_1_725877999 / (c_0_3520087068 + mx.f); 104 | } 105 | 106 | static inline v4sf 107 | vfastlog (v4sf x) 108 | { 109 | const v4sf c_0_69314718 = v4sfl (0.69314718f); 110 | 111 | return c_0_69314718 * vfastlog2 (x); 112 | } 113 | 114 | static inline v4sf 115 | vfasterlog2 (v4sf x) 116 | { 117 | union { v4sf f; v4si i; } vx = { x }; 118 | v4sf y = v4si_to_v4sf (vx.i); 119 | y *= v4sfl (1.1920928955078125e-7f); 120 | 121 | const v4sf c_126_94269504 = v4sfl (126.94269504f); 122 | 123 | return y - c_126_94269504; 124 | } 125 | 126 | static inline v4sf 127 | vfasterlog (v4sf x) 128 | { 129 | // const v4sf c_0_69314718 = v4sfl (0.69314718f); 130 | // 131 | // return c_0_69314718 * vfasterlog2 (x); 132 | 133 | union { v4sf f; v4si i; } vx = { x }; 134 | v4sf y = v4si_to_v4sf (vx.i); 135 | y *= v4sfl (8.2629582881927490e-8f); 136 | 137 | const v4sf c_87_989971088 = v4sfl (87.989971088f); 138 | 139 | return y - c_87_989971088; 140 | } 141 | 142 | #endif // __SSE2__ 143 | 144 | #endif // __FAST_LOG_H_ 145 | -------------------------------------------------------------------------------- /scipy2017codegen/fastapprox/fastpow.h: -------------------------------------------------------------------------------- 1 | 2 | /*=====================================================================* 3 | * Copyright (C) 2011 Paul Mineiro * 4 | * All rights reserved. * 5 | * * 6 | * Redistribution and use in source and binary forms, with * 7 | * or without modification, are permitted provided that the * 8 | * following conditions are met: * 9 | * * 10 | * * Redistributions of source code must retain the * 11 | * above copyright notice, this list of conditions and * 12 | * the following disclaimer. * 13 | * * 14 | * * Redistributions in binary form must reproduce the * 15 | * above copyright notice, this list of conditions and * 16 | * the following disclaimer in the documentation and/or * 17 | * other materials provided with the distribution. * 18 | * * 19 | * * Neither the name of Paul Mineiro nor the names * 20 | * of other contributors may be used to endorse or promote * 21 | * products derived from this software without specific * 22 | * prior written permission. * 23 | * * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * 25 | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * 26 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * 27 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * 28 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * 29 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * 30 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * 31 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * 32 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * 33 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * 34 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * 35 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * 36 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * 37 | * POSSIBILITY OF SUCH DAMAGE. * 38 | * * 39 | * Contact: Paul Mineiro * 40 | *=====================================================================*/ 41 | 42 | #ifndef __FAST_POW_H_ 43 | #define __FAST_POW_H_ 44 | 45 | #include 46 | #include "sse.h" 47 | #include "fastexp.h" 48 | #include "fastlog.h" 49 | 50 | static inline float 51 | fastpow (float x, 52 | float p) 53 | { 54 | return fastpow2 (p * fastlog2 (x)); 55 | } 56 | 57 | static inline float 58 | fasterpow (float x, 59 | float p) 60 | { 61 | return fasterpow2 (p * fasterlog2 (x)); 62 | } 63 | 64 | #ifdef __SSE2__ 65 | 66 | static inline v4sf 67 | vfastpow (const v4sf x, 68 | const v4sf p) 69 | { 70 | return vfastpow2 (p * vfastlog2 (x)); 71 | } 72 | 73 | static inline v4sf 74 | vfasterpow (const v4sf x, 75 | const v4sf p) 76 | { 77 | return vfasterpow2 (p * vfasterlog2 (x)); 78 | } 79 | 80 | #endif //__SSE2__ 81 | 82 | #endif // __FAST_POW_H_ 83 | -------------------------------------------------------------------------------- /scipy2017codegen/fastapprox/sse.h: -------------------------------------------------------------------------------- 1 | /*=====================================================================* 2 | * Copyright (C) 2011 Paul Mineiro * 3 | * All rights reserved. * 4 | * * 5 | * Redistribution and use in source and binary forms, with * 6 | * or without modification, are permitted provided that the * 7 | * following conditions are met: * 8 | * * 9 | * * Redistributions of source code must retain the * 10 | * above copyright notice, this list of conditions and * 11 | * the following disclaimer. * 12 | * * 13 | * * Redistributions in binary form must reproduce the * 14 | * above copyright notice, this list of conditions and * 15 | * the following disclaimer in the documentation and/or * 16 | * other materials provided with the distribution. * 17 | * * 18 | * * Neither the name of Paul Mineiro nor the names * 19 | * of other contributors may be used to endorse or promote * 20 | * products derived from this software without specific * 21 | * prior written permission. * 22 | * * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * 24 | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * 25 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * 26 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * 27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * 28 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * 29 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * 30 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * 31 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * 32 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * 33 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * 34 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * 35 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * 36 | * POSSIBILITY OF SUCH DAMAGE. * 37 | * * 38 | * Contact: Paul Mineiro * 39 | *=====================================================================*/ 40 | 41 | #ifndef __SSE_H_ 42 | #define __SSE_H_ 43 | 44 | #ifdef __SSE2__ 45 | 46 | #include 47 | 48 | #ifdef __cplusplus 49 | namespace { 50 | #endif // __cplusplus 51 | 52 | typedef __m128 v4sf; 53 | typedef __m128i v4si; 54 | 55 | #define v4si_to_v4sf _mm_cvtepi32_ps 56 | #define v4sf_to_v4si _mm_cvttps_epi32 57 | 58 | #if _MSC_VER && !__INTEL_COMPILER 59 | template 60 | __forceinline char GetChar(T value, size_t index) { return ((char*)&value)[index]; } 61 | 62 | #define AS_4CHARS(a) \ 63 | GetChar(int32_t(a), 0), GetChar(int32_t(a), 1), \ 64 | GetChar(int32_t(a), 2), GetChar(int32_t(a), 3) 65 | 66 | #define _MM_SETR_EPI32(a0, a1, a2, a3) \ 67 | { AS_4CHARS(a0), AS_4CHARS(a1), AS_4CHARS(a2), AS_4CHARS(a3) } 68 | 69 | #define v4sfl(x) (const v4sf { (x), (x), (x), (x) }) 70 | #define v4sil(x) (const v4si _MM_SETR_EPI32(x, x, x, x)) 71 | 72 | __forceinline const v4sf operator+(const v4sf& a, const v4sf& b) { return _mm_add_ps(a,b); } 73 | __forceinline const v4sf operator-(const v4sf& a, const v4sf& b) { return _mm_sub_ps(a,b); } 74 | __forceinline const v4sf operator/(const v4sf& a, const v4sf& b) { return _mm_div_ps(a,b); } 75 | __forceinline const v4sf operator*(const v4sf& a, const v4sf& b) { return _mm_mul_ps(a,b); } 76 | 77 | __forceinline const v4sf operator+(const v4sf& a) { return a; } 78 | __forceinline const v4sf operator-(const v4sf& a) { return _mm_xor_ps(a, _mm_castsi128_ps(_mm_set1_epi32(0x80000000))); } 79 | 80 | __forceinline const v4sf operator&(const v4sf& a, const v4sf& b) { return _mm_and_ps(a,b); } 81 | __forceinline const v4sf operator|(const v4sf& a, const v4sf& b) { return _mm_or_ps(a,b); } 82 | __forceinline const v4sf operator^(const v4sf& a, const v4sf& b) { return _mm_xor_ps(a,b); } 83 | 84 | __forceinline const v4si operator&(const v4si& a, const v4si& b) { return _mm_and_si128(a,b); } 85 | __forceinline const v4si operator|(const v4si& a, const v4si& b) { return _mm_or_si128(a,b); } 86 | __forceinline const v4si operator^(const v4si& a, const v4si& b) { return _mm_xor_si128(a,b); } 87 | 88 | __forceinline const v4sf operator+=(v4sf& a, const v4sf& b) { return a = a + b; } 89 | __forceinline const v4sf operator-=(v4sf& a, const v4sf& b) { return a = a - b; } 90 | __forceinline const v4sf operator*=(v4sf& a, const v4sf& b) { return a = a * b; } 91 | __forceinline const v4sf operator/=(v4sf& a, const v4sf& b) { return a = a / b; } 92 | 93 | __forceinline const v4si operator|=(v4si& a, const v4si& b) { return a = a | b; } 94 | __forceinline const v4si operator&=(v4si& a, const v4si& b) { return a = a & b; } 95 | __forceinline const v4si operator^=(v4si& a, const v4si& b) { return a = a ^ b; } 96 | #else 97 | #define v4sfl(x) ((const v4sf) { (x), (x), (x), (x) }) 98 | #define v2dil(x) ((const v4si) { (x), (x) }) 99 | #define v4sil(x) v2dil((((long long) (x)) << 32) | (long long) (x)) 100 | #endif 101 | 102 | typedef union { v4sf f; float array[4]; } v4sfindexer; 103 | #define v4sf_index(_findx, _findi) \ 104 | ({ \ 105 | v4sfindexer _findvx = { _findx } ; \ 106 | _findvx.array[_findi]; \ 107 | }) 108 | typedef union { v4si i; int array[4]; } v4siindexer; 109 | #define v4si_index(_iindx, _iindi) \ 110 | ({ \ 111 | v4siindexer _iindvx = { _iindx } ; \ 112 | _iindvx.array[_iindi]; \ 113 | }) 114 | 115 | typedef union { v4sf f; v4si i; } v4sfv4sipun; 116 | #if _MSC_VER && !__INTEL_COMPILER 117 | #define v4sf_fabs(x) _mm_and_ps(x, _mm_castsi128_ps(_mm_set1_epi32(0x7fffffff))) 118 | #else 119 | #define v4sf_fabs(x) \ 120 | ({ \ 121 | v4sfv4sipun vx; \ 122 | vx.f = x; \ 123 | vx.i &= v4sil (0x7FFFFFFF); \ 124 | vx.f; \ 125 | }) 126 | #endif 127 | 128 | #ifdef __cplusplus 129 | } // end namespace 130 | #endif // __cplusplus 131 | 132 | #endif // __SSE2__ 133 | 134 | #endif // __SSE_H_ 135 | -------------------------------------------------------------------------------- /scipy2017codegen/magic.py: -------------------------------------------------------------------------------- 1 | import IPython.core.magic as ipym 2 | 3 | @ipym.magics_class 4 | class ExerciseMagic(ipym.Magics): 5 | 6 | @ipym.cell_magic 7 | def exercise(self, line, cell=None): 8 | token = ' # EXERCISE: ' 9 | output = [] 10 | for line in open(line).readlines(): 11 | if token in line: 12 | pre, post = line.split(token) 13 | output.append(pre.replace(post, '???')) 14 | else: 15 | output.append(line) 16 | self.shell.set_next_input('# %exercise %s\n%s' % (line, '\n'.join(output))) 17 | 18 | def load_ipython_extension(ipython): 19 | ipython.register_magics(ExerciseMagic) 20 | 21 | def unload_ipython_extension(ipython): 22 | pass 23 | -------------------------------------------------------------------------------- /scipy2017codegen/odesys.py: -------------------------------------------------------------------------------- 1 | from itertools import chain # Py 2.7 does not support func(*args1, *args2) 2 | import sympy as sym 3 | from scipy.integrate import odeint 4 | 5 | class ODEsys(object): 6 | 7 | default_integrator = 'odeint' 8 | 9 | def __init__(self, f, y, t=None, params=(), tex_names=None, lambdify=None): 10 | assert len(f) == len(y), 'f is dy/dt' 11 | self.f = tuple(f) 12 | self.y = tuple(y) 13 | self.t = t 14 | self.p = tuple(params) 15 | self.tex_names = tex_names 16 | self.j = sym.Matrix(self.ny, 1, f).jacobian(y) 17 | self.lambdify = lambdify or sym.lambdify 18 | self.setup() 19 | 20 | @property 21 | def ny(self): 22 | return len(self.y) 23 | 24 | def setup(self): 25 | self.lambdified_f = self.lambdify(self.y + self.p, self.f) 26 | self.lambdified_j = self.lambdify(self.y + self.p, self.j) 27 | 28 | def f_eval(self, y, t, *params): 29 | return self.lambdified_f(*chain(y, params)) 30 | 31 | def j_eval(self, y, t, *params): 32 | return self.lambdified_j(*chain(y, params)) 33 | 34 | def integrate(self, *args, **kwargs): 35 | integrator = kwargs.pop('integrator', self.default_integrator) 36 | return getattr(self, 'integrate_%s' % integrator)(*args, **kwargs) 37 | 38 | def integrate_odeint(self, tout, y0, params=(), rtol=1e-8, atol=1e-8, **kwargs): 39 | return odeint(self.f_eval, y0, tout, args=tuple(params), full_output=True, 40 | Dfun=self.j_eval, rtol=rtol, atol=atol, **kwargs) 41 | 42 | def print_info(self, info): 43 | if info is None: 44 | return 45 | nrhs = info.get('num_rhs') 46 | if not nrhs: 47 | nrhs = info['nfe'][-1] 48 | njac = info.get('num_dls_jac_evals') 49 | if not njac: 50 | njac = info['nje'][-1] 51 | print("The rhs was evaluated %d times and the jacobian %d times" % (nrhs, njac)) 52 | 53 | def plot_result(self, tout, yout, info=None, ax=None): 54 | ax = ax or plt.subplot(1, 1, 1) 55 | for i, label in enumerate(self.tex_names): 56 | ax.plot(tout, yout[:, i], label='$%s$' % label) 57 | ax.set_ylabel('$\mathrm{concentration\ /\ mol \cdot dm^{-3}}$') 58 | ax.set_xlabel('$\mathrm{time\ /\ s}$') 59 | ax.legend(loc='best') 60 | self.print_info(info) 61 | -------------------------------------------------------------------------------- /scipy2017codegen/odesys_cvode.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import uuid 4 | import sympy as sym 5 | import setuptools 6 | import numpy as np 7 | import setuptools 8 | import pyximport 9 | from scipy2017codegen import templates 10 | from scipy2017codegen.odesys import ODEsys 11 | 12 | pyximport.install() 13 | 14 | kw = { 15 | 'sources': [], 16 | 'include_dirs': [os.getcwd(), np.get_include()], 17 | 'libraries': ['sundials_cvode', 'sundials_nvecserial'], 18 | 'library_dirs': [], 19 | 'extra_compile_args': [], 20 | 'extra_link_args': [] 21 | } 22 | 23 | osx = sys.platform.lower() == 'darwin' 24 | win = os.name == 'nt' 25 | posix = os.name == 'posix' 26 | 27 | if not win: 28 | kw['libraries'] += ['m'] 29 | 30 | if posix: 31 | kw['libraries'] += ['openblas'] 32 | 33 | 34 | class ODEcvode(ODEsys): 35 | 36 | default_integrator = 'cvode' 37 | 38 | def setup(self): 39 | self.uid = uuid.uuid4().hex[:10] 40 | self.mod_name = 'ode_c_%s' % self.uid 41 | idxs = list(range(len(self.f))) 42 | subs = {s: sym.Symbol('y[%d]' % i) for i, s in enumerate(self.y)} 43 | f_exprs = ['out[%d] = %s;' % (i, sym.ccode(self.f[i].xreplace(subs))) 44 | for i in idxs] 45 | j_col_defs = ['realtype * const col_%d = DENSE_COL(J, %d);' % (ci, ci) 46 | for ci in idxs] 47 | j_exprs = ['col_%d[%d] = %s;' % (ci, ri, self.j[ri, ci].xreplace(subs)) 48 | for ci in idxs for ri in idxs if self.j[ri, ci] != 0] 49 | ctx = dict( 50 | func = '\n '.join(f_exprs + ['return 0;']), 51 | dense_jac = '\n '.join(j_col_defs + j_exprs + ['return 0;']), 52 | band_jac = 'return -1;' 53 | ) 54 | open('integrate_serial_%s.c' % self.uid, 'wt').write(templates.sundials['integrate_serial.c'] % ctx) 55 | open('%s.pyx' % self.mod_name, 'wt').write(templates.sundials['_integrate_serial.pyx'] % {'uid': self.uid}) 56 | open('%s.pyxbld' % self.mod_name, 'wt').write(templates.pyxbld % kw) 57 | self.mod = __import__(self.mod_name) 58 | self.integrate_odeint = None 59 | 60 | def integrate_cvode(self, tout, y0, params=(), rtol=1e-8, atol=1e-8, **kwargs): 61 | return self.mod._integrate(np.asarray(tout, dtype=np.float64), 62 | np.asarray(y0, dtype=np.float64), 63 | np.atleast_1d(np.asarray(params, dtype=np.float64)), 64 | abstol=np.atleast_1d(np.asarray(atol, dtype=np.float64)), 65 | reltol=rtol, 66 | **kwargs) 67 | -------------------------------------------------------------------------------- /scipy2017codegen/odesys_cython.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | import numpy as np 3 | import sympy as sym 4 | import setuptools 5 | import pyximport 6 | from scipy2017codegen import templates 7 | from scipy2017codegen.odesys import ODEsys 8 | 9 | pyximport.install() 10 | 11 | cython_template = """ 12 | cimport numpy as cnp 13 | import numpy as np 14 | 15 | def f(cnp.ndarray[cnp.float64_t, ndim=1] y, double t, %(args)s): 16 | cdef cnp.ndarray[cnp.float64_t, ndim=1] out = np.empty(y.size) 17 | %(f_exprs)s 18 | return out 19 | 20 | def j(cnp.ndarray[cnp.float64_t, ndim=1] y, double t, %(args)s): 21 | cdef cnp.ndarray[cnp.float64_t, ndim=2] out = np.empty((y.size, y.size)) 22 | %(j_exprs)s 23 | return out 24 | 25 | """ 26 | 27 | class CythonODEsys(ODEsys): 28 | 29 | def setup(self): 30 | self.mod_name = 'ode_cython_%s' % uuid.uuid4().hex[:10] 31 | idxs = list(range(len(self.f))) 32 | subs = {s: sym.Symbol('y[%d]' % i) for i, s in enumerate(self.y)} 33 | f_exprs = ['out[%d] = %s' % (i, str(self.f[i].xreplace(subs))) for i in idxs] 34 | j_exprs = ['out[%d, %d] = %s' % (ri, ci, self.j[ri, ci].xreplace(subs)) for ri in idxs for ci in idxs] 35 | ctx = dict( 36 | args=', '.join(map(str, self.p)), 37 | f_exprs = '\n '.join(f_exprs), 38 | j_exprs = '\n '.join(j_exprs), 39 | ) 40 | open('%s.pyx' % self.mod_name, 'wt').write(cython_template % ctx) 41 | open('%s.pyxbld' % self.mod_name, 'wt').write(templates.pyxbld % dict( 42 | sources=[], include_dirs=[np.get_include()], 43 | library_dirs=[], libraries=[], extra_compile_args=[], extra_link_args=[] 44 | )) 45 | mod = __import__(self.mod_name) 46 | self.f_eval = mod.f 47 | self.j_eval = mod.j 48 | -------------------------------------------------------------------------------- /scipy2017codegen/odesys_diffusion.py: -------------------------------------------------------------------------------- 1 | from itertools import chain 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from scipy2017codegen.odesys import ODEsys 5 | 6 | 7 | class MOLsys(ODEsys): 8 | """ System of ODEs based on method of lines on the interval x = [0, x_end] """ 9 | 10 | def __init__(self, *args, **kwargs): 11 | self.x_end = kwargs.pop('x_end') 12 | self.n_lines = kwargs.pop('n_lines') 13 | self.D = kwargs.pop('D') 14 | self.dx = self.x_end / self.n_lines 15 | super(MOLsys, self).__init__(*args, **kwargs) 16 | 17 | def f_eval(self, y, t, *params): 18 | f_out = np.empty(self.ny*self.n_lines) 19 | for i in range(self.n_lines): 20 | slc = slice(i*self.ny, (i+1)*self.ny) 21 | y_bis = self.second_derivatives_spatial(i, y, f_out[slc]) 22 | f_out[slc] *= self.D 23 | f_out[slc] += self.lambdified_f(*chain(y[slc], params)) 24 | return f_out 25 | 26 | def central_reference_bin(self, i): 27 | return np.clip(i, 1, self.ny - 2) 28 | 29 | def j_eval(self, y, t, *params): 30 | j_out = np.zeros((self.ny*self.n_lines, self.ny*self.n_lines)) # dense matrix 31 | for i in range(self.n_lines): 32 | slc = slice(i*self.ny, (i+1)*self.ny) 33 | j_out[slc, slc] = self.lambdified_j(*chain(y[slc], params)) 34 | k = self.central_reference_bin(i) 35 | for j in range(self.ny): 36 | j_out[i*self.ny + j, (k-1)*self.ny + j] += self.D[j]/self.dx**2 37 | j_out[i*self.ny + j, (k )*self.ny + j] += -2*self.D[j]/self.dx**2 38 | j_out[i*self.ny + j, (k+1)*self.ny + j] += self.D[j]/self.dx**2 39 | return j_out 40 | 41 | def second_derivatives_spatial(self, i, y, out): 42 | k = self.central_reference_bin(i) 43 | for j in range(self.ny): 44 | left = y[(k-1)*self.ny + j] 45 | cent = y[(k )*self.ny + j] 46 | rght = y[(k+1)*self.ny + j] 47 | out[j] = (left - 2*cent + rght)/self.dx**2 48 | 49 | def integrate(self, tout, y0, params=(), **kwargs): 50 | y0 = np.array(np.vstack(y0).T.flat) 51 | yout, info = super(MOLsys, self).integrate(tout, y0, params, **kwargs) 52 | return yout.reshape((tout.size, self.n_lines, self.ny)).transpose((0, 2, 1)), info 53 | 54 | def x_centers(self): 55 | return np.linspace(self.dx/2, self.x_end - self.dx/2, self.n_lines) 56 | 57 | def plot_result(self, tout, yout, info=None, ax=None): 58 | ax = ax or plt.subplot(1, 1, 1) 59 | x_lines = self.x_centers() 60 | for i, t in enumerate(tout): 61 | for j in range(self.ny): 62 | c = [0.0, 0.0, 0.0] 63 | c[j] = t/tout[-1] 64 | plt.plot(x_lines, yout[i, j, :], color=c) 65 | self.print_info(info) 66 | -------------------------------------------------------------------------------- /scipy2017codegen/odesys_vode.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.integrate import ode 3 | from scipy2017codegen.odesys import ODEsys 4 | 5 | class VODEsys(ODEsys): 6 | default_integrator = 'vode' 7 | 8 | def integrate_vode(self, tout, y0, params=(), method='bdf', rtol=1e-8, atol=1e-8, **kwargs): 9 | def f(t, y, *args): 10 | f.ncall +=1 11 | return np.asarray(self.f_eval(y, t, *args)) 12 | f.ncall = 0 13 | def j(t, y, *args): 14 | j.ncall += 1 15 | return np.asarray(self.j_eval(y, t, *args)) 16 | j.ncall = 0 17 | r = ode(f, j) 18 | r.set_integrator('vode', method=method, rtol=rtol, atol=atol, **kwargs) 19 | yout = np.zeros((len(tout), len(y0))) 20 | yout[0, :] = y0 21 | r.set_initial_value(yout[0, :], tout[0]) 22 | if params: 23 | r.set_f_params(params) 24 | r.set_jac_params(params) 25 | for idx in range(1, len(tout)): 26 | r.integrate(tout[idx]) 27 | assert r.successful(), "Integration failed" 28 | yout[idx, :] = r.y 29 | return yout, {'num_rhs': f.ncall, 'num_dls_jac_evals': j.ncall} 30 | -------------------------------------------------------------------------------- /scipy2017codegen/plotting.py: -------------------------------------------------------------------------------- 1 | import sympy as sym 2 | from sympy.printing.jscode import JavascriptCodePrinter 3 | 4 | 5 | def batman_equations(): 6 | 7 | x = sym.symbols('x', real=True) 8 | 9 | shoulder = ((sym.S(6) * sym.Abs(sym.sqrt(10)) / 7 + (sym.S(3) / 2 - 10 | sym.Abs(x) / 2)) - (sym.S(6) * sym.Abs(sym.sqrt(10)) / 14) * 11 | sym.Abs(sym.sqrt(4 - (sym.Abs(x) - 1)**2))) 12 | cheek = 9 - 8 * sym.Abs(x) 13 | ear = 3 * sym.Abs(x) + sym.S(3) / 4 14 | head = 2 + sym.S(2) / 4 15 | top_wing = 3 * sym.sqrt(-x**2 + 49) / 7 16 | bottom_wing = -top_wing 17 | tail = ((sym.Abs(x / 2) - ((3 * sym.sqrt(33) - 7) / 112) * x**2 - 3) + 18 | sym.sqrt(1 - (sym.Abs(sym.Abs(x) - 2) - 1)**2)) 19 | 20 | top = sym.Piecewise( 21 | (top_wing, x >= 3), 22 | (shoulder, x >= 1), 23 | (cheek, x >= sym.S(3) / 4), 24 | (ear, x >= sym.S(7) / 12), 25 | (head, x >= -sym.S(7) / 12), 26 | (ear, x >= -sym.S(3) / 4), 27 | (cheek, x >= -1), 28 | (shoulder, x >= -3), 29 | (top_wing, True)) 30 | 31 | bottom = sym.Piecewise( 32 | (bottom_wing, x >= 4), 33 | (tail, x >= -4), 34 | (bottom_wing, True)) 35 | 36 | return top, bottom 37 | 38 | 39 | def batman_equations_heaviside(): 40 | # From : http://mathworld.wolfram.com/BatmanCurve.html 41 | 42 | x = sym.symbols('x', real=True) 43 | h_ = sym.symbols('h_') 44 | 45 | w = 3 * sym.sqrt(1 - (x / 7)**2) 46 | l = ((x + 3) / 2 - sym.S(3) / 7 * sym.sqrt(10) * sym.sqrt(4 - (x + 1)**2) + 47 | sym.S(6) / 7 * sym.sqrt(10)) 48 | r = ((3 - x) / 2 - sym.S(3) / 7 * sym.sqrt(10) * sym.sqrt(4 - (x - 1)**2) + 49 | sym.S(6) / 7 * sym.sqrt(10)) 50 | f = ((h_ - l) * sym.Heaviside(x + 1, 0) + 51 | (r - h_) * sym.Heaviside(x - 1, 0) + 52 | (l - w) * sym.Heaviside(x + 3, 0) + 53 | (w - r) * sym.Heaviside(x - 3, 0) + 54 | w) 55 | f_of = f.xreplace({x: sym.Abs(x + sym.S(1) / 2) + 56 | sym.Abs(x - sym.S(1) / 2) + 6}) 57 | h = sym.S(1) / 2 * (f_of - 11 * (x + sym.S(3) / 4) + sym.Abs(x - sym.S(3) / 4)) 58 | f = f.xreplace({h_: h}) 59 | g = (sym.S(1) / 2 * (sym.Abs(x / 2) + sym.sqrt(1 - (sym.Abs(sym.Abs(x) - 2) - 60 | 1)**2) - sym.S(1) / 112 * (3 * sym.sqrt(33) - 7) * x**2 + 3 * 61 | sym.sqrt(1 - (sym.S(1) / 7 * x)**2) - 3) * ((x + 4) / sym.Abs(x + 4) - 62 | (x - 4) / sym.Abs(x - 4)) - 3 * sym.sqrt(1 - (x / 7)**2)) 63 | 64 | return f, g 65 | 66 | 67 | def batman_equations_implicit(): 68 | # try different form that seems to have numerical accuracy issues 69 | # From: https://gist.github.com/traeblain/1487795 70 | x, y = sym.symbols('x, y') 71 | eq1 = ((x/7)**2*sym.sqrt(sym.Abs(sym.Abs(x)-3)/(sym.Abs(x)-3))+(y/3)**2* 72 | sym.sqrt(sym.Abs(y+3/7*sym.sqrt(33))/(y+3/7*sym.sqrt(33)))-1) 73 | eq2 = (sym.Abs(x/2)-((3*sym.sqrt(33)-7)/112)*x**2-3+ 74 | sym.sqrt(1-(sym.Abs(sym.Abs(x)-2)-1)**2)-y) 75 | eq3 = (9*sym.sqrt(sym.Abs((sym.Abs(x)-1)*(sym.Abs(x)-.75))/((1-sym.Abs(x))* 76 | (sym.Abs(x)-.75)))-8*sym.Abs(x)-y) 77 | eq4 = (3*sym.Abs(x)+.75*sym.sqrt(sym.Abs((sym.Abs(x)-.75)*(sym.Abs(x)-.5))/ 78 | ((.75-sym.Abs(x))*(sym.Abs(x)-.5)))-y) 79 | eq5 = (2.25*sym.sqrt(sym.Abs((x-.5)*(x+.5))/((.5-x)*(.5+x)))-y) 80 | eq6 = (6*sym.sqrt(10)/7+(1.5-.5*sym.Abs(x))*sym.sqrt(sym.Abs(sym.Abs(x)-1)/ 81 | (sym.Abs(x)-1))-(6*sym.sqrt(10)/14)*sym.sqrt(4-(sym.Abs(x)-1)**2)-y) 82 | 83 | return eq1, eq2, eq3, eq4, eq5, eq6 84 | 85 | 86 | class JSHeavisidePrinter(JavascriptCodePrinter): 87 | """Slight mod to have Heavisides print so they can be plotted.""" 88 | 89 | def _print_Heaviside(self, expr): 90 | # NOTE : expr.rewrite(sym.Piecewise) almost does the right thing. 91 | P = sym.Piecewise((0, expr.args[0] < 0), (1, expr.args[0] >= 0), 92 | (sym.S(1) / 2, True)) 93 | return self._print(P) 94 | 95 | 96 | js_template = """\ 97 | 98 | require(['chartjs'], function(chartjs){{ 99 | 100 | function f(x) {{ 101 | return {top_function} 102 | }}; 103 | 104 | function g(x) {{ 105 | return {bottom_function} 106 | }}; 107 | 108 | function linspace(a,b,n) {{ 109 | // From: https://gist.github.com/joates/6584908 110 | if(typeof n === "undefined") n = Math.max(Math.round(b-a)+1,1); 111 | if(n<2) {{ return n===1?[a]:[]; }} 112 | var i,ret = Array(n); 113 | n--; 114 | for(i=n;i>=0;i--) {{ ret[i] = (i*b+(n-i)*a)/n; }} 115 | return ret; 116 | }} 117 | 118 | var ctx = document.getElementById("{chart_id}"); 119 | var data = {{ 120 | labels: linspace(-7.5, 7.5, 500), 121 | datasets: [{{ 122 | label: "top", 123 | function: f, 124 | borderColor: "rgba(75, 192, 192, 1)", 125 | data: [], 126 | fill: false, 127 | lineTension: 0, 128 | }}, 129 | {{ 130 | label: "bottom", 131 | function: g, 132 | borderColor: "rgba(153, 102, 255, 1)", 133 | data: [], 134 | fill: false, 135 | lineTension: 0, 136 | }}] 137 | }}; 138 | 139 | chartjs.Chart.pluginService.register({{ 140 | beforeInit: function(chart) {{ 141 | var data = chart.config.data; 142 | for (var i = 0; i < data.datasets.length; i++) {{ 143 | for (var j = 0; j < data.labels.length; j++) {{ 144 | var fct = data.datasets[i].function, 145 | x = data.labels[j], 146 | y = fct(x); 147 | data.datasets[i].data.push(y); 148 | }} 149 | }} 150 | }} 151 | }}); 152 | 153 | var myBarChart = new chartjs.Chart(ctx, {{ 154 | type: 'line', 155 | data: data, 156 | options: {{ 157 | scales: {{ 158 | yAxes: [{{ 159 | ticks: {{ 160 | beginAtZero:true 161 | }} 162 | }}] 163 | }} 164 | }} 165 | }}); 166 | 167 | }}); 168 | 169 | element.append("");\ 170 | """ 171 | -------------------------------------------------------------------------------- /scipy2017codegen/sundials_templates/_integrate_serial.pyx: -------------------------------------------------------------------------------- 1 | from libc.stdlib cimport malloc, free 2 | cimport numpy as cnp 3 | import numpy as np 4 | 5 | cdef extern from "integrate_serial_%(uid)s.c": 6 | int integrate(double *, double *, int, int, void*, double*, double, 7 | double, double, double*, int, int, int, int, long int *) 8 | 9 | def _integrate(cnp.ndarray[cnp.float64_t, ndim=1] tout, 10 | cnp.ndarray[cnp.float64_t, ndim=1] y0, 11 | cnp.ndarray[cnp.float64_t, ndim=1] params, 12 | cnp.ndarray[cnp.float64_t, ndim=1] abstol, 13 | double reltol, 14 | double h_init=0.0, double h_max=0.0, 15 | step_type='bdf', # or 'adams' 16 | mode='dense', # or 'banded' 17 | int mu=0, 18 | int ml=0): 19 | cdef: 20 | int status = 0 21 | int nt = tout.size 22 | int ny = y0.size 23 | long int * info = malloc(8*sizeof(long int)) 24 | cnp.ndarray[cnp.float64_t, ndim=2] yout = np.empty((nt, ny)) 25 | if abstol.size == 1: 26 | abstol = np.tile(abstol, y0.size) 27 | assert abstol.size == y0.size, 'abstol size mismatch' 28 | status = integrate(&tout[0], &y0[0], nt, ny, (params.data), &abstol[0], reltol, 29 | h_init, h_max, &yout[0, 0], {'adams': 1, 'bdf': 2}[step_type.lower()], 30 | {'dense': 1, 'banded': 2}[mode.lower()], mu, ml, info) 31 | nfo = { 32 | 'num_steps': info[0], 33 | 'num_rhs': info[1], 34 | 'num_lin_solv_setups': info[2], 35 | 'num_err_test_fails': info[3], 36 | 'num_nonlin_solv_iters': info[4], 37 | 'num_nonlin_solv_conv_fails': info[5], 38 | 'num_dls_jac_evals': info[6], 39 | 'num_dls_rhs_evals': info[7], 40 | 'status': status 41 | } 42 | free(info) 43 | return yout, nfo 44 | -------------------------------------------------------------------------------- /scipy2017codegen/sundials_templates/integrate_serial.c: -------------------------------------------------------------------------------- 1 | #include /* prototypes for CVODE fcts., CV_BDF, CV_ADAMS */ 2 | #include /* serial N_Vector types, fcts., macros */ 3 | 4 | #include /* definitions DlsMat DENSE_ELEM */ 5 | #include /* definition of type realtype */ 6 | 7 | #include // memcpy 8 | #if defined(WITH_LAPACK) 9 | #include /* prototype for CVDense */ 10 | // Sundials 2.7.0 changed int -> long int, but BLAS uses int 11 | // We use DIM_T defined here: 12 | #define DIM_T int 13 | #define OUR_DENSE CVLapackDense 14 | #define OUR_BAND CVLapackBand 15 | #else 16 | #include /* prototype for CVDense */ 17 | #include /* prototype for CVBand */ 18 | #define OUR_DENSE CVDense 19 | #define OUR_BAND CVBand 20 | #define DIM_T long int 21 | #endif 22 | 23 | #ifdef _MSC_VER 24 | # ifndef restrict 25 | # define restrict __restrict 26 | # endif 27 | #endif 28 | 29 | 30 | int func (realtype t, N_Vector nv_y, N_Vector f, void * params) { 31 | const realtype * const restrict y = &NV_Ith_S(nv_y, 0); 32 | realtype * const restrict out = &NV_Ith_S(f, 0); 33 | %(func)s 34 | } 35 | 36 | int dense_jac (DIM_T N, realtype t, N_Vector nv_y, N_Vector fy, DlsMat J, void *params, N_Vector tmp1, N_Vector tmp2, N_Vector tmp3) { 37 | const realtype * const restrict y = &NV_Ith_S(nv_y, 0); 38 | %(dense_jac)s 39 | } 40 | 41 | int band_jac (DIM_T N, long int mu, long int ml, realtype t, N_Vector y, N_Vector fy, DlsMat J, void *params, N_Vector tmp1, N_Vector tmp2, N_Vector tmp3) { 42 | %(band_jac)s 43 | } 44 | 45 | enum { 46 | STATUS_FOUT = 1000, 47 | STATUS_Y, 48 | STATUS_ABSTOL, 49 | STATUS_STEP_TYPE, 50 | STATUS_CVODE_MEM, 51 | STATUS_MODE 52 | }; 53 | 54 | int integrate(const realtype * const restrict tout, 55 | const realtype * const restrict y0, 56 | int nt, DIM_T ny, 57 | void * params, 58 | const realtype * const restrict abstol, 59 | realtype reltol, 60 | realtype h_init, realtype h_max, 61 | realtype * const restrict yout, 62 | int step_type_idx, // 1 => CV_ADAMS, 2 => CV_BDF 63 | int mode, // 1 => SUNDIALS_DENSE, 2 => SUNDIALS_BAND 64 | int mu, // number of upper diagonals (when banded) 65 | int ml, // number of lower diagonals (when banded) 66 | long int * const restrict info) 67 | { 68 | int i; 69 | int status = 0; 70 | N_Vector nv_y = NULL; 71 | N_Vector nv_abstol = NULL; 72 | void *cvode_mem = NULL; 73 | realtype cur_t = tout[0]; 74 | 75 | nv_y = N_VMake_Serial(ny, (realtype *)y0); 76 | if (nv_y == NULL){ 77 | status = STATUS_Y; 78 | goto exit_y; 79 | } 80 | 81 | nv_abstol = N_VMake_Serial(ny, (realtype *)abstol); 82 | if (nv_abstol == NULL){ 83 | status = STATUS_ABSTOL; 84 | goto exit_abstol; 85 | } 86 | 87 | if (step_type_idx == 1){ 88 | step_type_idx = CV_ADAMS; 89 | }else if (step_type_idx == 2) { 90 | step_type_idx = CV_BDF; 91 | }else{ 92 | status = STATUS_STEP_TYPE; 93 | goto exit_abstol; 94 | } 95 | 96 | // For now we skip CV_FUNCTIONAL only use CV_NEWTON 97 | cvode_mem = CVodeCreate(step_type_idx, CV_NEWTON); 98 | if (cvode_mem == NULL){ 99 | status = STATUS_CVODE_MEM; 100 | goto exit_cvode_mem; 101 | } 102 | 103 | status = CVodeInit(cvode_mem, func, tout[0], nv_y); 104 | if (status != 0) goto exit_runtime; 105 | 106 | status = CVodeSVtolerances(cvode_mem, reltol, nv_abstol); 107 | if (status != 0) goto exit_runtime; 108 | 109 | /* Call CVDense/CVLapackDense to specify the dense linear solver */ 110 | switch(mode){ 111 | case(1): // SUNDIALS_DENSE 112 | status = OUR_DENSE(cvode_mem, ny); 113 | if (status != 0) goto exit_runtime; 114 | /* Set the Jacobian routine to Jac (user-supplied) */ 115 | status = CVDlsSetDenseJacFn(cvode_mem, dense_jac); 116 | break; 117 | case(2): // SUNDIALS_BAND 118 | status = OUR_BAND(cvode_mem, ny, mu, ml); 119 | if (status != 0) goto exit_runtime; 120 | status = CVDlsSetBandJacFn(cvode_mem, band_jac); 121 | break; 122 | default: 123 | status = STATUS_MODE; 124 | } 125 | if (status != 0) goto exit_runtime; 126 | 127 | status = CVodeSetUserData(cvode_mem, params); 128 | if (status != 0) goto exit_runtime; 129 | 130 | if (h_init > 0.0) CVodeSetInitStep(cvode_mem, h_init); 131 | if (h_max > 0.0) CVodeSetMaxStep(cvode_mem, h_max); 132 | 133 | /* Store output before first step */ 134 | memcpy(yout, y0, sizeof(realtype)*ny); 135 | /* Run integration */ 136 | for (i = 1; i < nt; ++i){ 137 | status = CVode(cvode_mem, tout[i], nv_y, &cur_t, CV_NORMAL); 138 | if (status != CV_SUCCESS) 139 | break; 140 | memcpy(yout + ny*i, &NV_Ith_S(nv_y, 0), sizeof(realtype)*ny); // copy to output argument; 141 | } 142 | CVodeGetNumSteps(cvode_mem, info); 143 | CVodeGetNumRhsEvals(cvode_mem, info + 1); 144 | CVodeGetNumLinSolvSetups(cvode_mem, info + 2); 145 | CVodeGetNumErrTestFails(cvode_mem, info + 3); 146 | CVodeGetNumNonlinSolvIters(cvode_mem, info + 4); 147 | CVodeGetNumNonlinSolvConvFails(cvode_mem, info + 5); 148 | CVDlsGetNumJacEvals(cvode_mem, info + 6); 149 | CVDlsGetNumRhsEvals(cvode_mem, info + 7); 150 | // Error handling 151 | exit_runtime: 152 | CVodeFree(&cvode_mem); 153 | exit_cvode_mem: 154 | N_VDestroy_Serial(nv_abstol); 155 | exit_abstol: 156 | N_VDestroy_Serial(nv_y); 157 | exit_y: 158 | return status; 159 | } 160 | -------------------------------------------------------------------------------- /scipy2017codegen/template.pyxbld: -------------------------------------------------------------------------------- 1 | def get_nt_compiler(): 2 | import os 3 | return os.getenv('SCIPY2017CODEGEN_COMPILER', 'msvc') # 'mingw32' or 'msvc' 4 | 5 | def make_ext(modname, pyxfilename): 6 | import os 7 | import sys 8 | from distutils.extension import Extension 9 | import scipy2017codegen 10 | 11 | sources = [pyxfilename] 12 | sources.extend(%(sources)s) 13 | 14 | libraries = %(libraries)s 15 | 16 | include_dirs = ['.'] 17 | include_dirs.extend(%(include_dirs)s) 18 | 19 | library_dirs = %(library_dirs)s 20 | 21 | extra_compile_args = ['-std=c99'] 22 | extra_compile_args.extend(%(extra_compile_args)s) 23 | 24 | extra_link_args = %(extra_link_args)s 25 | 26 | osx = sys.platform.lower() == 'darwin' 27 | win = os.name == 'nt' 28 | posix = os.name == 'posix' 29 | under_conda = 'CONDA_PREFIX' in os.environ 30 | 31 | if under_conda: 32 | prefix_path = lambda *args: os.path.join(os.environ['CONDA_PREFIX'], *args) 33 | if win and get_nt_compiler() == 'mingw32': 34 | ext.library_dirs.append(os.environ['CONDA_PREFIX']) # libpythonXX.dll is located in the environment root 35 | library_dirs.append(prefix_path('Library', 'lib') if osx or win else prefix_path('lib')) 36 | include_dirs.append(prefix_path('Library', 'include') if osx or win else prefix_path('include')) 37 | include_dirs.append(os.path.dirname(scipy2017codegen.__file__)) 38 | 39 | ext = Extension(modname, 40 | sources=sources, 41 | libraries=libraries, 42 | include_dirs=include_dirs, 43 | library_dirs=library_dirs, 44 | extra_compile_args=extra_compile_args, 45 | extra_link_args=extra_link_args) 46 | 47 | return ext 48 | 49 | def make_setup_args(): 50 | import os 51 | if os.name == 'nt': 52 | return dict(script_args=['--compiler={0}'.format(get_nt_compiler())]) 53 | else: 54 | return {} 55 | -------------------------------------------------------------------------------- /scipy2017codegen/templates.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import os 3 | 4 | def _path(*args): 5 | return os.path.join(os.path.dirname(__file__), *args) 6 | 7 | # pyxbld for pyximport (from cython): 8 | pyxbld = open(_path('template.pyxbld')).read() 9 | 10 | # Sundials: 11 | sundials_templates_dir = _path('sundials_templates') 12 | 13 | sundials = { 14 | os.path.basename(pth): open(pth).read() for pth in glob.glob( 15 | os.path.join(sundials_templates_dir, '*.*')) 16 | } 17 | 18 | def render_pyxbld(pyxbasename, **kwargs): 19 | if 'sources' not in kwargs: 20 | kwargs['sources'] = [] 21 | if 'include_dirs' not in kwargs: 22 | kwargs['include_dirs'] = [] 23 | if 'library_dirs' not in kwargs: 24 | kwargs['library_dirs'] = [] 25 | if 'libraries' not in kwargs: 26 | kwargs['libraries'] = [] 27 | if 'extra_compile_args' not in kwargs: 28 | kwargs['extra_compile_args'] = [] 29 | if 'extra_link_args' not in kwargs: 30 | kwargs['extra_link_args'] = [] 31 | open(pyxbasename + '.pyxbld', 'wt').write(pyxbld % kwargs) 32 | -------------------------------------------------------------------------------- /scipy2017codegen/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sympy/scipy-2017-codegen-tutorial/4bd0cdb1bdbdc796bb90c08114a00e390b3d3026/scipy2017codegen/tests/__init__.py -------------------------------------------------------------------------------- /scipy2017codegen/tests/test_cvode.py: -------------------------------------------------------------------------------- 1 | import json 2 | import numpy as np 3 | 4 | 5 | from cvode import ODEcvode 6 | from chem import odesys_from_reactions_names_and_params 7 | 8 | def test_ODEcvode(): 9 | watrad_data = json.load(open('radiolysis_300_Gy_s.json')) 10 | cvode_sys = odesys_from_reactions_names_and_params(ODEcls=ODEcvode, **watrad_data) 11 | tout = np.logspace(-6, 3, 200) # close to one hour of operation 12 | c0 = {'H2O': 55.4e3, 'H+': 1e-4, 'OH-': 1e-4} 13 | y0 = [c0.get(symb.name, 0) for symb in cvode_sys.y] 14 | yout, info = cvode_sys.integrate(tout, y0) 15 | assert 10 < info['num_steps'] < 5000 16 | assert info['status'] == 0 17 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [easy_install] 2 | zip_ok = 0 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | name = 'scipy2017codegen' 3 | setup( 4 | name=name, 5 | version='2017.1', 6 | description='Code for the codgen tutorial at SciPy 2017.', 7 | packages=[name], 8 | include_package_data=True, 9 | author='SymPy development team' 10 | ) 11 | -------------------------------------------------------------------------------- /test_installation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from sys import exit 4 | from subprocess import check_output 5 | 6 | error = False 7 | 8 | try: 9 | from pkg_resources import parse_version 10 | except ImportError: 11 | print("Setuptools must be installed for the tutorial.") 12 | error = True 13 | 14 | try: 15 | import numpy 16 | except ImportError: 17 | print("NumPy is required for the tutorial") 18 | error = True 19 | 20 | try: 21 | import Cython 22 | except ImportError: 23 | print("Cython is required for the tutorial") 24 | error = True 25 | 26 | try: 27 | import sympy 28 | except ImportError: 29 | print("SymPy must be installed for the tutorial") 30 | error = True 31 | else: 32 | if parse_version(sympy.__version__) < parse_version('1.1'): 33 | print("SymPy >= 1.1 is required for the tutorial.") 34 | from sympy.utilities.autowrap import autowrap 35 | from sympy.abc import x 36 | from sympy import sin 37 | try: 38 | f = autowrap(sin(x), backend='cython') 39 | assert f(0) == 0 40 | except Exception as e: 41 | msg = ("sympy.utilities.autowrap.autowrap does not work, your Cython " 42 | "installation or compiler may be missing") 43 | print(msg) 44 | print(e) 45 | error = True 46 | 47 | try: 48 | import scipy 49 | except ImportError: 50 | print("Scipy is required for the tutorial") 51 | error = True 52 | 53 | try: 54 | s = check_output('conda --version', shell=True) 55 | except FileNotFoundError: 56 | print("conda is needed (either anaconda or miniconda from https://www.continuum.io/downloads)") 57 | print("(try rerunning this script under conda if you are using for system's python distribution)") 58 | error = True 59 | else: 60 | installed_version = s.decode('utf-8').strip().split(' ')[-1] 61 | if parse_version(installed_version) < parse_version('4.1.0'): 62 | msg = ("You have conda {} installed. The tutorial requires " 63 | "conda >= 4.1.0. Please update conda ($ conda update conda).") 64 | print(msg.format(installed_version)) 65 | error = True 66 | 67 | try: 68 | import matplotlib 69 | except ImportError: 70 | print("matplotlib is required for the tutorial") 71 | error = True 72 | 73 | try: 74 | import notebook 75 | except ImportError: 76 | print("notebook (jupyter notebook) is required for the tutorial") 77 | error = True 78 | 79 | exit(error) 80 | -------------------------------------------------------------------------------- /tutorial.md: -------------------------------------------------------------------------------- 1 | # Title 2 | 3 | Automatic Code Generation with SymPy 4 | 5 | # Tutorial Topic 6 | 7 | Computational Science and Numerical Techniques 8 | 9 | # Student’s Python Knowledge Level 10 | 11 | Intermediate 12 | 13 | # Please provide a detailed abstract of your tutorial: 14 | 15 | This tutorial will introduce code generation concepts using the SymPy library. 16 | SymPy is a pure Python library for symbolic mathematics. Code generation refers 17 | to the act of converting a SymPy symbolic expression into equivalent code in 18 | some language. This allows one to use SymPy to symbolically model a problem, 19 | and generate fast numerical code for specific platforms that executes that 20 | model. This is a powerful tool that is useful to scientists in many domains. 21 | Code generation allows users to speed up existing code, to deal only with the 22 | high level mathematics of a problem, avoids mathematical errors and typos, 23 | makes it possible to deal with expressions that would otherwise be too large to 24 | write by hand, and opens possibilities to perform automatic mathematical 25 | optimizations of expressions. 26 | 27 | SymPy supports generating code for C, C++, Fortran, Matlab/Octave, Python, 28 | Cython, Julia, Javascript, LLVM, Rust, Haskell, Mathematica, Tensorflow, and 29 | Theano, and can easily be extended to other languages. SymPy’s code generation 30 | is used by libraries such as PyDy, pyodesys, sympybotics, pycalphad, and many 31 | other programs. 32 | 33 | ## Learning objectives 34 | 35 | Attendees will be able to: 36 | - write SymPy expressions describing mathematical functions and identify the 37 | function arguments and outputs. 38 | - use the SymPy code printers to transform SymPy expressions representing 39 | common domain specific functions into multiple output languages. 40 | - use the SymPy code generation routines to output compilable C code and use 41 | Cython to access these functions in Python. 42 | - generate custom vectorized functions with the three SymPy functions: 43 | lambdify, ufuncify, and autowrap. 44 | - create both custom code printers that make use of specialized C libraries and 45 | common subexpression elimination (CSE). 46 | - subclass the core SymPy printers and create a printer for a custom language. 47 | 48 | ## Outline 49 | 50 | Intro to SymPy Expressions [30 minutes] 51 | 52 | - Description: Writing common domain specific mathematical expressions with 53 | SymPy. 54 | - Motivating Examples: Long expressions, Matrix operations, and Loop Fusion 55 | from classical mechanics, chemical kinetics, nuclear dynamics, and materials 56 | science. 57 | 58 | Code Printers [30 minutes] 59 | 60 | - Description: Printing expressions in multiple languages (C, Fortran, Rust, 61 | Julia, Octave, Javascript, etc) 62 | - Motivating Example: 2D interactive plot in a Jupyter notebook by javascript 63 | injection 64 | 65 | The Easy Way: High Level Generation (lambdify, ufuncify) [30 minutes] 66 | 67 | - Description: Generate loop fused NumPy ufuncs and compare to automatically 68 | generated NumPy code. Show how you can extend lambdify with custom Python 69 | functions. 70 | - Motivating Example: Generate a Jacobian function for a chemical kinetic 71 | problem. 72 | 73 | The Harder Way: Code generation and compilation [1 hour] 74 | 75 | - Description: Write a tight low level loop with an indexed type and a long 76 | expressions with knowing C. 77 | - Motivating Example: Evaluate the chemical kinetic Jacobian in a loop. 78 | 79 | Cythonizing Your Code (manually and autowrap) [30 minutes] 80 | 81 | - Description: Generate C code to evaluate the gradient and Jacobian of an 82 | ordinary differential equation and wrap it for use with SciPy’s odeint 83 | function. 84 | - Motivating Example: Chaotic triple pendulum example from classical mechanics. 85 | 86 | Extending SymPy’s classes [1 hour] 87 | 88 | - Description: Show how to use external C libraries and optimize your code with 89 | common sub-expression elimination. 90 | - Motivating Example: Speed up triple pendulum execution with CSE and GSL 91 | integrator. 92 | 93 | The attendees will come away with a powerful set of tools that will allow them 94 | to develop high performance numerical code using Python that compliments NumPy 95 | and SciPy. This tutorial will be ideal for users of the SciPy Stack that would 96 | like to increase the performance of their Python code, get into some of the 97 | depths of how low-level languages can interact and be used from Python, or to 98 | learn a new technique for expressing mathematical models in Python. 99 | 100 | # Give us a short bio, including relevant teaching experience. If you have recorded talks or tutorials available online, please include links. 101 | 102 | ## Jason K. Moore 103 | 104 | Jason is a professor at UC Davis in the Mechanical and Aerospace Engineering 105 | Department. He is also core developer with both the PyDy and SymPy projects. He 106 | utilizes both packages to run optimal control algorithms for biomechanical 107 | systems, in particular data driven powered prosthetic designs and human control 108 | identification. He is a strong proponent for Open Science and just bought his 109 | first new skateboard in over 10 years. Jason has given talks and tutorials at 110 | numerous conferences, is a Software Carpentry instructor, and gives 60 to 80 111 | lectures a year while teaching. Some examples are: 112 | 113 | - Scipy 2013 Talk: https://youtu.be/Jtt9hexk93o 114 | - SciPy 2013 Talk: https://youtu.be/H9AK65ZY-Vw 115 | - PyCon 2014 Tutorial: https://youtu.be/IoMR-ESzqw8 116 | - SciPy 2014 Tutorial: https://youtu.be/lWbeuDwYVto 117 | - SciPy 2015 Tutorial: https://youtu.be/mdo2NYtA-xY 118 | - SciPy 2015 Talk: https://youtu.be/ZJiYs2HuQy8 119 | - SciPy 2016 Tutorial: https://youtu.be/r4piIKV4sDw 120 | 121 | ## Aaron Meurer 122 | 123 | Aaron is the lead developer of SymPy. He works in the ERGS group at the 124 | University of South Carolina. He has co-taught tutorials on SymPy as previous 125 | SciPy conferences: 126 | 127 | Tutorials: 128 | 129 | - SciPy 2011: “SymPy tutorial” https://conference.scipy.org/scipy2011/tutorials.php#mateusz 130 | - SciPy 2013: “SymPy tutorial” https://conference.scipy.org/scipy2013/tutorial_detail.php?id=101 131 | - SciPy 2014: “SymPy tutorial” https://conference.scipy.org/scipy2014/schedule/presentation/1661/ 132 | - SciPy 2016: “SymPy tutorial” https://scipy2016.scipy.org/ehome/146062/332960/ 133 | 134 | Talks: 135 | 136 | - SciPy 2014 “Conda: A cross platform package manager for any binary distribution”, https://www.youtube.com/watch?v=UaIvrDWrIWM 137 | - SciPy 2016 “SymPy Code Generation”, https://www.youtube.com/watch?v=nmI-cDAUjdE 138 | 139 | ## Please provide detailed setup instructions for all necessary software. 140 | 141 | - Install Anaconda or Miniconda 142 | - From the terminal or navigator: `conda install numpy scipy cython sympy jupyter` 143 | 144 | # What skills are needed to successfully participate in your tutorial (select all required using CTRL or CMD for multiselect) 145 | 146 | Numpy basics, Numpy advanced, SciPy 147 | 148 | # If other topics are a prerequisite, please explain further. 149 | 150 | This tutorial assumes a basic knowledge of the SymPy library (note: if a basic 151 | SymPy tutorial is submitted and accepted, it should be a prerequisite of this 152 | tutorial). We will be working with a number of different languages. Familiarity 153 | with the basics of IPython, Jupyter, NumPy, SciPy is also required. Familiarity 154 | with Cython, C, and Javascript will be helpful, but not required. 155 | 156 | # Please provide a short Python program that can be used to verify whether a participant’s environment is set up correctly. 157 | 158 | ```python 159 | from sys import exit 160 | 161 | try: 162 | import sympy 163 | except ImportError: 164 | exit("SymPy must be installed for the tutorial") 165 | 166 | if sympy.__version__ != '1.1': 167 | exit("SymPy 1.1 is required for the tutorial") 168 | 169 | try: 170 | import numpy 171 | except ImportError: 172 | exit("NumPy is required for the tutorial") 173 | 174 | try: 175 | import Cython 176 | except ImportError: 177 | exit("Cython is required for the tutorial") 178 | 179 | try: 180 | import scipy 181 | except ImportError: 182 | exit("scipy is required for the tutorial") 183 | 184 | from sympy.utilities.autowrap import ufuncify 185 | from sympy.abc import x 186 | from sympy import sin 187 | 188 | try: 189 | f = ufuncify(x, sin(x)) 190 | assert f(0) == 0 191 | except: 192 | print("sympy.utilities.autowrap.ufuncify does not work") 193 | raise 194 | ``` 195 | 196 | # All tutorials will be reviewed for completeness a week prior to the conference. Do you foresee any problems meeting that deadline? 197 | 198 | As with many academics, we’d like to be able to work up until the day of on our 199 | tutorial. This is a new tutorial and will take significant preparation time. We 200 | believe we have sufficient experience in preparation of teaching materials to 201 | guarantee everything will be in order by show time. 202 | 203 | Also, we were not quite sure how to categorize this talk in terms of the 204 | "Student's Python Knowledge Level". We have selected intermediate because we 205 | will not be using very advanced Python code, but due to the nature of the 206 | tutorial working with multiple languages, it may be considered advanced. Please 207 | advise us on the appropriate selection. 208 | 209 | # Will you be available to help with setup instructions to your pre-tutorial email list in the week prior to your tutorial? 210 | 211 | Yes. 212 | 213 | # Notes 214 | 215 | - Printing to different languages. Show each language. 216 | - How to create compilable code in C and Fortran 217 | - Lambdify, ufuncify, autowrap 218 | - Vector stuff, Indexed, MatrixExpressions 219 | - Boilerplate code to inject the code output into 220 | - Manual cython code generation 221 | - Lots of examples. 222 | - CSE 223 | - Extending the code generation. 224 | - Custom languages/libraries. 225 | - Custom operations. 226 | --------------------------------------------------------------------------------