├── .github └── workflows │ └── main.yml ├── .gitignore ├── README.md ├── SETUP.md ├── content ├── _config.yml ├── _static │ └── myfile.css ├── _toc.yml ├── advection_euler │ ├── advection │ │ ├── advection-partI.ipynb │ │ ├── advection-partII.ipynb │ │ ├── advection-partIII.ipynb │ │ ├── advection-states.png │ │ ├── advection.md │ │ ├── array-labels.png │ │ ├── fd_ghost.png │ │ ├── fd_ghost_lohi.png │ │ ├── fd_grid.png │ │ ├── fd_grid_basic.png │ │ ├── fv_ghost.png │ │ ├── fv_grid.png │ │ ├── generalgrid.png │ │ ├── grids.png │ │ ├── riemann-adv-mol.png │ │ ├── riemann-mol.png │ │ └── riemann.png │ ├── burgers │ │ ├── advection-characteristics.png │ │ ├── burgers-characteristics-rare.png │ │ ├── burgers-characteristics-shock.png │ │ ├── burgers-methods.ipynb │ │ ├── burgers.md │ │ └── exercises.ipynb │ ├── euler │ │ ├── euler-eigen.ipynb │ │ ├── euler-riemann-sampling.ipynb │ │ ├── euler-riemann.ipynb │ │ ├── euler-solver.ipynb │ │ ├── euler.md │ │ ├── fv_grid.png │ │ ├── rarefaction_span.png │ │ ├── riemann-mol.png │ │ ├── riemann-sod.png │ │ ├── riemann-waves-jump.png │ │ ├── riemann-waves.png │ │ ├── riemann_exact.py │ │ ├── riemann_state.png │ │ ├── simplegrid_gc.png │ │ └── sod-exact.out │ └── projects │ │ ├── 2dgrid.png │ │ ├── fvrestrict.png │ │ ├── piecewise-parabolic.png │ │ ├── projects.md │ │ └── spherical_sedov.txt ├── basics │ ├── ODEs │ │ ├── ODEs-partI.ipynb │ │ ├── ODEs-partII.ipynb │ │ ├── ODEs.md │ │ ├── boundary-value-problems.ipynb │ │ ├── ellipse_initial_cond.png │ │ ├── exercises.ipynb │ │ ├── rk2_Euler.png │ │ ├── rk2_final.png │ │ ├── rk2_halfEuler.png │ │ ├── rk4_final.png │ │ ├── rk4_k1.png │ │ ├── rk4_k2.png │ │ ├── rk4_k3.png │ │ └── rk4_k4.png │ ├── diff-int │ │ ├── compound.png │ │ ├── compound2.png │ │ ├── derivs.png │ │ ├── diff-int.md │ │ ├── differentiation.ipynb │ │ ├── exercises.ipynb │ │ ├── fd_grid.png │ │ ├── integration.ipynb │ │ ├── rectangle_N1.png │ │ ├── rectangle_N6.png │ │ ├── simpsons_N1.png │ │ ├── simpsons_N6.png │ │ ├── trapezoid_N1.png │ │ └── trapezoid_N6.png │ ├── floating-point │ │ ├── floating-point.md │ │ └── numerical_error.ipynb │ ├── linear-algebra │ │ ├── la-basics.ipynb │ │ └── la-overview.md │ └── roots │ │ ├── newton_00.png │ │ ├── newton_01.png │ │ ├── newton_02.png │ │ └── root-finding.ipynb ├── elliptic_multigrid │ ├── intro │ │ └── elliptic-problems.ipynb │ ├── multigrid │ │ ├── fvrestrict.png │ │ ├── grid.py │ │ ├── mg_test.py │ │ ├── mgtower.png │ │ ├── multigrid-restrict-prolong.ipynb │ │ ├── multigrid-two-grid.ipynb │ │ ├── multigrid-vcycles.ipynb │ │ ├── multigrid.md │ │ ├── multigrid.py │ │ └── vcycle.png │ └── relaxation │ │ ├── ccfd_ghost.png │ │ ├── ccfd_grid_bnd.png │ │ ├── elliptic-modes.ipynb │ │ ├── elliptic-relaxation.ipynb │ │ ├── elliptic-residual.ipynb │ │ ├── fd_grid_bnd.png │ │ ├── relaxation.md │ │ └── smoothing.py ├── intro.md ├── reaction_networks │ ├── ODEs-implicit-nonlinear-systems.ipynb │ ├── ODEs-implicit-systems.ipynb │ ├── ODEs-implicit.ipynb │ ├── integration-example.ipynb │ ├── reactions.md │ └── stiff-ODEs.md ├── references.bib ├── rt_small.png └── stars │ ├── lane-emden.ipynb │ ├── le_extrap.png │ └── stars.md ├── figures ├── 2dgrid.py ├── advection_states.py ├── array_labels.py ├── ccfd_ghost.py ├── derivatives.py ├── fd.py ├── fd_ghost.py ├── fd_ghost_lohi.py ├── fd_lohi.py ├── fd_with_function.py ├── fv_ghost.py ├── fvrestrict.py ├── integrals.py ├── mgtower.py ├── riemann.py ├── rk2_plot.py ├── rk4_plot.py ├── roots_plot.py └── vcycle.py ├── projects └── sedov │ ├── riemann_approximate.py │ ├── riemann_exact.py │ ├── sedov.py │ └── sod-exact.out ├── requirements.txt └── style.txt /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Publish JupyterBook to GitHub Pages 2 | 3 | on: 4 | push: # trigger build only when push to main 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Set up Python 15 | uses: actions/setup-python@v4 16 | with: 17 | python-version: "3.10" 18 | - name: Install Python dependencies 19 | run: | 20 | sudo apt-get install python3-pip 21 | pip install -r requirements.txt 22 | pip install ghp-import 23 | PATH="${PATH}:${HOME}/.local/bin" 24 | 25 | - name: Build book HTML 26 | run: | 27 | jupyter-book build ./content 28 | 29 | - name: Push _build/html to gh-pages 30 | run: | 31 | sudo chown -R $(whoami):$(whoami) . 32 | git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com" 33 | git config --global user.name "$GITHUB_ACTOR" 34 | git remote set-url origin "https://$GITHUB_ACTOR:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY" 35 | 36 | ghp-import ./content/_build/html -f -p -n 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | *~ 3 | \#* 4 | 5 | .ipynb_checkpoints 6 | 7 | __pycache__ 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License: CC BY-NC-SA 4.0](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by-nc-sa/4.0/) 2 | [![Publish JupyterBook to GitHub Pages](https://github.com/zingale/comp_astro_tutorial/actions/workflows/main.yml/badge.svg)](https://github.com/zingale/comp_astro_tutorial/actions/workflows/main.yml) 3 | 4 | # Computational Astrophysics Tutorial 5 | 6 | This is a set of notebooks for an undergraduate computational astrophysics tutorial, Spring 2021. 7 | 8 | See the main JupyterBook to browse: https://zingale.github.io/comp_astro_tutorial/intro.html 9 | -------------------------------------------------------------------------------- /SETUP.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # create the initial jupyter-book 4 | 5 | ``` 6 | jupyter-book create comp_astro_tutorial 7 | ``` 8 | 9 | and put it under github contol (on main) 10 | 11 | # create the empty `gh-pages` branch: 12 | 13 | following: https://gist.github.com/ramnathv/2227408 14 | 15 | ``` 16 | cd /path/to/repo-name 17 | git symbolic-ref HEAD refs/heads/gh-pages 18 | rm .git/index 19 | git clean -fdx 20 | echo "My GitHub Page" > index.html 21 | git add . 22 | git commit -a -m "First pages commit" 23 | git push origin gh-pages 24 | ``` 25 | 26 | # Create the build gitub action. 27 | 28 | I based it off of: 29 | https://github.com/choldgraf/deploy_configurations/blob/master/.github/workflows/main.yml 30 | 31 | 32 | -------------------------------------------------------------------------------- /content/_config.yml: -------------------------------------------------------------------------------- 1 | # Book settings 2 | # Learn more at https://jupyterbook.org/customize/config.html 3 | 4 | title: Tutorial on Computational Astrophysics 5 | author: Michael Zingale 6 | logo: rt_small.png 7 | copyright: "2021" 8 | 9 | # Force re-execution of notebooks on each build. 10 | # See https://jupyterbook.org/content/execute.html 11 | execute: 12 | execute_notebooks: force 13 | 14 | # Define the name of the latex output file for PDF builds 15 | latex: 16 | latex_documents: 17 | targetname: book.tex 18 | 19 | # Information about where the book exists on the web 20 | repository: 21 | url: https://github.com/zingale/comp_astro_tutorial # Online location of your book 22 | path_to_book: content # Optional path to your book, relative to the repository root 23 | branch: main # Which branch of the repository should be used when creating links (optional) 24 | 25 | # Add GitHub buttons to your book 26 | # See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository 27 | html: 28 | use_issues_button: true 29 | use_repository_button: true 30 | extra_footer: | 31 |

32 | © 2021-2022; CC-BY-NC-SA 4.0 33 |

34 | 35 | sphinx: 36 | config: 37 | html_show_copyright: false 38 | 39 | launch_buttons: 40 | binderhub_url: "https://mybinder.org" 41 | colab_url: "https://colab.research.google.com" 42 | 43 | 44 | parse: 45 | extensions: 46 | - myst_parser 47 | - sphinx_design 48 | 49 | myst_enable_extensions: 50 | # don't forget to list any other extensions you want enabled, 51 | # including those that are enabled by default! 52 | - amsmath 53 | - dollarmath 54 | - linkify 55 | - colon_fence 56 | -------------------------------------------------------------------------------- /content/_static/myfile.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap'); 2 | @import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap'); 3 | 4 | body { 5 | font-family: "Open Sans", sans-serif; 6 | } 7 | 8 | .heading-style, h1, h2, h3, h4, h5, h6 { 9 | font-family: "Open Sans", sans-serif; 10 | } 11 | -------------------------------------------------------------------------------- /content/_toc.yml: -------------------------------------------------------------------------------- 1 | format: jb-book 2 | root: intro 3 | parts: 4 | - caption: Basics 5 | chapters: 6 | - file: basics/floating-point/floating-point 7 | sections: 8 | - file: basics/floating-point/numerical_error 9 | - file: basics/diff-int/diff-int 10 | sections: 11 | - file: basics/diff-int/differentiation 12 | - file: basics/diff-int/integration 13 | - file: basics/diff-int/exercises 14 | - file: basics/roots/root-finding 15 | - file: basics/ODEs/ODEs 16 | sections: 17 | - file: basics/ODEs/ODEs-partI 18 | - file: basics/ODEs/ODEs-partII 19 | - file: basics/ODEs/boundary-value-problems 20 | - file: basics/ODEs/exercises 21 | - file: basics/linear-algebra/la-overview 22 | sections: 23 | - file: basics/linear-algebra/la-basics 24 | 25 | - caption: Advection and Hydrodynamics 26 | chapters: 27 | - file: advection_euler/advection/advection 28 | sections: 29 | - file: advection_euler/advection/advection-partI 30 | - file: advection_euler/advection/advection-partII 31 | - file: advection_euler/advection/advection-partIII 32 | - file: advection_euler/burgers/burgers 33 | sections: 34 | - file: advection_euler/burgers/burgers-methods 35 | - file: advection_euler/burgers/exercises 36 | - file: advection_euler/euler/euler 37 | sections: 38 | - file: advection_euler/euler/euler-eigen 39 | - file: advection_euler/euler/euler-riemann 40 | - file: advection_euler/euler/euler-riemann-sampling 41 | - file: advection_euler/euler/euler-solver 42 | - file: advection_euler/projects/projects.md 43 | 44 | - caption: Elliptic Problems and Multigrid 45 | chapters: 46 | - file: elliptic_multigrid/intro/elliptic-problems 47 | - file: elliptic_multigrid/relaxation/relaxation 48 | sections: 49 | - file: elliptic_multigrid/relaxation/elliptic-relaxation 50 | - file: elliptic_multigrid/relaxation/elliptic-residual 51 | - file: elliptic_multigrid/relaxation/elliptic-modes 52 | - file: elliptic_multigrid/multigrid/multigrid 53 | sections: 54 | - file: elliptic_multigrid/multigrid/multigrid-restrict-prolong 55 | - file: elliptic_multigrid/multigrid/multigrid-two-grid 56 | - file: elliptic_multigrid/multigrid/multigrid-vcycles 57 | 58 | - caption: Stars 59 | chapters: 60 | - file: stars/stars 61 | - file: stars/lane-emden 62 | 63 | - caption: Integrating Stiff Reaction Networks 64 | chapters: 65 | - file: reaction_networks/reactions 66 | - file: reaction_networks/stiff-ODEs 67 | sections: 68 | - file: reaction_networks/ODEs-implicit 69 | - file: reaction_networks/ODEs-implicit-systems 70 | - file: reaction_networks/ODEs-implicit-nonlinear-systems 71 | - file: reaction_networks/integration-example 72 | 73 | 74 | -------------------------------------------------------------------------------- /content/advection_euler/advection/advection-partII.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "import matplotlib.pyplot as plt" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "# Finite-Volumes" 18 | ] 19 | }, 20 | { 21 | "cell_type": "markdown", 22 | "metadata": {}, 23 | "source": [ 24 | "Ultimately we are interested in conservation laws, so we want a method that respects conservation. We'll look at this with advection first.\n", 25 | "\n", 26 | "Here we see 3 different types of grids:\n", 27 | "\n", 28 | "![discretization types](grids.png)\n", 29 | "\n", 30 | "The first 2 are *finite-difference* grids—the data is represented at a specific point in the domain. The two differ in that the second case is a cell-centered finite-difference grid. In this case, there is not a point on the physical boundaries, while with the first example (what we were using previously), there is a data point on each boundary. For the cell-centered case, we typically talk about dividing the data into cells or zones and the data living at the center of the zone.\n", 31 | "\n", 32 | "The last example is a *finite-volume* grid. Here, we don't think of the data living at a specific point, but instead we keep track of the total amount of a quantity (or its average) in a volume. This is represented above as the shaded region inside our zone." 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "## Cell-averages\n", 40 | "\n", 41 | "Imagine we have a function $f(x)$ that we wish to represent on our grid, using a finite-volume discretization.\n", 42 | "\n", 43 | "![finite-volume grid](fv_grid.png)\n", 44 | "\n", 45 | "We can define the average of $f(x)$ in a zone that goes from $x_{i-1/2}$ to $x_{i+1/2}$ as:\n", 46 | "\n", 47 | "$$\\langle f\\rangle_i = \\frac{1}{\\Delta x} \\int_{x_{i-1/2}}^{x_{i+1/2}}f(x) dx$$\n", 48 | "\n", 49 | "We use the angle brackets to indicate that this is an average, and use an integer index, $i$, to indicate that this data lives in zone $i$." 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "metadata": {}, 55 | "source": [ 56 | "Now consider our linear advection equation,\n", 57 | "\n", 58 | "$$\\frac{\\partial a}{\\partial t} = -u\\frac{\\partial a}{\\partial x}$$\n", 59 | "\n", 60 | "written in conservative form:\n", 61 | "\n", 62 | "$$\\frac{\\partial a}{\\partial t} = -\\frac{\\partial F(a)}{\\partial x}$$\n", 63 | "\n", 64 | "where $F(a) = ua$ is the flux.\n", 65 | "\n", 66 | "Let's average (integrate) this equation over a zone (from $[x_{i-1/2}, x_{i+1/2}]$):\n", 67 | "\n", 68 | "$$\\frac{1}{\\Delta x} \\int_{x-1/2}^{x+1/2} \\frac{\\partial a}{\\partial t} dx = \n", 69 | " -\\frac{1}{\\Delta x} \\int_{x-1/2}^{x+1/2} \\frac{\\partial F}{\\partial x} dx$$\n", 70 | " \n", 71 | "we can recognize the left is the time-derivative of the average of $a$ and the right, via the divergence theorem is just the flux different through the boundary of the zone:\n", 72 | "\n", 73 | "$$\\frac{\\partial}{\\partial t} \\langle a\\rangle_i = -\\frac{1}{\\Delta x}\n", 74 | " \\left [ F_{i+1/2} - F_{i-1/2} \\right ]$$" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "metadata": {}, 80 | "source": [ 81 | "## Time update" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "metadata": {}, 87 | "source": [ 88 | "Let's start with a first-order update in time. We can use the Euler method we already saw,\n", 89 | "\n", 90 | "$$\\frac{\\partial \\langle a \\rangle}{\\partial t} \\approx \\frac{\\langle a\\rangle^{n+1} - \\langle a\\rangle^{n}}{\\Delta t}$$\n", 91 | "\n", 92 | "in which case our update appears as:\n", 93 | "\n", 94 | "$$\\langle a\\rangle_i^{n+1} = \\langle a\\rangle_i^{n} - \\frac{\\Delta t}{\\Delta x} (F_{i+1/2}^n - F_{i-1/2}^n)$$\n", 95 | "\n", 96 | "We expect this to be first order accurate in time. Notice that the fluxes are evaluated at the old time." 97 | ] 98 | }, 99 | { 100 | "cell_type": "markdown", 101 | "metadata": {}, 102 | "source": [ 103 | "## Reconstruction and the Riemann problem" 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "We need the value of the flux on the interface, $F_{i+1/2}$, we find this using the analytic expression\n", 111 | "for the flux, $F(a)$, as:\n", 112 | "\n", 113 | "$$[F(a)]_{i+1/2} = F(a_{i+1/2})$$\n", 114 | "\n", 115 | "So now we need to find the interface state, $a_{i+1/2}$. \n", 116 | "\n", 117 | "Getting the value of $a$ on the interface from the average, $\\langle a \\rangle$ is called *reconstruction*. It means we need to infer how $a$ actually varies throughout the cell just from the information we have about its average value. There are a variety of methods we can use (some of which we will explore later). For now, we will do the simplest, and assume that $a(x)$ is constant in each cell:\n", 118 | "\n", 119 | "$$a_{i+1/2,L} = a_i^n$$\n", 120 | "$$a_{i+1/2,R} = a_{i+1}^n$$\n", 121 | "\n", 122 | "Notice that we actually have 2 values for the interface state when we do this, one coming from each side of the interface—we label these as the left and right states:\n", 123 | "\n", 124 | "![left and right states for advection](riemann-adv-mol.png)" 125 | ] 126 | }, 127 | { 128 | "cell_type": "markdown", 129 | "metadata": {}, 130 | "source": [ 131 | "Now we need to resolve this degeneracy—which of the two states (or what combination of them) is the correct interface state? This is where the physics of the problem comes into play. This is called the *Riemann problem*.\n", 132 | "\n", 133 | "For advection it is easy. We know that for $u > 0$ that $a(x)$ moves from left to right, so the correct state on the interface is the left state—this is upwinding.\n", 134 | "\n", 135 | "$$\n", 136 | "a_{i+1/2} = \\mathcal{R}(a_{i+1/2,L}, a_{i+1/2,R}) = \n", 137 | "\\left \\{ \n", 138 | "\\begin{array}{c}\n", 139 | " a_{i+1/2,L} \\quad u > 0 \\\\\n", 140 | " a_{i+1/2,R} \\quad u < 0 \n", 141 | "\\end{array}\n", 142 | "\\right .\n", 143 | "$$\n", 144 | "\n", 145 | "where we indicate the Riemann problem as $\\mathcal{R}(a_{i+1/2,L},a_{i+1/2,R})$." 146 | ] 147 | }, 148 | { 149 | "cell_type": "markdown", 150 | "metadata": {}, 151 | "source": [ 152 | "For the advection equation, with $u > 0$, our interface states are \n", 153 | "\n", 154 | "$$F_{i+1/2} = F(a_{i+1/2}) = u a_{i+1/2} = u \\langle a \\rangle_i$$\n", 155 | "\n", 156 | "$$F_{i-1/2} = F(a_{i-1/2}) = u a_{i-1/2} = u \\langle a \\rangle_{i-1}$$\n", 157 | "\n", 158 | "Inserting these into our difference equation, we have:\n", 159 | "\n", 160 | "$$\\langle a \\rangle_i^{n+1} = \\langle a \\rangle_i^n - \\frac{u \\Delta t}{\\Delta x} (\\langle a \\rangle_i^n - \\langle a \\rangle_{i-1}^n)$$\n", 161 | "\n", 162 | "This is precisely the upwind finite-difference scheme we saw earlier." 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": null, 168 | "metadata": {}, 169 | "outputs": [], 170 | "source": [] 171 | } 172 | ], 173 | "metadata": { 174 | "kernelspec": { 175 | "display_name": "Python 3", 176 | "language": "python", 177 | "name": "python3" 178 | }, 179 | "language_info": { 180 | "codemirror_mode": { 181 | "name": "ipython", 182 | "version": 3 183 | }, 184 | "file_extension": ".py", 185 | "mimetype": "text/x-python", 186 | "name": "python", 187 | "nbconvert_exporter": "python", 188 | "pygments_lexer": "ipython3", 189 | "version": "3.8.7" 190 | } 191 | }, 192 | "nbformat": 4, 193 | "nbformat_minor": 4 194 | } 195 | -------------------------------------------------------------------------------- /content/advection_euler/advection/advection-states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/advection/advection-states.png -------------------------------------------------------------------------------- /content/advection_euler/advection/advection.md: -------------------------------------------------------------------------------- 1 | Linear Advection Equation 2 | ========================= 3 | 4 | The linear advection equation is a model equation for understanding 5 | the core algorithms we will use for hydrodynamics. 6 | -------------------------------------------------------------------------------- /content/advection_euler/advection/array-labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/advection/array-labels.png -------------------------------------------------------------------------------- /content/advection_euler/advection/fd_ghost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/advection/fd_ghost.png -------------------------------------------------------------------------------- /content/advection_euler/advection/fd_ghost_lohi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/advection/fd_ghost_lohi.png -------------------------------------------------------------------------------- /content/advection_euler/advection/fd_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/advection/fd_grid.png -------------------------------------------------------------------------------- /content/advection_euler/advection/fd_grid_basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/advection/fd_grid_basic.png -------------------------------------------------------------------------------- /content/advection_euler/advection/fv_ghost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/advection/fv_ghost.png -------------------------------------------------------------------------------- /content/advection_euler/advection/fv_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/advection/fv_grid.png -------------------------------------------------------------------------------- /content/advection_euler/advection/generalgrid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/advection/generalgrid.png -------------------------------------------------------------------------------- /content/advection_euler/advection/grids.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/advection/grids.png -------------------------------------------------------------------------------- /content/advection_euler/advection/riemann-adv-mol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/advection/riemann-adv-mol.png -------------------------------------------------------------------------------- /content/advection_euler/advection/riemann-mol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/advection/riemann-mol.png -------------------------------------------------------------------------------- /content/advection_euler/advection/riemann.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/advection/riemann.png -------------------------------------------------------------------------------- /content/advection_euler/burgers/advection-characteristics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/burgers/advection-characteristics.png -------------------------------------------------------------------------------- /content/advection_euler/burgers/burgers-characteristics-rare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/burgers/burgers-characteristics-rare.png -------------------------------------------------------------------------------- /content/advection_euler/burgers/burgers-characteristics-shock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/burgers/burgers-characteristics-shock.png -------------------------------------------------------------------------------- /content/advection_euler/burgers/burgers.md: -------------------------------------------------------------------------------- 1 | Burgers' Equation 2 | ================= 3 | 4 | The inviscid Burgers' equation provides a great introduction to shocks and rarefactions. 5 | -------------------------------------------------------------------------------- /content/advection_euler/burgers/exercises.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Exercises" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "

\n", 15 | "\n", 16 | "**1. Conservative vs. non-conservative form:**\n", 17 | " \n", 18 | "Let's consider a finite difference approximation to Burgers' equation. For simplicity, let's assume that $u > 0$ always. We can discretize it to first-order accuracy using upwinding in conservation form:\n", 19 | "\n", 20 | "$$\\frac{u_i^{n+1} - u_i^n}{\\Delta t} = \\frac{1}{\\Delta x} \\left (\\frac{1}{2} (u_i^n )^2 - \\frac{1}{2} (u_{i-1}^n)^2 \\right )$$\n", 21 | " \n", 22 | "or in the original differential form as:\n", 23 | " \n", 24 | "$$\\frac{u_i^{n+1} - u_i^n}{\\Delta t} = u_i^n \\frac{u_i^n - u_{i-1}^n}{\\Delta x}$$\n", 25 | " \n", 26 | "Now consider the follow set of initial conditions:\n", 27 | " \n", 28 | "$$u(x, t=0) = \\left \\{ \\begin{array}{c} 2 & \\mbox{if}~ x < 1/2 \\\\\n", 29 | " 1 & \\mbox{if}~ x \\ge 1/2 \\end{array} \\right .$$\n", 30 | " \n", 31 | "This will drive a rightward moving shock with a speed $S = 3/2$.\n", 32 | " \n", 33 | "Code up both of the above discretizations and measure the speed of the shock---which method gets it correct?\n", 34 | "
" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [] 43 | } 44 | ], 45 | "metadata": { 46 | "kernelspec": { 47 | "display_name": "Python 3", 48 | "language": "python", 49 | "name": "python3" 50 | }, 51 | "language_info": { 52 | "codemirror_mode": { 53 | "name": "ipython", 54 | "version": 3 55 | }, 56 | "file_extension": ".py", 57 | "mimetype": "text/x-python", 58 | "name": "python", 59 | "nbconvert_exporter": "python", 60 | "pygments_lexer": "ipython3", 61 | "version": "3.8.7" 62 | } 63 | }, 64 | "nbformat": 4, 65 | "nbformat_minor": 4 66 | } 67 | -------------------------------------------------------------------------------- /content/advection_euler/euler/euler.md: -------------------------------------------------------------------------------- 1 | # Euler Equations 2 | 3 | The Euler equations describe compressible flow in the absence of 4 | dissipative effects (like viscosity). In one-dimension, they appear 5 | as: 6 | 7 | $$ 8 | \begin{align*} 9 | \frac{\partial \rho}{\partial t} + \frac{\partial (\rho u)}{\partial x} &= 0 \\ 10 | \frac{\partial (\rho u)}{\partial t} + \frac{\partial}{\partial x} (\rho u^2 + p) &= 0 \\ 11 | \frac{\partial (\rho E)}{\partial t} + \frac{\partial}{\partial x} \left [ (\rho E + p) u \right ] &= 0 12 | \end{align*} 13 | $$ 14 | 15 | Here, $E$ is the specific total energy which is related to the specific internal energy as: 16 | 17 | $$ 18 | \rho E = \rho e + \frac{1}{2} \rho u^2 19 | $$ 20 | 21 | and the system is closed by an equation of state: 22 | 23 | $$p = p(\rho, e)$$ 24 | 25 | A common equation of state is a *gamma-law EOS*: 26 | 27 | $$ p = \rho e (\gamma - 1)$$ 28 | 29 | where $\gamma$ is a constant. For an ideal gas, $\gamma$ is the ratio of specific heats, $c_p / c_v$. 30 | 31 | 32 | ## Conservative form 33 | 34 | As expressed above, the Euler equations are in conservative form. We can define the conservative 35 | state, ${\bf U}$ as: 36 | 37 | $${\bf U} = \left ( \begin{array}{c} \rho \\ \rho u \\ \rho E \end{array} \right )$$ 38 | 39 | and the flux, ${\bf F}({\bf U})$ as: 40 | 41 | $${\bf F} = \left ( \begin{array}{c} \rho u \\ \rho u^2 + p \\ (\rho E + p) u \end{array} \right )$$ 42 | 43 | and then our system in conservative form is: 44 | 45 | $${\bf U}_t + [{\bf F}({\bf U})]_x = 0$$ 46 | 47 | and we can do the same technique of discretizing the domain into cells 48 | and integrating over the volume of a cell to get the finite-volume 49 | conservative update for the system: 50 | 51 | $$\frac{\partial {\bf U}_i}{\partial t} = 52 | - \frac{1}{\Delta x} \left [ {\bf F}({\bf U}_{i+1/2}) - {\bf F}({\bf U}_{i+1/2}) \right ]$$ 53 | 54 | This means that we will be able to use the same basic solution methodology 55 | from advection and Burgers' equation with the Euler equations, so long 56 | as we can find the fluxes. This primarily means that we need to understand the Riemann 57 | problem for the Euler equations. 58 | 59 | ## Primitive variable form 60 | 61 | We can alternately express the Euler equations in terms of the primitive variables, ${\bf q}$: 62 | 63 | $${\bf q} = \left ( \begin{array}{c} \rho \\ u \\ p \end{array} \right )$$ 64 | 65 | and the evolution equations are: 66 | 67 | $$ 68 | \left ( \begin{array}{c} \rho \\ u \\ p \end{array} \right )_t + 69 | \left ( \begin{array}{c} u & \rho & 0 \\ 0 & u & 1/\rho \\ 0 & \Gamma_1 p & u \end{array} \right ) 70 | \left ( \begin{array}{c} \rho \\ u \\ p \end{array} \right )_x = 0 71 | $$ 72 | 73 | where $\Gamma_1 = d \log p/d \log \rho |_s$ 74 | 75 | or compactly: 76 | 77 | $${\bf q}_t + {\bf A}({\bf q}) {\bf q}_x = 0$$ 78 | 79 | Another useful quantity is the speed of sound, defined as: 80 | 81 | $$c = \sqrt{\frac{\Gamma_1 p}{\rho}}$$ 82 | 83 | 84 | ## Characteristic form 85 | 86 | We call a system *hyperbolic* if the eigenvalues are real and finite. 87 | For our matrix ${\bf A}({\bf q})$ above, the eigenvalues are: 88 | 89 | $$ 90 | \begin{align} 91 | \lambda^{(-)} &= u - c \\ 92 | \lambda^{(0)} &= u \\ 93 | \lambda^{(+)} &= u + c \\ 94 | \end{align} 95 | $$ 96 | 97 | These are the speeds at which information propagates in our system. 98 | 99 | We can also find the right and left eigenvectors: 100 | 101 | $${\bf A} {\bf r}^{(\nu)} = \lambda^{(\nu)} {\bf r}^{(\nu)}$$ 102 | 103 | $${\bf l}^{(\nu)} {\bf A} = \lambda^{(\nu)} {\bf l}^{(\nu)}$$ 104 | 105 | giving: 106 | 107 | $$ 108 | {\bf r}^{(-)} = \left ( \begin{array}{c} 1 \\ -c/\rho \\ c^2 \end{array} \right ) 109 | % 110 | \qquad 111 | {\bf r}^{(0)} = \left ( \begin{array}{c} 1 \\ 0 \\ 0 \end{array} \right ) 112 | % 113 | \qquad 114 | {\bf r}^{(+)} = \left ( \begin{array}{c} 1 \\ c/\rho \\ c^2 \end{array} \right ) 115 | $$ 116 | 117 | and 118 | 119 | $$ 120 | \begin{align*} 121 | {\bf l}^{(-)} &= \left ( \begin{array}{ccc} 0 & -\frac{\rho}{2c} & \frac{1}{2c^2} \end{array} \right ) \\ 122 | {\bf l}^{(0)} &= \left ( \begin{array}{ccc} 1 & 0 & -\frac{1}{c^2} \end{array} \right ) \\ 123 | {\bf l}^{(+)} &= \left ( \begin{array}{ccc} 0 & \frac{\rho}{2c} & \frac{1}{2c^2} \end{array} \right ) \\ 124 | \end{align*} 125 | $$ 126 | 127 | These are normalized such that 128 | 129 | $${\bf l}^{(i)} \cdot {\bf r}^{(j)} = \delta_{ij}$$ 130 | 131 | The final form of the Euler equations we will need is the *characteristic form*. We can 132 | construct square matrices ${\bf R}$ and ${\bf L}$ by grouping together the eigenvectors: 133 | 134 | $${\bf R} = \left ( {\bf r}^{(-)} | {\bf r}^{(0)} | {\bf r}^{(+)} \right )$$ 135 | 136 | $${\bf L} = \left ( \begin{array}{c} {\bf l}^{(-)} \\ 137 | {\bf l}^{(0)} \\ 138 | {\bf l}^{(+)} \end{array} \right )$$ 139 | 140 | These satisfy: 141 | 142 | $${\bf L}{\bf R} = {\bf R} {\bf L} = {\bf I}$$ 143 | 144 | We then define the characteristic variables via: 145 | 146 | $$d{\bf w} = {\bf L} d{\bf q}$$ 147 | 148 | Since we are nonlinear, we cannot simply integrate this. Using this definition, we can 149 | rewrite our system as: 150 | 151 | $${\bf w}_t + {\bf \Lambda} {\bf w}_x = 0$$ 152 | 153 | where ${\bf \Lambda}$ is a diagonal matrix with the eigenvalues on the diagonal: 154 | 155 | $$ 156 | {\bf \Lambda} = {\bf LAR} = 157 | \left ( \begin{array}{ccc} 158 | \lambda^{(-)} & & \\ 159 | & \lambda^{(0)} & \\ 160 | & & \lambda^{(+)} \end{array} \right ) 161 | $$ 162 | 163 | In this form, the 3 equations are decoupled and are just advection 164 | equations for each of the characteristic variables. If our system 165 | were linear, we'd be done -- each characteristic variable would advect 166 | at its given wave speed without interacting with one-another. 167 | 168 | Since we are nonlinear, our solution is more difficult, but the 169 | characteristic system still tells us a lot. 170 | 171 | > Consider an initial discontinuity in the primitive variables. Each 172 | > of the 3 characteristic waves will carry a jump in their associated 173 | > characteristic quantity away from the discontinuity at their 174 | > characteristic speed. 175 | 176 | ![Riemann waves carrying a jump](riemann-waves-jump.png) 177 | 178 | The corresponding jump in the primitive variable is then just 179 | 180 | $$d{\bf q} = {\bf L}^{-1} d{\bf w} = {\bf R}d{\bf w}$$ 181 | 182 | Looking at the right eigenvectors, we see that all primitive variables 183 | jump across the left and right waves, but only density jumps across 184 | the middle wave. 185 | 186 | * The middle wave is called a *contact discontinuity*. Since the velocity 187 | does not jump across it, there can be no compression or expansion, so 188 | shocks and rarefactions are not allowed. 189 | 190 | * The left and right waves can be either a shock or a rarefaction. 191 | 192 | Here's an example of the solution of a Riemann problem for the Euler equations. 193 | The state on the left is initialized to: 194 | 195 | $$ 196 | \begin{align*} 197 | \rho_L &= 1 \\ 198 | u_L &= 0 \\ 199 | p_L &= 1 \\ 200 | \end{align*} 201 | $$ 202 | 203 | and on the right: 204 | 205 | $$ 206 | \begin{align*} 207 | \rho_R &= 1/8 \\ 208 | u_R &= 0 \\ 209 | p_R &= 1/10 \\ 210 | \end{align*} 211 | $$ 212 | 213 | ![Riemann Sod problem](riemann-sod.png) 214 | 215 | The jumps we see are just as the right eigenvectors indicated. Only 216 | the density jumps across the middle wave. The left wave is a shock 217 | and the right wave is a rarefaction. 218 | 219 | 220 | -------------------------------------------------------------------------------- /content/advection_euler/euler/fv_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/euler/fv_grid.png -------------------------------------------------------------------------------- /content/advection_euler/euler/rarefaction_span.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/euler/rarefaction_span.png -------------------------------------------------------------------------------- /content/advection_euler/euler/riemann-mol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/euler/riemann-mol.png -------------------------------------------------------------------------------- /content/advection_euler/euler/riemann-sod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/euler/riemann-sod.png -------------------------------------------------------------------------------- /content/advection_euler/euler/riemann-waves-jump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/euler/riemann-waves-jump.png -------------------------------------------------------------------------------- /content/advection_euler/euler/riemann-waves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/euler/riemann-waves.png -------------------------------------------------------------------------------- /content/advection_euler/euler/riemann_exact.py: -------------------------------------------------------------------------------- 1 | """An exact Riemann solver for the Euler equations with a gamma-law 2 | gas. The left and right states are stored as State objects. We then 3 | create a RiemannProblem object with the left and right state: 4 | 5 | > rp = RiemannProblem(left_state, right_state) 6 | 7 | Next we solve for the star state: 8 | 9 | > rp.find_star_state() 10 | 11 | Finally, we sample the solution to find the interface state, which 12 | is returned as a State object: 13 | 14 | > q_int = rp.sample_solution() 15 | """ 16 | 17 | import numpy as np 18 | import scipy.optimize as optimize 19 | 20 | class State: 21 | """ a simple object to hold a primitive variable state """ 22 | 23 | def __init__(self, p=1.0, u=0.0, rho=1.0): 24 | self.p = p 25 | self.u = u 26 | self.rho = rho 27 | 28 | def __str__(self): 29 | return f"rho: {self.rho}; u: {self.u}; p: {self.p}" 30 | 31 | class RiemannProblem: 32 | """ a class to define a Riemann problem. It takes a left 33 | and right state. Note: we assume a constant gamma """ 34 | 35 | def __init__(self, left_state, right_state, gamma=1.4): 36 | self.left = left_state 37 | self.right = right_state 38 | self.gamma = gamma 39 | 40 | self.ustar = None 41 | self.pstar = None 42 | 43 | def __str__(self): 44 | return f"pstar = {self.pstar}, ustar = {self.ustar}" 45 | 46 | def u_hugoniot(self, p, side): 47 | """define the Hugoniot curve, u(p).""" 48 | 49 | if side == "left": 50 | state = self.left 51 | s = 1.0 52 | elif side == "right": 53 | state = self.right 54 | s = -1.0 55 | 56 | c = np.sqrt(self.gamma*state.p/state.rho) 57 | 58 | if p < state.p: 59 | # rarefaction 60 | u = state.u + s*(2.0*c/(self.gamma-1.0))* \ 61 | (1.0 - (p/state.p)**((self.gamma-1.0)/(2.0*self.gamma))) 62 | else: 63 | # shock 64 | beta = (self.gamma+1.0)/(self.gamma-1.0) 65 | u = state.u + s*(2.0*c/np.sqrt(2.0*self.gamma*(self.gamma-1.0)))* \ 66 | (1.0 - p/state.p)/np.sqrt(1.0 + beta*p/state.p) 67 | 68 | return u 69 | 70 | def find_star_state(self, p_min=0.001, p_max=1000.0): 71 | """ root find the Hugoniot curve to find ustar, pstar """ 72 | 73 | # we need to root-find on 74 | self.pstar = optimize.brentq( 75 | lambda p: self.u_hugoniot(p, "left") - self.u_hugoniot(p, "right"), 76 | p_min, p_max) 77 | self.ustar = self.u_hugoniot(self.pstar, "left") 78 | 79 | 80 | def shock_solution(self, sgn, state): 81 | """return the interface solution considering a shock""" 82 | 83 | p_ratio = self.pstar/state.p 84 | c = np.sqrt(self.gamma*state.p/state.rho) 85 | 86 | # Toro, eq. 4.52 / 4.59 87 | S = state.u + sgn*c*np.sqrt(0.5*(self.gamma + 1.0)/self.gamma*p_ratio + 88 | 0.5*(self.gamma - 1.0)/self.gamma) 89 | 90 | # are we to the left or right of the shock? 91 | if (self.ustar < 0 and S < 0) or (self.ustar > 0 and S > 0): 92 | # R/L region 93 | solution = state 94 | else: 95 | # * region -- get rhostar from Toro, eq. 4.50 / 4.57 96 | gam_fac = (self.gamma - 1.0)/(self.gamma + 1.0) 97 | rhostar = state.rho * (p_ratio + gam_fac)/(gam_fac * p_ratio + 1.0) 98 | solution = State(rho=rhostar, u=self.ustar, p=self.pstar) 99 | 100 | return solution 101 | 102 | def rarefaction_solution(self, sgn, state): 103 | """return the interface solution considering a rarefaction wave""" 104 | 105 | # find the speed of the head and tail of the rarefaction fan 106 | 107 | # isentropic (Toro eq. 4.54 / 4.61) 108 | p_ratio = self.pstar/state.p 109 | c = np.sqrt(self.gamma*state.p/state.rho) 110 | cstar = c*p_ratio**((self.gamma-1.0)/(2*self.gamma)) 111 | 112 | lambda_head = state.u + sgn*c 113 | lambda_tail = self.ustar + sgn*cstar 114 | 115 | gam_fac = (self.gamma - 1.0)/(self.gamma + 1.0) 116 | 117 | if (sgn > 0 and lambda_head < 0) or (sgn < 0 and lambda_head > 0): 118 | # R/L region 119 | solution = state 120 | 121 | elif (sgn > 0 and lambda_tail > 0) or (sgn < 0 and lambda_tail < 0): 122 | # * region, we use the isentropic density (Toro 4.53 / 4.60) 123 | solution = State(rho = state.rho*p_ratio**(1.0/self.gamma), 124 | u = self.ustar, p = self.pstar) 125 | 126 | else: 127 | # we are in the fan -- Toro 4.56 / 4.63 128 | rho = state.rho * (2/(self.gamma + 1.0) - 129 | sgn*gam_fac*state.u/c)**(2.0/(self.gamma-1.0)) 130 | u = 2.0/(self.gamma + 1.0) * ( -sgn*c + 0.5*(self.gamma - 1.0)*state.u) 131 | p = state.p * (2/(self.gamma + 1.0) - 132 | sgn*gam_fac*state.u/c)**(2.0*self.gamma/(self.gamma-1.0)) 133 | solution = State(rho=rho, u=u, p=p) 134 | 135 | return solution 136 | 137 | def sample_solution(self): 138 | """given the star state (ustar, pstar), find the state on the interface""" 139 | 140 | if self.ustar < 0: 141 | # we are in the R* or R region 142 | state = self.right 143 | sgn = 1.0 144 | else: 145 | # we are in the L* or L region 146 | state = self.left 147 | sgn = -1.0 148 | 149 | # is the non-contact wave a shock or rarefaction? 150 | if self.pstar > state.p: 151 | # compression! we are a shock 152 | solution = self.shock_solution(sgn, state) 153 | 154 | else: 155 | # rarefaction 156 | solution = self.rarefaction_solution(sgn, state) 157 | 158 | return solution 159 | 160 | 161 | if __name__ == "__main__": 162 | 163 | q_l = State(rho=1.0, u=0.0, p=1.0) 164 | q_r = State(rho=0.125, u=0.0, p=0.1) 165 | 166 | rp = RiemannProblem(q_l, q_r, gamma=1.4) 167 | 168 | rp.find_star_state() 169 | q_int = rp.sample_solution() 170 | print(q_int) 171 | -------------------------------------------------------------------------------- /content/advection_euler/euler/riemann_state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/euler/riemann_state.png -------------------------------------------------------------------------------- /content/advection_euler/euler/simplegrid_gc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/euler/simplegrid_gc.png -------------------------------------------------------------------------------- /content/advection_euler/euler/sod-exact.out: -------------------------------------------------------------------------------- 1 | # Exact solution for the Sod problem at t = 0.2 s, using Toro's exact Riemann 2 | # solver (Ch. 4) and gamma = 1.4 3 | x rho u p e 4 | 0.003906 1.000000 0.000000 1.000000 2.500000 5 | 0.011719 1.000000 0.000000 1.000000 2.500000 6 | 0.019531 1.000000 0.000000 1.000000 2.500000 7 | 0.027344 1.000000 0.000000 1.000000 2.500000 8 | 0.035156 1.000000 0.000000 1.000000 2.500000 9 | 0.042969 1.000000 0.000000 1.000000 2.500000 10 | 0.050781 1.000000 0.000000 1.000000 2.500000 11 | 0.058594 1.000000 0.000000 1.000000 2.500000 12 | 0.066406 1.000000 0.000000 1.000000 2.500000 13 | 0.074219 1.000000 0.000000 1.000000 2.500000 14 | 0.082031 1.000000 0.000000 1.000000 2.500000 15 | 0.089844 1.000000 0.000000 1.000000 2.500000 16 | 0.097656 1.000000 0.000000 1.000000 2.500000 17 | 0.105469 1.000000 0.000000 1.000000 2.500000 18 | 0.113281 1.000000 0.000000 1.000000 2.500000 19 | 0.121094 1.000000 0.000000 1.000000 2.500000 20 | 0.128906 1.000000 0.000000 1.000000 2.500000 21 | 0.136719 1.000000 0.000000 1.000000 2.500000 22 | 0.144531 1.000000 0.000000 1.000000 2.500000 23 | 0.152344 1.000000 0.000000 1.000000 2.500000 24 | 0.160156 1.000000 0.000000 1.000000 2.500000 25 | 0.167969 1.000000 0.000000 1.000000 2.500000 26 | 0.175781 1.000000 0.000000 1.000000 2.500000 27 | 0.183594 1.000000 0.000000 1.000000 2.500000 28 | 0.191406 1.000000 0.000000 1.000000 2.500000 29 | 0.199219 1.000000 0.000000 1.000000 2.500000 30 | 0.207031 1.000000 0.000000 1.000000 2.500000 31 | 0.214844 1.000000 0.000000 1.000000 2.500000 32 | 0.222656 1.000000 0.000000 1.000000 2.500000 33 | 0.230469 1.000000 0.000000 1.000000 2.500000 34 | 0.238281 1.000000 0.000000 1.000000 2.500000 35 | 0.246094 1.000000 0.000000 1.000000 2.500000 36 | 0.253906 1.000000 0.000000 1.000000 2.500000 37 | 0.261719 1.000000 0.000000 1.000000 2.500000 38 | 0.269531 0.978445 0.025727 0.969954 2.478304 39 | 0.277344 0.951706 0.058279 0.933048 2.450988 40 | 0.285156 0.925555 0.090831 0.897353 2.423823 41 | 0.292969 0.899982 0.123383 0.862834 2.396810 42 | 0.300781 0.874977 0.155935 0.829460 2.369948 43 | 0.308594 0.850532 0.188487 0.797199 2.343237 44 | 0.316406 0.826635 0.221039 0.766019 2.316678 45 | 0.324219 0.803279 0.253591 0.735890 2.290270 46 | 0.332031 0.780454 0.286144 0.706783 2.264013 47 | 0.339844 0.758151 0.318696 0.678668 2.237908 48 | 0.347656 0.736360 0.351248 0.651518 2.211954 49 | 0.355469 0.715073 0.383800 0.625304 2.186152 50 | 0.363281 0.694282 0.416352 0.599999 2.160501 51 | 0.371094 0.673977 0.448904 0.575577 2.135001 52 | 0.378906 0.654150 0.481456 0.552012 2.109653 53 | 0.386719 0.634792 0.514008 0.529278 2.084456 54 | 0.394531 0.615896 0.546560 0.507353 2.059410 55 | 0.402344 0.597452 0.579112 0.486210 2.034516 56 | 0.410156 0.579452 0.611664 0.465827 2.009773 57 | 0.417969 0.561889 0.644216 0.446181 1.985182 58 | 0.425781 0.544755 0.676769 0.427249 1.960742 59 | 0.433594 0.528041 0.709321 0.409010 1.936453 60 | 0.441406 0.511739 0.741873 0.391443 1.912316 61 | 0.449219 0.495843 0.774425 0.374526 1.888330 62 | 0.457031 0.480345 0.806977 0.358240 1.864495 63 | 0.464844 0.465236 0.839529 0.342565 1.840812 64 | 0.472656 0.450510 0.872081 0.327481 1.817280 65 | 0.480469 0.436160 0.904633 0.312971 1.793900 66 | 0.488281 0.426319 0.927453 0.303130 1.777600 67 | 0.496094 0.426319 0.927453 0.303130 1.777600 68 | 0.503906 0.426319 0.927453 0.303130 1.777600 69 | 0.511719 0.426319 0.927453 0.303130 1.777600 70 | 0.519531 0.426319 0.927453 0.303130 1.777600 71 | 0.527344 0.426319 0.927453 0.303130 1.777600 72 | 0.535156 0.426319 0.927453 0.303130 1.777600 73 | 0.542969 0.426319 0.927453 0.303130 1.777600 74 | 0.550781 0.426319 0.927453 0.303130 1.777600 75 | 0.558594 0.426319 0.927453 0.303130 1.777600 76 | 0.566406 0.426319 0.927453 0.303130 1.777600 77 | 0.574219 0.426319 0.927453 0.303130 1.777600 78 | 0.582031 0.426319 0.927453 0.303130 1.777600 79 | 0.589844 0.426319 0.927453 0.303130 1.777600 80 | 0.597656 0.426319 0.927453 0.303130 1.777600 81 | 0.605469 0.426319 0.927453 0.303130 1.777600 82 | 0.613281 0.426319 0.927453 0.303130 1.777600 83 | 0.621094 0.426319 0.927453 0.303130 1.777600 84 | 0.628906 0.426319 0.927453 0.303130 1.777600 85 | 0.636719 0.426319 0.927453 0.303130 1.777600 86 | 0.644531 0.426319 0.927453 0.303130 1.777600 87 | 0.652344 0.426319 0.927453 0.303130 1.777600 88 | 0.660156 0.426319 0.927453 0.303130 1.777600 89 | 0.667969 0.426319 0.927453 0.303130 1.777600 90 | 0.675781 0.426319 0.927453 0.303130 1.777600 91 | 0.683594 0.426319 0.927453 0.303130 1.777600 92 | 0.691406 0.265574 0.927453 0.303130 2.853541 93 | 0.699219 0.265574 0.927453 0.303130 2.853541 94 | 0.707031 0.265574 0.927453 0.303130 2.853541 95 | 0.714844 0.265574 0.927453 0.303130 2.853541 96 | 0.722656 0.265574 0.927453 0.303130 2.853541 97 | 0.730469 0.265574 0.927453 0.303130 2.853541 98 | 0.738281 0.265574 0.927453 0.303130 2.853541 99 | 0.746094 0.265574 0.927453 0.303130 2.853541 100 | 0.753906 0.265574 0.927453 0.303130 2.853541 101 | 0.761719 0.265574 0.927453 0.303130 2.853541 102 | 0.769531 0.265574 0.927453 0.303130 2.853541 103 | 0.777344 0.265574 0.927453 0.303130 2.853541 104 | 0.785156 0.265574 0.927453 0.303130 2.853541 105 | 0.792969 0.265574 0.927453 0.303130 2.853541 106 | 0.800781 0.265574 0.927453 0.303130 2.853541 107 | 0.808594 0.265574 0.927453 0.303130 2.853541 108 | 0.816406 0.265574 0.927453 0.303130 2.853541 109 | 0.824219 0.265574 0.927453 0.303130 2.853541 110 | 0.832031 0.265574 0.927453 0.303130 2.853541 111 | 0.839844 0.265574 0.927453 0.303130 2.853541 112 | 0.847656 0.265574 0.927453 0.303130 2.853541 113 | 0.855469 0.125000 0.000000 0.100000 2.000000 114 | 0.863281 0.125000 0.000000 0.100000 2.000000 115 | 0.871094 0.125000 0.000000 0.100000 2.000000 116 | 0.878906 0.125000 0.000000 0.100000 2.000000 117 | 0.886719 0.125000 0.000000 0.100000 2.000000 118 | 0.894531 0.125000 0.000000 0.100000 2.000000 119 | 0.902344 0.125000 0.000000 0.100000 2.000000 120 | 0.910156 0.125000 0.000000 0.100000 2.000000 121 | 0.917969 0.125000 0.000000 0.100000 2.000000 122 | 0.925781 0.125000 0.000000 0.100000 2.000000 123 | 0.933594 0.125000 0.000000 0.100000 2.000000 124 | 0.941406 0.125000 0.000000 0.100000 2.000000 125 | 0.949219 0.125000 0.000000 0.100000 2.000000 126 | 0.957031 0.125000 0.000000 0.100000 2.000000 127 | 0.964844 0.125000 0.000000 0.100000 2.000000 128 | 0.972656 0.125000 0.000000 0.100000 2.000000 129 | 0.980469 0.125000 0.000000 0.100000 2.000000 130 | 0.988281 0.125000 0.000000 0.100000 2.000000 131 | 0.996094 0.125000 0.000000 0.100000 2.000000 132 | -------------------------------------------------------------------------------- /content/advection_euler/projects/2dgrid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/projects/2dgrid.png -------------------------------------------------------------------------------- /content/advection_euler/projects/fvrestrict.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/projects/fvrestrict.png -------------------------------------------------------------------------------- /content/advection_euler/projects/piecewise-parabolic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/advection_euler/projects/piecewise-parabolic.png -------------------------------------------------------------------------------- /content/advection_euler/projects/projects.md: -------------------------------------------------------------------------------- 1 | # Practice Projects 2 | 3 | Here are some project ideas to help reinforce this material. 4 | 5 | ## 1. Measuring convergence of our Euler solver 6 | 7 | We measured convergence with advection by comparing to the exact 8 | solution. But what about the case when we don't know the exact 9 | solution? We can use a grid convergence study to assess the 10 | convergence. Here's how it works. 11 | 12 | 1. Pick a smooth problem -- a good problem is the acoustic pulse 13 | described in this paper: 14 | 15 | [A high-order 16 | finite-volume method for conservation laws on locally refined 17 | grids](https://msp.org/camcos/2011/6-1/p01.xhtml) 18 | 19 | See section 4.2. You'll do this in 1-d in our solver with periodic 20 | BCs. 21 | 22 | 2. Run the problem at 4 different resolutions, each varying by a 23 | factor of 2, e.g., 32, 64, 128, and 256 zones. 24 | 25 | 3. Compute an error between the run with N zones and the run with 2N 26 | zones as follows: 27 | 28 | * Coarsen the problem with 2N zones down to N zones by averaging 29 | 2 fine zones into a single coarse zone. This is shown below: 30 | 31 | ![restriction from a fine grid to a coarse grid](fvrestrict.png) 32 | 33 | Here, $\phi^f$ is a variable on the finer resolution grid and 34 | $\phi^c$ is the variable on the coarse grid. We see that $\phi^c_j$ 35 | has two fine grid counterparts: $\phi^f_i$ and $\phi^f_{i+1}$, so 36 | we would do: 37 | 38 | $$\phi^c_j = \frac{1}{2} \left ( \phi^f_i + \phi^f_{i+1} \right )$$ 39 | 40 | * Compute the $L_2$norm of the difference between the 41 | coarsened 2N zone run and the N zone run. 42 | 43 | * Do this for all pairs, so for the 4 runs proposed above, you'd 44 | have 3 errors corresponding to 64-128, 128-256, and 256-512. 45 | 46 | 4. Plot the errors along with a line representing ideal 2nd order convergence. 47 | 48 | optional: try a different integrator, like 4th order Runge-Kutta and 49 | see how that changes the convergence. 50 | 51 | 52 | ## 2. Sedov explosion and spherical symmetry 53 | 54 | We solved the equations of hydrodynamics in 1-d Cartesian coordinates. 55 | If we want to model something that is spherically symmetric, we can do 56 | 1-d spherical coordinates. 57 | 58 | In 1-d spherical coordinates, the equations of hydrodynamics are: 59 | 60 | $$ 61 | \begin{align*} 62 | \frac{\partial \rho}{\partial t} + \frac{1}{r^2} \frac{\partial (r^2 \rho u)}{\partial r} &= 0 \\ 63 | \frac{\partial (\rho u)}{\partial t} + \frac{1}{r^2} \frac{\partial (r^2 \rho u^2)}{\partial r} + \frac{\partial p}{\partial r} &= 0 \\ 64 | \frac{\partial (\rho E)}{\partial t} + \frac{1}{r^2} \frac{\partial }{\partial r} \left [ r^2 (\rho E + p) u \right ] &= 0 65 | \end{align*} 66 | $$ 67 | 68 | The main difference is that the divergence has area and volume terms now. 69 | 70 | A good problem to try with this is the Sedov blast wave explosion. In 71 | this problem, you put a lot of energy into a point at the center (the 72 | origin of our spherical coordinates) and watch a spherical blast wave 73 | move outward. There is an analytic solution for a gamma-law gas that 74 | can be compared to. 75 | 76 | To solve this with the method-of-lines approach, we would need to: 77 | 78 | 1. Add the $r$ terms to the conservative update. 79 | 80 | 2. Implement reflecting boundary conditions at the origin of coordinates. 81 | 82 | Reflecting boundary conditions mean that each zone in the ghost 83 | cells has the same value as the corresponding zone in the interior 84 | (e.g., same distance away from the boundary). But for the 85 | velocity, we switch the sign, such that the velocity moving toward 86 | the interface will go to zero. 87 | 88 | This would look like: 89 | 90 | $$\begin{align*} 91 | \rho_{\mathrm{lo}-1} &= \rho_{\mathrm{lo}} \\ 92 | \rho_{\mathrm{lo}-2} &= \rho_{\mathrm{lo+1}} \\ 93 | \end{align*}$$ 94 | 95 | $$\begin{align*} 96 | (\rho u)_{\mathrm{lo}-1} &= -(\rho u)_{\mathrm{lo}} \\ 97 | (\rho u)_{\mathrm{lo}-2} &= -(\rho u)_{\mathrm{lo+1}} \\ 98 | \end{align*}$$ 99 | 100 | $$\begin{align*} 101 | (\rho E)_{\mathrm{lo}-1} &= (\rho E)_{\mathrm{lo}} \\ 102 | (\rho E)_{\mathrm{lo}-2} &= (\rho E)_{\mathrm{lo+1}} \\ 103 | \end{align*}$$ 104 | 105 | 106 | 3. Setup the Sedov problem (I give a common implementation of the initial conditions below) 107 | and run and compare to the exact solution. 108 | 109 | For the initial conditions, it is common to specify an initial radius of the explosion, $r_0$. Then 110 | initialize the pressure as: 111 | 112 | $$p = \left \{ \begin{array}{cc} p_\mathrm{expl} & r \le r_0 \\ 113 | p_\mathrm{ambient} & r > r_0 \end{array} \right . $$ 114 | 115 | with 116 | 117 | $$p_\mathrm{ambient} = 10^{-5}$$ 118 | 119 | and 120 | 121 | $$p_\mathrm{expl} = (\gamma -1) \frac{E_\mathrm{Sedov}}{(4/3) \pi r_0^3}$$ 122 | 123 | and $E_\mathrm{Sedov} = 1$ is the initial explosion energy. This 124 | formulation finds the pressure corresponding to spreading that 125 | energy over a sphere of radius $r_0$. 126 | 127 | The initial velocity is set to 0.0. 128 | 129 | The density everywhere is 1.0. 130 | 131 | Here's the analytic solution for a case with $\gamma = 1.4$: 132 | {download}`spherical_sedov.txt` 133 | 134 | ## 3. HLL Riemann solver 135 | 136 | We solved the Riemann problem for the Euler equations exactly, but 137 | many times in practice we use approximate Riemann solvers. The HLL 138 | solver is a popular solver. Research this method and implement it in 139 | the Euler code and compare the solutions you get with it to those with 140 | the exact solver. 141 | 142 | 143 | ## 4. Piecewise parabolic reconstruction for advection 144 | 145 | In class, we considered piecewise constant and piecewise linear 146 | reconstruction with advection. The next step is piecewise parabolic 147 | reconstruction. This is described originally here: 148 | 149 | [The Piecewise Parabolic Method (PPM) for Gas-Dynamical Systems](https://crd.lbl.gov/assets/pubs_presos/AMCS/ANAG/A141984.pdf) 150 | 151 | We want to try this with our method-of-lines integration scheme, which 152 | considerably simplifies things compared to that original paper. 153 | 154 | Here's an example of piecewise parabolic reconstruction of data in each zone: 155 | 156 | ![ppm](piecewise-parabolic.png) 157 | 158 | As with piecewise linear, we will need to limit the parabola to 159 | prevent overshoots. The dotted lines in the figure above are 160 | unlimited parabolas and the solid lines are the limited parabola. 161 | 162 | The basic idea is as follows: 163 | 164 | 1. In each zone, we construct a parabola, given by CW Eq. 1.4. The CW 165 | paper uses $\xi$ as the space coordinate, were $\xi_i$ is a zone 166 | center and $\xi_{i-1/2}$ and $\xi_{i+1/2}$ are the left and right 167 | edges. It also allows for a non-uniformly spaced grid. 168 | 169 | The parabola has the form: 170 | 171 | $$a(\xi) = a_{L,i} + x [ \Delta a_i + a_{6,i} (1 - x) ]$$ 172 | 173 | where 174 | 175 | $$x = \frac{\xi - \xi_{i-1/2}}{\Delta \xi}$$ 176 | 177 | and 178 | 179 | $$\Delta a_i = a_{R,i} - a_{L,i}$$ 180 | 181 | $$a_{6,i} = 6 \left [ a_i - \frac{1}{2} (a_{L,i} + a_{R,i}) \right ]$$ 182 | 183 | Eqs. 1.5 through 1.10 give the method for computing the 3 184 | coefficients of the parabola for each cell as well as limiting them 185 | so as to not introduce any new extrema. 186 | 187 | For a uniform grid, the steps are: 188 | 189 | * Find the first guess at the interface values: 190 | 191 | $$a_{i+1/2} = \frac{1}{2} (a_i + a_{i+1}) + \frac{1}{6} (\delta a_i - \delta a_{i+1} )$$ 192 | 193 | with 194 | 195 | 196 | $$\delta a_i = \left \{ \begin{array}{cc} 197 | \min\left (\frac{1}{2}|a_{i+1} - a_{i-1}|, 2 |a_{i+1} - a_i|, 2 |a_i - a_{i-1}|\right ) 198 | \mathrm{sign}(a_{i+1} - a_{i-1}) & \mbox{if}\ (a_{i+1} - a_i)(a_i - a_{i-1}) > 0 \\ 199 | 0 & \mbox{otherwise} \end{array} \right .$$ 200 | 201 | * Next, for zone $i$, set the left and right interface values using the above: 202 | 203 | $$a_{L,i} = a_{i-1/2}$$ 204 | $$a_{R,i} = a_{i+1/2}$$ 205 | 206 | * Now limit the data according to Eq. 1.10. 207 | 208 | Note: you'll need 3 ghost cells in order to compute all of the information needed. 209 | 210 | 211 | 2. For the method-of-lines scheme we are using, we simply evaluate the parabola 212 | on each interface and this gives that's zones edge state. For a parabolic reconstruction 213 | in zone $i$ of the form $a(\xi)$, our interface states are: 214 | 215 | $$a_{i-1/2,R} = a(\xi_{i-1/2}) = a_{L,i}$$ 216 | 217 | $$a_{i+1/2,L} = a(\xi_{i+1/2}) = a_{R,i}$$ 218 | 219 | 3. Compare the solution you get with PPM for the Sod problem to the 220 | one we got with piecewise linear slopes. 221 | 222 | ## 5. Two-dimensional advection 223 | 224 | The linear advection equation in 2-d is: 225 | 226 | $$a_t + u a_x + v a_y = 0$$ 227 | 228 | In conservative form, we'd write this as: 229 | 230 | $$\frac{\partial a}{\partial t} + \frac{\partial (u a)}{\partial x} + \frac{\partial (v a)}{\partial y} = 0$$ 231 | 232 | We can develop a finite volume method by defining an average as: 233 | 234 | $$\langle a \rangle_{i,j} = \frac{1}{\Delta x \Delta y} \int_{x_{i-1/2}}^{x_{i+1/2}} \int_{y_{j-1/2}}^{y_{j+1/2}} a(x, y) dx dy$$ 235 | 236 | and our final update would look like (dropping the $\langle \rangle$): 237 | 238 | $$\frac{\partial}{\partial t} a_{i,j} = - \frac{1}{\Delta x} (F^{(x)}_{i+1/2,j} - F^{(x)}_{i-1/2,j}) - \frac{1}{\Delta y} (F^{(y)}_{i,j+1/2} - F^{(y)}_{i,j-1/2})$$ 239 | 240 | where $F^{(x)} = u a$ and $F^{(y)} = v a$. 241 | 242 | This can be solved using the same method-of-lines technique we did in 243 | 1-d, but now we need to create and manage a 2-d grid, fill ghost cells 244 | on both $x$ and $y$ boundaries, and compute fluxes through both $x$ 245 | and $y$ interfaces. 246 | 247 | ![an example 2-d grid showing the interface states](2dgrid.png) 248 | 249 | The flux computations are done simply by 250 | reconstructing in one coordinate direction and solving the Riemann 251 | problem in that direction. 252 | 253 | Code up a 2-d advection solver and test it on advecting a Gaussian. 254 | 255 | The timestep limiter needs to be adapted a bit, and is now: 256 | 257 | $$\Delta t = C \left [ \frac{u}{\Delta x} + \frac{v}{\Delta y} \right ]^{-1}$$ 258 | 259 | 260 | ## 6. Non-conservation? 261 | 262 | Suppose instead of solving the total energy equation in the Euler 263 | solver, you instead discretized the internal energy evolution 264 | equation: 265 | 266 | $$\frac{\partial (\rho e)}{\partial t} + \frac{\partial (\rho e u)}{\partial x} + p \frac{\partial u}{\partial x} = 0$$ 267 | 268 | You can compute the flux and the $p \partial u/\partial x$ term using the solution from the Riemann problem as: 269 | 270 | $$p_i \frac{u_{i+1/2} - u_{i-1/2}}{\Delta x}$$ 271 | 272 | Note that the pressure here is the cell-center (average) pressure. 273 | 274 | Code this up and run the Sod problem -- how well do you agree with the exact solution? 275 | 276 | 277 | 278 | ## 7. Explore pyro 279 | 280 | Look at the python hydro solver pyro: 281 | 282 | [https://pyro2.readthedocs.io/en/latest/](https://pyro2.readthedocs.io/en/latest/) 283 | 284 | Look at the list of solvers and work on running it and exploring the 285 | problems that it provides. Then add a problem to the solver, based on 286 | something you find in the literature. 287 | -------------------------------------------------------------------------------- /content/basics/ODEs/ODEs.md: -------------------------------------------------------------------------------- 1 | Ordinary Differential Equations 2 | =============================== 3 | 4 | There are a lot of methods for solving ordinary differential equations 5 | numerically. We'll look at a few common ones—essentially what 6 | we will need for integrating the equations of hydrodynamics later. 7 | 8 | We are going to be skipping entire classes of methods, including 9 | implicit methods (useful for stiff equations like those arising in 10 | reaction networks), multistep methods, and sympletic integrators 11 | (useful for N-body methods where energy conservation is desired). 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /content/basics/ODEs/ellipse_initial_cond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/ODEs/ellipse_initial_cond.png -------------------------------------------------------------------------------- /content/basics/ODEs/rk2_Euler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/ODEs/rk2_Euler.png -------------------------------------------------------------------------------- /content/basics/ODEs/rk2_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/ODEs/rk2_final.png -------------------------------------------------------------------------------- /content/basics/ODEs/rk2_halfEuler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/ODEs/rk2_halfEuler.png -------------------------------------------------------------------------------- /content/basics/ODEs/rk4_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/ODEs/rk4_final.png -------------------------------------------------------------------------------- /content/basics/ODEs/rk4_k1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/ODEs/rk4_k1.png -------------------------------------------------------------------------------- /content/basics/ODEs/rk4_k2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/ODEs/rk4_k2.png -------------------------------------------------------------------------------- /content/basics/ODEs/rk4_k3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/ODEs/rk4_k3.png -------------------------------------------------------------------------------- /content/basics/ODEs/rk4_k4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/ODEs/rk4_k4.png -------------------------------------------------------------------------------- /content/basics/diff-int/compound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/diff-int/compound.png -------------------------------------------------------------------------------- /content/basics/diff-int/compound2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/diff-int/compound2.png -------------------------------------------------------------------------------- /content/basics/diff-int/derivs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/diff-int/derivs.png -------------------------------------------------------------------------------- /content/basics/diff-int/diff-int.md: -------------------------------------------------------------------------------- 1 | Differentiation and Integration 2 | =============================== 3 | 4 | We will focus on the aspects of differentiation and integration that will 5 | be needed for us to understand integration of ODEs and PDEs later. 6 | 7 | -------------------------------------------------------------------------------- /content/basics/diff-int/differentiation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "tags": [ 8 | "hide-cell" 9 | ] 10 | }, 11 | "outputs": [], 12 | "source": [ 13 | "import numpy as np" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": {}, 19 | "source": [ 20 | "# Differentiation" 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "metadata": {}, 26 | "source": [ 27 | "There are two situations where we can imagine needing to compute a derivative:\n", 28 | "\n", 29 | "1. We have an analytic function, $f(x)$, and we want to create a\n", 30 | "numerical approximation to its derivative\n", 31 | "\n", 32 | "2. We have a function $f(x)$ defined only at a finite set of (possibly regularly spaced) points, and we want to use that discrete data to estimate the derivative" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "For the first case, it is usually best to take the analytic derivative. In the previous notebook however, we did look at the effect of roundoff on computing a derivative.\n", 40 | "\n", 41 | "We'll focus on the second case here." 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "## First order approximations\n", 49 | "Consider a set of points labeled with an index $i$, with the physical spacing between them denoted $\\Delta x$.\n", 50 | "\n", 51 | "![discrete data](fd_grid.png)\n", 52 | "\n", 53 | "We'll label the function value at $x_i$ as $f_i$, e.g., $f_i = f(x_i)$.\n", 54 | "\n", 55 | "We can use the result of the Taylor expansion we previously derived to write the derivative as:\n", 56 | "\n", 57 | "$$\\left . \\frac{d f}{dx} \\right |_i = \\frac{f_{i+1} - f_i}{\\Delta x} + \\mathcal{O}(\\Delta x)$$\n", 58 | "\n", 59 | "where $f_{i+1} = f(x_{i+1})$ is the data we have at the point $i+1$. \n", 60 | "\n", 61 | "As $\\Delta x \\rightarrow 0$, this approaches the definition of the derivative from calculus. However, we are not free to choose $\\Delta x$—it is a property of the discrete set of points we are given.\n", 62 | "\n", 63 | "Note: we could alternately have used the point to the right of $i$:\n", 64 | "\n", 65 | "$$\\left . \\frac{d f}{dx} \\right |_i = \\frac{f_{i} - f_{i-1}}{\\Delta x} + \\mathcal{O}(\\Delta x)$$\n" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "metadata": {}, 71 | "source": [ 72 | "## Second order approximation\n", 73 | "\n", 74 | "Looking at the Taylor expansion of $f_{i+1} = f(x_{i+1}) = f(x_i + \\Delta x)$, we see\n", 75 | "\n", 76 | "$$f_{i+1} = f_i + \\Delta x \\left .\\frac{df}{dx} \\right |_i + \\frac{1}{2} \\Delta x^2 \\left . \\frac{d^2f}{dx^2} \\right |_i + \\mathcal{O}(\\Delta x^3)$$\n", 77 | "\n", 78 | "likewise:\n", 79 | "\n", 80 | "$$f_{i-1} = f_i - \\Delta x \\left .\\frac{df}{dx} \\right |_i + \\frac{1}{2} \\Delta x^2 \\left . \\frac{d^2f}{dx^2} \\right |_i + \\mathcal{O}(\\Delta x^3)$$\n", 81 | "\n", 82 | "subtracting these two expressions give:\n", 83 | "\n", 84 | "$$f_{i+1} - f_{i-1} = 2 \\Delta x \\left . \\frac{df}{dx} \\right |_i + \\mathcal{O}(\\Delta x^3)$$\n", 85 | "\n", 86 | "or\n", 87 | "\n", 88 | "$$\\left . \\frac{df}{dx} \\right |_i = \\frac{f_{i+1} - f_{i-1}}{2 \\Delta x} +\\mathcal{O}(\\Delta x^2)$$\n", 89 | "\n", 90 | "This is called the *centered-difference* approximation to the first derivative. It is second order accurate." 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "Graphically, these different approximations appear as:\n", 98 | "![](derivs.png)" 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "metadata": {}, 104 | "source": [ 105 | "
\n", 106 | "\n", 107 | "**Exercise:**\n", 108 | "\n", 109 | "Consider the function $f(x) = \\sin(x)$. The code below defines 10 equally spaced points and defines $f(x)$ at each point. \n", 110 | " \n", 111 | "Use this discrete data to estimate the derivative at `x[3]` and compute the error with respect to the analytic value.\n", 112 | "
" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": 2, 118 | "metadata": {}, 119 | "outputs": [], 120 | "source": [ 121 | "x = np.linspace(0, np.pi, 10, endpoint=False)\n", 122 | "f = np.sin(x)" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": 4, 128 | "metadata": { 129 | "tags": [ 130 | "hide-cell" 131 | ] 132 | }, 133 | "outputs": [ 134 | { 135 | "name": "stdout", 136 | "output_type": "stream", 137 | "text": [ 138 | "left = 0.7042025064251414, right = 0.4521258405602084, centered = 0.5781641734926749\n", 139 | "analytic value = 0.5877852522924731\n" 140 | ] 141 | } 142 | ], 143 | "source": [ 144 | "# first we'll write functions to evaluate each of the derivative approximations\n", 145 | "# at a given index idx\n", 146 | "\n", 147 | "def left_sided_deriv(x, f, idx):\n", 148 | " \"\"\"return the left-sided derivative at x[idx]\"\"\"\n", 149 | " return (f[idx] - f[idx-1]) / (x[idx] - x[idx-1])\n", 150 | "\n", 151 | "def right_sided_deriv(x, f, idx):\n", 152 | " \"\"\"return the right-sided derivative at x[idx]\"\"\"\n", 153 | " return (f[idx+1] - f[idx]) / (x[idx+1] - x[idx])\n", 154 | "\n", 155 | "def centered_deriv(x, f, idx):\n", 156 | " \"\"\"return the left-sided derivative at x[idx]\"\"\"\n", 157 | " return (f[idx+1] - f[idx-1]) / (x[idx+1] - x[idx-1])\n", 158 | "\n", 159 | "# always use x[ival] for the location of the derivative\n", 160 | "ival = 3\n", 161 | "\n", 162 | "dl = left_sided_deriv(x, f, ival)\n", 163 | "dr = right_sided_deriv(x, f, ival)\n", 164 | "dc = centered_deriv(x, f, ival)\n", 165 | "\n", 166 | "print(f\"left = {dl}, right = {dr}, centered = {dc}\")\n", 167 | "print(f\"analytic value = {np.cos(x[ival])}\")" 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": null, 173 | "metadata": { 174 | "tags": [ 175 | "hide-cell" 176 | ] 177 | }, 178 | "outputs": [], 179 | "source": [] 180 | } 181 | ], 182 | "metadata": { 183 | "kernelspec": { 184 | "display_name": "Python 3", 185 | "language": "python", 186 | "name": "python3" 187 | }, 188 | "language_info": { 189 | "codemirror_mode": { 190 | "name": "ipython", 191 | "version": 3 192 | }, 193 | "file_extension": ".py", 194 | "mimetype": "text/x-python", 195 | "name": "python", 196 | "nbconvert_exporter": "python", 197 | "pygments_lexer": "ipython3", 198 | "version": "3.9.5" 199 | } 200 | }, 201 | "nbformat": 4, 202 | "nbformat_minor": 4 203 | } 204 | -------------------------------------------------------------------------------- /content/basics/diff-int/fd_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/diff-int/fd_grid.png -------------------------------------------------------------------------------- /content/basics/diff-int/rectangle_N1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/diff-int/rectangle_N1.png -------------------------------------------------------------------------------- /content/basics/diff-int/rectangle_N6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/diff-int/rectangle_N6.png -------------------------------------------------------------------------------- /content/basics/diff-int/simpsons_N1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/diff-int/simpsons_N1.png -------------------------------------------------------------------------------- /content/basics/diff-int/simpsons_N6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/diff-int/simpsons_N6.png -------------------------------------------------------------------------------- /content/basics/diff-int/trapezoid_N1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/diff-int/trapezoid_N1.png -------------------------------------------------------------------------------- /content/basics/diff-int/trapezoid_N6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/diff-int/trapezoid_N6.png -------------------------------------------------------------------------------- /content/basics/floating-point/floating-point.md: -------------------------------------------------------------------------------- 1 | Floating Point and Numerical Error 2 | ================================== 3 | 4 | In numerical calculations, we need to worry about several sources of 5 | error including floating point and truncation error. These notebooks 6 | demonstrate some of the basic properties of this error. 7 | 8 | The standard paper for describing floating point error is 9 | *What every computer scientist should know about floating-point arithmetic* by 10 | D. Goldberg (https://dl.acm.org/doi/10.1145/103162.103163) 11 | -------------------------------------------------------------------------------- /content/basics/linear-algebra/la-basics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": { 7 | "tags": [ 8 | "hide-cell" 9 | ] 10 | }, 11 | "outputs": [], 12 | "source": [ 13 | "import numpy as np\n", 14 | "import matplotlib.pyplot as plt" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "# Linear Algebra\n", 22 | "\n", 23 | "There are a lot of situations that we'll see lead to needing to solve a linear system of the form:\n", 24 | "\n", 25 | "$${\\bf A} {\\bf x} = {\\bf b}$$\n", 26 | "\n", 27 | "where ${\\bf A}$ is a matrix, ${\\bf b}$ is a known vector, and ${\\bf x}$ is the unknown vector we want to find.\n", 28 | "\n", 29 | "We'll start by looking at general methods, but there are lots of specialized ways to solve a linear system depending on the properties of the matrix ${\\bf A}$.\n", 30 | "\n", 31 | "Numerical linear algebra can tricky and its easy to do things wrong. Here's a nice summary: [Seven Sins of Numerical Linear Algebra](https://nhigham.com/2022/10/11/seven-sins-of-numerical-linear-algebra/)" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "## Review of Matrices\n", 39 | "\n", 40 | "Before we look at methods for solving linear systems, we'll start with reviewing some of the methods we learned in the past." 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "metadata": {}, 46 | "source": [ 47 | "### Matrix-vector multiplication\n", 48 | "\n", 49 | "Consider multiplying matrix ${\\bf A}$ and vector ${\\bf x}$ as ${\\bf A}{\\bf x} = {\\bf b}$. Take:\n", 50 | "\n", 51 | "* ${\\bf A}$ to be an $m\\times n$ matrix\n", 52 | "* ${\\bf x}$ to be an $n\\times 1$ (column) vector\n", 53 | "* ${\\bf b}$ will be an $m\\times 1$ (column) vector\n", 54 | "\n", 55 | "The multiplication looks like:\n", 56 | "\n", 57 | "$$b_i = (A x)_i = \\sum_{j=1}^M A_{ij} x_j$$" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": {}, 63 | "source": [ 64 | "Python has it's own matrix multiplication operator that works with NumPy arrays, so an example of this operation would be" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 6, 70 | "metadata": {}, 71 | "outputs": [ 72 | { 73 | "data": { 74 | "text/plain": [ 75 | "array([[ 0, 1, 2],\n", 76 | " [ 3, 4, 5],\n", 77 | " [ 6, 7, 8],\n", 78 | " [ 9, 10, 11]])" 79 | ] 80 | }, 81 | "execution_count": 6, 82 | "metadata": {}, 83 | "output_type": "execute_result" 84 | } 85 | ], 86 | "source": [ 87 | "A = np.arange(12).reshape(4, 3)\n", 88 | "A" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": 7, 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "x = np.array([1, -1, 1])" 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": 8, 103 | "metadata": {}, 104 | "outputs": [ 105 | { 106 | "data": { 107 | "text/plain": [ 108 | "array([ 1, 4, 7, 10])" 109 | ] 110 | }, 111 | "execution_count": 8, 112 | "metadata": {}, 113 | "output_type": "execute_result" 114 | } 115 | ], 116 | "source": [ 117 | "A @ x" 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "metadata": {}, 123 | "source": [ 124 | "Here we see `A` is a 4x3 matrix and x is a vector of 3 elements, so the result is a 4 element vector.\n", 125 | "\n", 126 | "Instead of using `@`, we can explicitly write out the multiplication ourselves to see the operations:" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": 11, 132 | "metadata": {}, 133 | "outputs": [], 134 | "source": [ 135 | "b = np.zeros(A.shape[0])\n", 136 | "for i in range(A.shape[0]): # loop over rows\n", 137 | " for j in range(A.shape[1]): # loop over columns\n", 138 | " b[i] += A[i, j] * x[j]" 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": 12, 144 | "metadata": {}, 145 | "outputs": [ 146 | { 147 | "data": { 148 | "text/plain": [ 149 | "array([ 1., 4., 7., 10.])" 150 | ] 151 | }, 152 | "execution_count": 12, 153 | "metadata": {}, 154 | "output_type": "execute_result" 155 | } 156 | ], 157 | "source": [ 158 | "b" 159 | ] 160 | }, 161 | { 162 | "cell_type": "markdown", 163 | "metadata": {}, 164 | "source": [ 165 | "We see that there are 2 loops in our implementation. This means that for a square matrix of size NxN, the number of multiplications scales as $\\mathcal{O}(N^2)$" 166 | ] 167 | }, 168 | { 169 | "cell_type": "markdown", 170 | "metadata": {}, 171 | "source": [ 172 | "### Matrix-matrix multiplication\n", 173 | "\n", 174 | "Now we can consider the case of multiplying 2 matrices ${\\bf C} = {\\bf A}{\\bf B}$. Now we essentially do a dot product of each row in $A$ with each column of $B$. This looks like:\n", 175 | "\n", 176 | "$$C_{ij} = (AB)_{ij} = \\sum_{k=1}^N A_{ik} B_{kj}$$\n", 177 | "\n", 178 | "Again, we can use the python `@` operator:" 179 | ] 180 | }, 181 | { 182 | "cell_type": "code", 183 | "execution_count": 14, 184 | "metadata": {}, 185 | "outputs": [], 186 | "source": [ 187 | "B = np.array([[1, -1, 2, 3, 0],\n", 188 | " [2, -2, 1, 5, -7],\n", 189 | " [-1, 3, 4, -8, 3]])" 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": 15, 195 | "metadata": {}, 196 | "outputs": [ 197 | { 198 | "data": { 199 | "text/plain": [ 200 | "array([[ 0, 4, 9, -11, -1],\n", 201 | " [ 6, 4, 30, -11, -13],\n", 202 | " [ 12, 4, 51, -11, -25],\n", 203 | " [ 18, 4, 72, -11, -37]])" 204 | ] 205 | }, 206 | "execution_count": 15, 207 | "metadata": {}, 208 | "output_type": "execute_result" 209 | } 210 | ], 211 | "source": [ 212 | "A @ B" 213 | ] 214 | }, 215 | { 216 | "cell_type": "markdown", 217 | "metadata": {}, 218 | "source": [ 219 | "Now we see that `A` is a 4x3 matrix and `B` is a 3x5 matrix, so the result is a 4x5 matrix. \n", 220 | "\n", 221 | "Again, we can explicitly write this ourselves to see the details:" 222 | ] 223 | }, 224 | { 225 | "cell_type": "code", 226 | "execution_count": 22, 227 | "metadata": {}, 228 | "outputs": [], 229 | "source": [ 230 | "assert A.shape[1] == B.shape[0]\n", 231 | "C = np.zeros((A.shape[0], B.shape[1]))\n", 232 | "for i in range(C.shape[0]): # loop over rows of C\n", 233 | " for j in range(C.shape[1]): # loop over columns of C\n", 234 | " for k in range(A.shape[1]): # filling element C[i,j] via inner product\n", 235 | " C[i, j] += A[i, k] * B[k, j]" 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": 23, 241 | "metadata": {}, 242 | "outputs": [ 243 | { 244 | "data": { 245 | "text/plain": [ 246 | "array([[ 0., 4., 9., -11., -1.],\n", 247 | " [ 6., 4., 30., -11., -13.],\n", 248 | " [ 12., 4., 51., -11., -25.],\n", 249 | " [ 18., 4., 72., -11., -37.]])" 250 | ] 251 | }, 252 | "execution_count": 23, 253 | "metadata": {}, 254 | "output_type": "execute_result" 255 | } 256 | ], 257 | "source": [ 258 | "C" 259 | ] 260 | }, 261 | { 262 | "cell_type": "markdown", 263 | "metadata": {}, 264 | "source": [ 265 | "Now we see that there are 3 loops, which means that for square matrices, matrix multiplication scales like $\\mathcal{O}(N^3)$.\n", 266 | "\n", 267 | "We note however, that if the matrix has some symmetries, it is possible to reduce the cost of the multiplication. Making use of repeated work can also reduce the cost in the general case (see \n", 268 | "https://en.wikipedia.org/wiki/Matrix_multiplication#Computational_complexity)" 269 | ] 270 | }, 271 | { 272 | "cell_type": "markdown", 273 | "metadata": { 274 | "tags": [] 275 | }, 276 | "source": [ 277 | "### Determinant\n", 278 | "\n", 279 | "A determinant operates on a square matrix and returns a scalar that characterizes the matrix. For our purposes, the most important property of a determinant is that a linear system, ${\\bf A}{\\bf x} = {\\bf b}$ is solvable only if the determinant of ${\\bf A}$ is nonzero.\n", 280 | "\n", 281 | "Some common ways to note the determinant operation are:\n", 282 | "\n", 283 | " * $|{\\bf A}|$\n", 284 | "\n", 285 | " * $\\mathrm{det}({\\bf A})$\n", 286 | " \n", 287 | "Computing the determinant for small matrices is straightforward:\n", 288 | "\n", 289 | "$$\\left | \\begin{array}{cc} a & b \\\\\n", 290 | " c & d \\end{array} \\right | = ad - bc$$\n", 291 | " \n", 292 | "$$\\left | \\begin{array}{ccc} a & b & c \\\\\n", 293 | " d & e & f \\\\\n", 294 | " g & h & i \\end{array} \\right | =\n", 295 | " a \\left | \\begin{array}{cc} e & f \\\\ h & i \\end{array} \\right | -\n", 296 | " b \\left | \\begin{array}{cc} d & f \\\\ g & i \\end{array} \\right | +\n", 297 | " c \\left | \\begin{array}{cc} d & e \\\\ g & h \\end{array} \\right |$$\n", 298 | "\n", 299 | "where the 3x3 case shown above is an example of [Laplace expansion](https://en.wikipedia.org/wiki/Laplace_expansion).\n", 300 | "\n", 301 | "While this can be extended to larger matrices, it becomes computationally expensive, and we will see a more natural way of getting the determinant as part of solving the linear system ${\\bf A}{\\bf x} = {\\bf b}$." 302 | ] 303 | }, 304 | { 305 | "cell_type": "markdown", 306 | "metadata": {}, 307 | "source": [ 308 | "### Inverse\n", 309 | "\n", 310 | "For a matrix ${\\bf A}$, the inverse, ${\\bf A}^{-1}$ is defined such that\n", 311 | "\n", 312 | "$${\\bf A}{\\bf A}^{-1} = {\\bf A}^{-1} {\\bf A} = {\\bf I}$$\n", 313 | "\n", 314 | "From this definition, we might thing that the way to solve the linear system ${\\bf A}{\\bf x} = {\\bf b}$ is to first compute the inverse of ${\\bf A}$ and then do:\n", 315 | "\n", 316 | "$${\\bf x} = {\\bf A}^{-1} {\\bf b}$$\n", 317 | "\n", 318 | "However, computing the inverse of a matrix is very computationally expensive, and we'll see that there are easier ways to directly solve the linear system." 319 | ] 320 | } 321 | ], 322 | "metadata": { 323 | "kernelspec": { 324 | "display_name": "Python 3 (ipykernel)", 325 | "language": "python", 326 | "name": "python3" 327 | }, 328 | "language_info": { 329 | "codemirror_mode": { 330 | "name": "ipython", 331 | "version": 3 332 | }, 333 | "file_extension": ".py", 334 | "mimetype": "text/x-python", 335 | "name": "python", 336 | "nbconvert_exporter": "python", 337 | "pygments_lexer": "ipython3", 338 | "version": "3.10.8" 339 | } 340 | }, 341 | "nbformat": 4, 342 | "nbformat_minor": 4 343 | } 344 | -------------------------------------------------------------------------------- /content/basics/linear-algebra/la-overview.md: -------------------------------------------------------------------------------- 1 | Linear Algebra 2 | ============== 3 | 4 | Solving a linear system of the form $Ax = b$ is at the heart of many 5 | numerical applications. 6 | -------------------------------------------------------------------------------- /content/basics/roots/newton_00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/roots/newton_00.png -------------------------------------------------------------------------------- /content/basics/roots/newton_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/roots/newton_01.png -------------------------------------------------------------------------------- /content/basics/roots/newton_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/basics/roots/newton_02.png -------------------------------------------------------------------------------- /content/elliptic_multigrid/intro/elliptic-problems.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "tags": [ 8 | "hide-cell" 9 | ] 10 | }, 11 | "outputs": [], 12 | "source": [ 13 | "import numpy as np\n", 14 | "import matplotlib.pyplot as plt" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "# Poisson problem\n", 22 | "\n", 23 | "Consider the Poisson equation\n", 24 | "\n", 25 | "$$\\nabla^2 \\phi = f$$\n", 26 | "\n", 27 | "This is a second-order elliptic equation, and therefore requires 2 boundary conditions.\n", 28 | "\n", 29 | "There is no time-dependence in this equation. The potential $\\phi$ responds instantaneously to the source $f$ and the boundary conditions." 30 | ] 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "metadata": {}, 35 | "source": [ 36 | "Consider the one-dimensional Poisson equation:\n", 37 | "\n", 38 | "$$\\phi^{\\prime\\prime} = f$$\n", 39 | "\n", 40 | "on the domain $[a, b]$.\n", 41 | "\n", 42 | "We can supply boundary conditions as\n", 43 | "\n", 44 | "* Dirichlet: $\\phi(a) = A$\n", 45 | "* Neumann: $\\phi^\\prime(a) = C$\n", 46 | "\n", 47 | "or a mix of the two. If the values are set to 0, we call the conditions homogeneous, otherwise we call them inhomogeneous." 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "
\n", 55 | " \n", 56 | "Not any set of boundary conditions is allowed.\n", 57 | "Consider $f = 0$, so our Poisson equation (is the Laplace equation):\n", 58 | "\n", 59 | "$$\\phi^{\\prime\\prime} = 0$$\n", 60 | "\n", 61 | "and the solution is just\n", 62 | "\n", 63 | "$$\\phi(x) = a x + b$$\n", 64 | "\n", 65 | "if we try to enforce different inhomogeneous Neumann boundary conditions on each end, then we get conflicting values for the slope—this is unsolvable.\n", 66 | " \n", 67 | "
" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "metadata": {}, 73 | "source": [ 74 | "To understand solvable boundary conditions for the general case, \n", 75 | "\n", 76 | "$$\\nabla^2 \\phi = f$$\n", 77 | "\n", 78 | "we start by integrating over the domain\n", 79 | "\n", 80 | "$$\\int_\\Omega \\nabla^2 \\phi d\\Omega = \\int_{\\partial \\Omega} \\nabla \\phi \\cdot {\\bf n} dS = \\int_\\Omega f d\\Omega$$\n", 81 | "\n", 82 | "If we have homogeneous Neumann BCs on all sides, $\\nabla \\phi \\cdot {\\bf n} = 0$, then the source, $f$, must satisfy\n", 83 | "\n", 84 | "$$\\int_\\Omega f d\\Omega = 0$$\n", 85 | "\n", 86 | "The same condition will apply, if the boundary conditions are periodic." 87 | ] 88 | } 89 | ], 90 | "metadata": { 91 | "kernelspec": { 92 | "display_name": "Python 3 (ipykernel)", 93 | "language": "python", 94 | "name": "python3" 95 | }, 96 | "language_info": { 97 | "codemirror_mode": { 98 | "name": "ipython", 99 | "version": 3 100 | }, 101 | "file_extension": ".py", 102 | "mimetype": "text/x-python", 103 | "name": "python", 104 | "nbconvert_exporter": "python", 105 | "pygments_lexer": "ipython3", 106 | "version": "3.9.5" 107 | } 108 | }, 109 | "nbformat": 4, 110 | "nbformat_minor": 4 111 | } 112 | -------------------------------------------------------------------------------- /content/elliptic_multigrid/multigrid/fvrestrict.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/elliptic_multigrid/multigrid/fvrestrict.png -------------------------------------------------------------------------------- /content/elliptic_multigrid/multigrid/grid.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | class Grid: 4 | def __init__(self, nx, ng=1, xmin=0, xmax=1, 5 | bc_left_type="dirichlet", bc_left_val=0.0, 6 | bc_right_type="dirichlet", bc_right_val=0.0): 7 | 8 | self.xmin = xmin 9 | self.xmax = xmax 10 | self.ng = ng 11 | self.nx = nx 12 | 13 | self.bc_left_type = bc_left_type 14 | self.bc_left_val = bc_left_val 15 | 16 | self.bc_right_type = bc_right_type 17 | self.bc_right_val = bc_right_val 18 | 19 | # python is zero-based. Make easy intergers to know where the 20 | # real data lives 21 | self.ilo = ng 22 | self.ihi = ng+nx-1 23 | 24 | # physical coords -- cell-centered 25 | self.dx = (xmax - xmin)/(nx) 26 | self.x = xmin + (np.arange(nx+2*ng)-ng+0.5)*self.dx 27 | 28 | # storage for the solution 29 | self.v = self.scratch_array() 30 | self.f = self.scratch_array() 31 | self.r = self.scratch_array() 32 | 33 | def scratch_array(self): 34 | """return a scratch array dimensioned for our grid """ 35 | return np.zeros((self.nx+2*self.ng), dtype=np.float64) 36 | 37 | def norm(self, e): 38 | """compute the L2 norm of e that lives on our grid""" 39 | return np.sqrt(self.dx * np.sum(e[self.ilo:self.ihi+1]**2)) 40 | 41 | def compute_residual(self): 42 | """compute and store the residual""" 43 | self.r[self.ilo:self.ihi+1] = self.f[self.ilo:self.ihi+1] - \ 44 | (self.v[self.ilo+1:self.ihi+2] - 45 | 2 * self.v[self.ilo:self.ihi+1] + 46 | self.v[self.ilo-1:self.ihi]) / self.dx**2 47 | 48 | def residual_norm(self): 49 | """compute the residual norm""" 50 | self.compute_residual() 51 | return self.norm(self.r) 52 | 53 | def source_norm(self): 54 | """compute the source norm""" 55 | return self.norm(self.f) 56 | 57 | def fill_bcs(self): 58 | """fill the boundary conditions on phi""" 59 | 60 | # we only deal with a single ghost cell here 61 | 62 | # left 63 | if self.bc_left_type.lower() == "dirichlet": 64 | self.v[self.ilo-1] = 2 * self.bc_left_val - self.v[self.ilo] 65 | elif self.bc_left_type.lower() == "neumann": 66 | self.v[self.ilo-1] = self.v[self.ilo] - self.dx * self.bc_left_val 67 | else: 68 | raise ValueError("invalid bc_left_type") 69 | 70 | # right 71 | if self.bc_right_type.lower() == "dirichlet": 72 | self.v[self.ihi+1] = 2 * self.bc_right_val - self.v[self.ihi] 73 | elif self.bc_right_type.lower() == "neumann": 74 | self.v[self.ihi+1] = self.v[self.ihi] - self.dx * self.bc_right_val 75 | else: 76 | raise ValueError("invalid bc_right_type") 77 | 78 | def restrict(self, comp="v"): 79 | """restrict the data to a coarser (by 2x) grid""" 80 | 81 | # create a coarse array 82 | ng = self.ng 83 | nc = self.nx//2 84 | 85 | ilo_c = ng 86 | ihi_c = ng + nc - 1 87 | 88 | coarse_data = np.zeros((nc + 2*ng), dtype=np.float64) 89 | 90 | if comp == "v": 91 | fine_data = self.v 92 | elif comp == "f": 93 | fine_data = self.f 94 | elif comp == "r": 95 | fine_data = self.r 96 | else: 97 | raise ValueError("invalid component") 98 | 99 | coarse_data[ilo_c:ihi_c+1] = 0.5 * (fine_data[self.ilo:self.ihi+1:2] + 100 | fine_data[self.ilo+1:self.ihi+1:2]) 101 | 102 | return coarse_data 103 | 104 | def prolong(self, comp="v"): 105 | """prolong the data in the current (coarse) grid to a finer (factor 106 | of 2 finer) grid using linear reconstruction. 107 | 108 | """ 109 | 110 | if comp == "v": 111 | coarse_data = self.v 112 | elif comp == "f": 113 | coarse_data = self.f 114 | elif comp == "r": 115 | coarse_data = self.r 116 | else: 117 | raise ValueError("invalid component") 118 | 119 | 120 | # allocate an array for the coarsely gridded data 121 | ng = self.ng 122 | nf = self.nx * 2 123 | 124 | fine_data = np.zeros((nf + 2*ng), dtype=np.float64) 125 | 126 | ilo_f = ng 127 | ihi_f = ng + nf - 1 128 | 129 | # slopes for the coarse data 130 | m_x = self.scratch_array() 131 | m_x[self.ilo:self.ihi+1] = 0.5 * (coarse_data[self.ilo+1:self.ihi+2] - 132 | coarse_data[self.ilo-1:self.ihi]) 133 | 134 | # fill the '1' children 135 | fine_data[ilo_f:ihi_f+1:2] = \ 136 | coarse_data[self.ilo:self.ihi+1] - 0.25 * m_x[self.ilo:self.ihi+1] 137 | 138 | # fill the '2' children 139 | fine_data[ilo_f+1:ihi_f+1:2] = \ 140 | coarse_data[self.ilo:self.ihi+1] + 0.25 * m_x[self.ilo:self.ihi+1] 141 | 142 | return fine_data 143 | -------------------------------------------------------------------------------- /content/elliptic_multigrid/multigrid/mg_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | 5 | an example of using the multigrid class to solve Laplace's equation. Here, we 6 | solve 7 | 8 | u_xx = sin(x) 9 | u = 0 on the boundary [0,1] 10 | 11 | The analytic solution is u(x) = -sin(x) + x sin(1) 12 | 13 | """ 14 | 15 | import numpy as np 16 | import multigrid 17 | import matplotlib.pyplot as plt 18 | 19 | 20 | def true(x): 21 | # the analytic solution 22 | return -np.sin(x) + x*np.sin(1.0) 23 | 24 | def f(x): 25 | # the righthand side 26 | return np.sin(x) 27 | 28 | # test the multigrid solver 29 | nx = 128 30 | 31 | # create the multigrid object 32 | a = multigrid.Multigrid(nx, 33 | bc_left_type="dirichlet", bc_right_type="dirichlet", 34 | verbose=1, true_function=true) 35 | 36 | # initialize the solution to 0 37 | a.init_solution() 38 | 39 | # initialize the RHS using the function f 40 | a.init_rhs(f(a.x)) 41 | 42 | # solve to a relative tolerance of 1.e-11 43 | elist, rlist = a.solve(rtol=1.e-11) 44 | 45 | ncycle = np.arange(len(elist)) + 1 46 | 47 | 48 | # get the solution 49 | v = a.get_solution() 50 | 51 | # compute the error from the analytic solution 52 | e = v - true(a.x) 53 | 54 | print(f"L2 error from true solution = {a.soln_grid.norm(e)}") 55 | print(f"rel. err from previous cycle = {a.relative_error}") 56 | print(f"num. cycles = {a.num_cycles}") 57 | 58 | fig = plt.figure() 59 | ax = fig.add_subplot(111) 60 | 61 | ax.plot(a.x[a.ilo:a.ihi+1], true(a.x[a.ilo:a.ihi+1]), color="0.5", ls=":", label="analytic solution") 62 | ax.scatter(a.x[a.ilo:a.ihi+1], v[a.ilo:a.ihi+1], color="C1", label="multigrid solution", marker="x") 63 | ax.set_xlabel("x") 64 | ax.set_ylabel(r"$\phi$") 65 | 66 | fig.set_size_inches(8.0, 8.0) 67 | 68 | fig.savefig("phi_analytic.png") 69 | 70 | 71 | fig = plt.figure() 72 | ax = fig.add_subplot(111) 73 | 74 | ax.plot(ncycle, elist, color="k", label=r"$\| e\|$") 75 | ax.plot(ncycle, rlist, "--", color="k", label=r"$\| r\|$") 76 | 77 | ax.set_xlabel("# of V-cycles") 78 | ax.set_ylabel("L2 norm of error") 79 | 80 | ax.set_yscale('log') 81 | 82 | fig.set_size_inches(8.0,6.0) 83 | 84 | ax.legend(frameon=False) 85 | 86 | fig.tight_layout() 87 | 88 | fig.savefig("mg_error_vs_cycle.png") 89 | 90 | -------------------------------------------------------------------------------- /content/elliptic_multigrid/multigrid/mgtower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/elliptic_multigrid/multigrid/mgtower.png -------------------------------------------------------------------------------- /content/elliptic_multigrid/multigrid/multigrid-restrict-prolong.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "import matplotlib.pyplot as plt" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": { 16 | "tags": [ 17 | "hide-cell" 18 | ] 19 | }, 20 | "source": [ 21 | "# Restriction and Prolongation" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | "We need a way to transfer data from the current grid to a coarser grid (*restriction*) or finer grid (*prolongation*).\n", 29 | "\n", 30 | "The procedure by which we do this depends on the type of grid we are using. We'll continue to consider a cell-centered finite-difference / finite-volume grid.\n", 31 | "\n", 32 | "Since the finite-volume method is conservative, we want to ensure that the operations that we do here maintain that conservation." 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "Consider the following 2 grids:\n", 40 | "\n", 41 | "![a coarse and fine grid](fvrestrict.png)\n", 42 | "\n", 43 | "The fine grid (top) is indexed with $i$ and the coarse grid (bottom) is indexing with $j$.\n", 44 | "\n", 45 | "Note that the grids are properly nested: two fine cells fit exactly into a single coarse cell.\n", 46 | "\n", 47 | "In general, the jump in resolution can be something other than 2, but we'll restrict ourselves to jumps by 2 here." 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "## Restriction" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": {}, 60 | "source": [ 61 | "Consider the case of bringing the data from the fine grid to the coarse grid. Since two fine cells are contained exactly in a single coarse cell, a conservative aveaging of the fine data down to the coarse grid would be:\n", 62 | "\n", 63 | "$$\\phi^c_j = \\frac{1}{2} \\left ( \\phi^f_i + \\phi^f_{i+1} \\right )$$\n", 64 | "\n", 65 | "This is also second-order accurate at the coarse cell cell-center." 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "metadata": {}, 71 | "source": [ 72 | "## Prolongation\n", 73 | "\n", 74 | "To initialize the two fine cells from a coarse cell, we could do simple direct injection:\n", 75 | "\n", 76 | "$$\\phi_i^f = \\phi_j^c$$\n", 77 | "\n", 78 | "$$\\phi_{i+1}^f = \\phi_j^c$$\n", 79 | "\n", 80 | "This is only first order accurate. A second-order accurate method of initializing the fine data starts with reconstructing the coarse data as a line:\n", 81 | "\n", 82 | "$$\\phi^{\\mathrm{coarse}}(x) = m_x (x - x_j) + \\phi^c_j$$\n", 83 | "\n", 84 | "with\n", 85 | "\n", 86 | "$$m_x = \\frac{\\phi^c_{j+1} - \\phi^c_{j-1}}{2\\Delta x}$$\n", 87 | "\n", 88 | "Then we integrate this line over the two fine cells to find their values:\n", 89 | "\n", 90 | "$$\\phi^f_j = \\phi^c_i - \\frac{\\Delta x}{4} m_x$$\n", 91 | "\n", 92 | "$$\\phi^f_{j+1} = \\phi^c_i + \\frac{\\Delta x}{4} m_x$$" 93 | ] 94 | }, 95 | { 96 | "cell_type": "markdown", 97 | "metadata": {}, 98 | "source": [ 99 | "## Implementation\n", 100 | "\n", 101 | "Let's create a basic grid class that implements this. We'll assume homogeneous Dirichlet boundary conditions." 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": 2, 107 | "metadata": {}, 108 | "outputs": [], 109 | "source": [ 110 | "class Grid:\n", 111 | " def __init__(self, nx, ng=1, xmin=0, xmax=1):\n", 112 | "\n", 113 | " self.xmin = xmin\n", 114 | " self.xmax = xmax\n", 115 | " self.ng = ng\n", 116 | " self.nx = nx\n", 117 | "\n", 118 | " self.ilo = ng\n", 119 | " self.ihi = ng+nx-1\n", 120 | "\n", 121 | " # physical coords -- cell-centered\n", 122 | " self.dx = (xmax - xmin)/(nx)\n", 123 | " self.x = xmin + (np.arange(nx+2*ng)-ng+0.5)*self.dx\n", 124 | "\n", 125 | " # storage for the solution\n", 126 | " self.phi = self.scratch_array()\n", 127 | "\n", 128 | " def scratch_array(self):\n", 129 | " \"\"\"return a scratch array dimensioned for our grid \"\"\"\n", 130 | " return np.zeros((self.nx+2*self.ng), dtype=np.float64)\n", 131 | "\n", 132 | " def fill_bcs(self):\n", 133 | " \"\"\"fill the boundary conditions on phi -- we'll assume Dirichlet\"\"\"\n", 134 | "\n", 135 | " self.phi[self.ilo-1] = -self.phi[self.ilo]\n", 136 | " self.phi[self.ihi+1] = -self.phi[self.ihi]\n", 137 | "\n", 138 | " def restrict(self):\n", 139 | " \"\"\"conservative restriction\"\"\"\n", 140 | " \n", 141 | " # create a coarse array\n", 142 | " ng = self.ng\n", 143 | " nc = self.nx//2\n", 144 | "\n", 145 | " ilo_c = ng\n", 146 | " ihi_c = ng + nc - 1\n", 147 | "\n", 148 | " coarse_data = np.zeros((nc + 2*ng), dtype=np.float64)\n", 149 | "\n", 150 | " coarse_data[ilo_c:ihi_c+1] = 0.5 * (self.phi[self.ilo:self.ihi+1:2] +\n", 151 | " self.phi[self.ilo+1:self.ihi+1:2])\n", 152 | "\n", 153 | " return coarse_data\n", 154 | "\n", 155 | " def prolong(self):\n", 156 | " \"\"\"\n", 157 | " prolong the data in the current (coarse) grid to a finer\n", 158 | " (factor of 2 finer) grid using linear reconstruction.\n", 159 | " \"\"\"\n", 160 | "\n", 161 | " # allocate an array for the coarsely gridded data\n", 162 | " ng = self.ng\n", 163 | " nf = self.nx * 2\n", 164 | "\n", 165 | " fine_data = np.zeros((nf + 2*ng), dtype=np.float64)\n", 166 | "\n", 167 | " ilo_f = ng\n", 168 | " ihi_f = ng + nf - 1\n", 169 | "\n", 170 | " # slopes for the coarse data\n", 171 | " m_x = self.scratch_array()\n", 172 | " m_x[self.ilo:self.ihi+1] = 0.5 * (self.phi[self.ilo+1:self.ihi+2] -\n", 173 | " self.phi[self.ilo-1:self.ihi])\n", 174 | "\n", 175 | " # fill the left children\n", 176 | " fine_data[ilo_f:ihi_f+1:2] = \\\n", 177 | " self.phi[self.ilo:self.ihi+1] - 0.25 * m_x[self.ilo:self.ihi+1]\n", 178 | "\n", 179 | " # fill the right children\n", 180 | " fine_data[ilo_f+1:ihi_f+1:2] = \\\n", 181 | " self.phi[self.ilo:self.ihi+1] + 0.25 * m_x[self.ilo:self.ihi+1]\n", 182 | "\n", 183 | " return fine_data" 184 | ] 185 | }, 186 | { 187 | "cell_type": "markdown", 188 | "metadata": {}, 189 | "source": [ 190 | "Let's test it out. If we are conservative, we should be able to prolong and then restrict some initial data and get back what we started with.\n", 191 | "\n", 192 | "We'll create 2 grid objects: a coarse and fine one" 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": 3, 198 | "metadata": {}, 199 | "outputs": [], 200 | "source": [ 201 | "nx = 6\n", 202 | "coarse = Grid(nx)\n", 203 | "fine = Grid(2*nx)" 204 | ] 205 | }, 206 | { 207 | "cell_type": "markdown", 208 | "metadata": {}, 209 | "source": [ 210 | "Now initialize the coarse data to just a sequence of integers" 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": 4, 216 | "metadata": {}, 217 | "outputs": [ 218 | { 219 | "data": { 220 | "text/plain": [ 221 | "array([-1., 1., 2., 3., 4., 5., 6., -6.])" 222 | ] 223 | }, 224 | "execution_count": 4, 225 | "metadata": {}, 226 | "output_type": "execute_result" 227 | } 228 | ], 229 | "source": [ 230 | "coarse.phi[coarse.ilo:coarse.ihi+1] = np.arange(nx)+1\n", 231 | "coarse.fill_bcs()\n", 232 | "coarse.phi" 233 | ] 234 | }, 235 | { 236 | "cell_type": "markdown", 237 | "metadata": {}, 238 | "source": [ 239 | "Notice that because of this choice of initial data and the homogeneous Dirichlet BC on the right, the data is very steep there. That should not matter for this though—we should still be conservative.\n", 240 | "\n", 241 | "Let's initialize the fine data" 242 | ] 243 | }, 244 | { 245 | "cell_type": "code", 246 | "execution_count": 5, 247 | "metadata": {}, 248 | "outputs": [ 249 | { 250 | "data": { 251 | "text/plain": [ 252 | "array([-0.625, 0.625, 1.375, 1.75 , 2.25 , 2.75 , 3.25 , 3.75 ,\n", 253 | " 4.25 , 4.75 , 5.25 , 7.375, 4.625, -4.625])" 254 | ] 255 | }, 256 | "execution_count": 5, 257 | "metadata": {}, 258 | "output_type": "execute_result" 259 | } 260 | ], 261 | "source": [ 262 | "fine.phi[:] = coarse.prolong()\n", 263 | "fine.fill_bcs()\n", 264 | "fine.phi" 265 | ] 266 | }, 267 | { 268 | "cell_type": "markdown", 269 | "metadata": {}, 270 | "source": [ 271 | "Now let's restrict this and compare to the original coarse data" 272 | ] 273 | }, 274 | { 275 | "cell_type": "code", 276 | "execution_count": 6, 277 | "metadata": {}, 278 | "outputs": [ 279 | { 280 | "data": { 281 | "text/plain": [ 282 | "array([0., 1., 2., 3., 4., 5., 6., 0.])" 283 | ] 284 | }, 285 | "execution_count": 6, 286 | "metadata": {}, 287 | "output_type": "execute_result" 288 | } 289 | ], 290 | "source": [ 291 | "coarse_new = fine.restrict()\n", 292 | "coarse_new" 293 | ] 294 | }, 295 | { 296 | "cell_type": "markdown", 297 | "metadata": {}, 298 | "source": [ 299 | "Notice that this is the same as our original data—our prolongation and restriction operators are conservative." 300 | ] 301 | }, 302 | { 303 | "cell_type": "code", 304 | "execution_count": null, 305 | "metadata": {}, 306 | "outputs": [], 307 | "source": [] 308 | } 309 | ], 310 | "metadata": { 311 | "kernelspec": { 312 | "display_name": "Python 3 (ipykernel)", 313 | "language": "python", 314 | "name": "python3" 315 | }, 316 | "language_info": { 317 | "codemirror_mode": { 318 | "name": "ipython", 319 | "version": 3 320 | }, 321 | "file_extension": ".py", 322 | "mimetype": "text/x-python", 323 | "name": "python", 324 | "nbconvert_exporter": "python", 325 | "pygments_lexer": "ipython3", 326 | "version": "3.9.5" 327 | } 328 | }, 329 | "nbformat": 4, 330 | "nbformat_minor": 4 331 | } 332 | -------------------------------------------------------------------------------- /content/elliptic_multigrid/multigrid/multigrid.md: -------------------------------------------------------------------------------- 1 | Multigrid 2 | ========= 3 | 4 | Now we learn how to transfer data between grids at different 5 | resolution and use it to accelerate the convergence of our relaxation. 6 | 7 | A lot of inspiration came from the fantastic book *A Multigrid 8 | Tutorial* by W. L. Briggs. That text mostly works with node-centered 9 | grids, while we work with cell-centered / finite-volume grids. 10 | -------------------------------------------------------------------------------- /content/elliptic_multigrid/multigrid/multigrid.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import grid 3 | 4 | class Multigrid: 5 | """ 6 | The main multigrid class for cell-centered data. 7 | 8 | We require that nx be a power of 2 for simplicity 9 | """ 10 | 11 | def __init__(self, nx, xmin=0.0, xmax=1.0, 12 | bc_left_type="dirichlet", bc_right_type="dirichlet", 13 | nsmooth=10, nsmooth_bottom=50, 14 | verbose=0, 15 | true_function=None): 16 | 17 | self.nx = nx 18 | self.ng = 1 19 | 20 | self.xmin = xmin 21 | self.xmax = xmax 22 | 23 | self.nsmooth = nsmooth 24 | self.nsmooth_bottom = nsmooth_bottom 25 | 26 | self.max_cycles = 100 27 | 28 | self.verbose = verbose 29 | 30 | self.bc_left_type = bc_left_type 31 | self.bc_right_type = bc_right_type 32 | 33 | # a function that gives the analytic solution (if available) 34 | # for diagnostics only 35 | self.true_function = true_function 36 | 37 | # assume that self.nx = 2^(nlevels-1) 38 | # this defines nlevels such that we end exactly on a 2 zone grid 39 | self.nlevels = int(np.log(self.nx)/np.log(2.0)) 40 | 41 | # a multigrid object will be a list of grids 42 | self.grids = [] 43 | 44 | # create the grids. Here, self.grids[0] will be the coarsest 45 | # grid and self.grids[nlevel-1] will be the finest grid we 46 | # store the solution, v, the rhs, f. 47 | 48 | nx_t = 2 49 | for _ in range(self.nlevels): 50 | 51 | # add a grid for this level 52 | self.grids.append(grid.Grid(nx_t, xmin=self.xmin, xmax=self.xmax, 53 | bc_left_type=self.bc_left_type, 54 | bc_right_type=self.bc_right_type)) 55 | 56 | nx_t *= 2 57 | 58 | # provide coordinate and indexing information for the solution mesh 59 | self.soln_grid = self.grids[self.nlevels-1] 60 | 61 | self.ilo = self.soln_grid.ilo 62 | self.ihi = self.soln_grid.ihi 63 | 64 | self.x = self.soln_grid.x 65 | self.dx = self.soln_grid.dx 66 | 67 | # store the source norm 68 | self.source_norm = 0.0 69 | 70 | # after solving, keep track of the number of cycles taken and 71 | # the residual error (normalized to the source norm) 72 | 73 | self.num_cycles = 0 74 | self.residual_error = 1.e33 75 | 76 | def get_solution(self): 77 | return self.grids[self.nlevels-1].v.copy() 78 | 79 | def get_solution_object(self): 80 | return self.grids[self.nlevels-1] 81 | 82 | def init_solution(self): 83 | """ 84 | initialize the solution to the elliptic problem as zero 85 | """ 86 | self.soln_grid.v[:] = 0.0 87 | 88 | def init_rhs(self, data): 89 | self.soln_grid.f[:] = data.copy() 90 | 91 | # store the source norm 92 | self.source_norm = self.soln_grid.norm(self.soln_grid.f) 93 | 94 | def smooth(self, level, nsmooth): 95 | """ use Gauss-Seidel iterations to smooth """ 96 | 97 | myg = self.grids[level] 98 | 99 | myg.fill_bcs() 100 | 101 | # do red-black G-S 102 | for _ in range(nsmooth): 103 | 104 | myg.v[myg.ilo:myg.ihi+1:2] = 0.5 * ( 105 | -myg.dx * myg.dx * myg.f[myg.ilo:myg.ihi+1:2] + 106 | myg.v[myg.ilo+1:myg.ihi+2:2] + myg.v[myg.ilo-1:myg.ihi:2]) 107 | 108 | myg.fill_bcs() 109 | 110 | myg.v[myg.ilo+1:myg.ihi+1:2] = 0.5 * ( 111 | -myg.dx * myg.dx * myg.f[myg.ilo+1:myg.ihi+1:2] + 112 | myg.v[myg.ilo+2:myg.ihi+2:2] + myg.v[myg.ilo:myg.ihi:2]) 113 | 114 | myg.fill_bcs() 115 | 116 | def solve(self, rtol=1.e-11): 117 | """do V-cycles util the L2 norm of the relative solution difference is 118 | < rtol 119 | 120 | """ 121 | 122 | if self.verbose: 123 | print("source norm = ", self.source_norm) 124 | 125 | residual_error = 1.e33 126 | cycle = 1 127 | 128 | # diagnostics that are returned -- residual error norm and true 129 | # error norm (if possible) for each cycle 130 | rlist = [] 131 | elist = [] 132 | 133 | while residual_error > rtol and cycle <= self.max_cycles: 134 | 135 | # zero out the solution on all but the finest grid 136 | for level in range(self.nlevels-1): 137 | self.grids[level].v[:] = 0.0 138 | 139 | # descending part 140 | if self.verbose: 141 | print(f"<<< beginning V-cycle (cycle {cycle}) >>>\n") 142 | 143 | self.v_cycle(self.nlevels-1) 144 | 145 | # compute the residual error, relative to the source norm 146 | residual_error = self.soln_grid.residual_norm() 147 | if self.source_norm != 0.0: 148 | residual_error /= self.source_norm 149 | 150 | if residual_error < rtol: 151 | self.num_cycles = cycle 152 | self.residual_error = residual_error 153 | self.soln_grid.fill_bcs() 154 | 155 | if self.verbose: 156 | print(f"cycle {cycle}: residual err / source norm = {residual_error:11.6g}\n") 157 | 158 | rlist.append(residual_error) 159 | 160 | if self.true_function is not None: 161 | elist.append(self.soln_grid.norm(self.soln_grid.v - self.true_function(self.soln_grid.x))) 162 | 163 | cycle += 1 164 | 165 | return elist, rlist 166 | 167 | def v_cycle(self, level): 168 | 169 | if level > 0: 170 | fp = self.grids[level] 171 | cp = self.grids[level-1] 172 | 173 | if self.verbose: 174 | old_res_norm = fp.residual_norm() 175 | 176 | # smooth on the current level 177 | self.smooth(level, self.nsmooth) 178 | 179 | # compute the residual 180 | fp.compute_residual() 181 | 182 | if self.verbose: 183 | print(f" level = {level}, nx = {fp.nx:4}, residual change: {old_res_norm:11.6g} -> {fp.norm(fp.r):11.6g}") 184 | 185 | # restrict the residual down to the RHS of the coarser level 186 | cp.f[:] = fp.restrict("r") 187 | 188 | # solve the coarse problem 189 | self.v_cycle(level-1) 190 | 191 | # prolong the error up from the coarse grid 192 | fp.v += cp.prolong("v") 193 | 194 | if self.verbose: 195 | old_res_norm = fp.residual_norm() 196 | 197 | # smooth 198 | self.smooth(level, self.nsmooth) 199 | 200 | if self.verbose: 201 | print(f" level = {level}, nx = {fp.nx:4}, residual change: {old_res_norm:11.6g} -> {fp.residual_norm():11.6g}") 202 | 203 | else: 204 | # solve the discrete coarse problem just via smoothing 205 | if self.verbose: 206 | print(" bottom solve") 207 | 208 | bp = self.grids[0] 209 | 210 | self.smooth(0, self.nsmooth_bottom) 211 | 212 | bp.fill_bcs() 213 | -------------------------------------------------------------------------------- /content/elliptic_multigrid/multigrid/vcycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/elliptic_multigrid/multigrid/vcycle.png -------------------------------------------------------------------------------- /content/elliptic_multigrid/relaxation/ccfd_ghost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/elliptic_multigrid/relaxation/ccfd_ghost.png -------------------------------------------------------------------------------- /content/elliptic_multigrid/relaxation/ccfd_grid_bnd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/elliptic_multigrid/relaxation/ccfd_grid_bnd.png -------------------------------------------------------------------------------- /content/elliptic_multigrid/relaxation/fd_grid_bnd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/elliptic_multigrid/relaxation/fd_grid_bnd.png -------------------------------------------------------------------------------- /content/elliptic_multigrid/relaxation/relaxation.md: -------------------------------------------------------------------------------- 1 | Relaxation 2 | ========== 3 | 4 | We will use an iterative technique for solving our elliptic problems called 5 | relaxation. We'll start by understanding how relaxation works and then 6 | explore how to speed up the process through multigrid. 7 | -------------------------------------------------------------------------------- /content/elliptic_multigrid/relaxation/smoothing.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | class Grid: 4 | def __init__(self, nx, ng=1, xmin=0, xmax=1, 5 | bc_left_type="dirichlet", bc_left_val=0.0, 6 | bc_right_type="dirichlet", bc_right_val=0.0): 7 | 8 | self.xmin = xmin 9 | self.xmax = xmax 10 | self.ng = ng 11 | self.nx = nx 12 | 13 | self.bc_left_type = bc_left_type 14 | self.bc_left_val = bc_left_val 15 | 16 | self.bc_right_type = bc_right_type 17 | self.bc_right_val = bc_right_val 18 | 19 | # python is zero-based. Make easy intergers to know where the 20 | # real data lives 21 | self.ilo = ng 22 | self.ihi = ng+nx-1 23 | 24 | # physical coords -- cell-centered 25 | self.dx = (xmax - xmin)/(nx) 26 | self.x = xmin + (np.arange(nx+2*ng)-ng+0.5)*self.dx 27 | 28 | # storage for the solution 29 | self.phi = self.scratch_array() 30 | self.f = self.scratch_array() 31 | 32 | def scratch_array(self): 33 | """return a scratch array dimensioned for our grid """ 34 | return np.zeros((self.nx+2*self.ng), dtype=np.float64) 35 | 36 | def norm(self, e): 37 | """compute the L2 norm of e that lives on our grid""" 38 | return np.sqrt(self.dx * np.sum(e[self.ilo:self.ihi+1]**2)) 39 | 40 | def residual_norm(self): 41 | """compute the residual norm""" 42 | r = self.scratch_array() 43 | r[self.ilo:self.ihi+1] = self.f[self.ilo:self.ihi+1] - (self.phi[self.ilo+1:self.ihi+2] - 44 | 2 * self.phi[self.ilo:self.ihi+1] + 45 | self.phi[self.ilo-1:self.ihi]) / self.dx**2 46 | return self.norm(r) 47 | 48 | def source_norm(self): 49 | """compute the source norm""" 50 | return self.norm(self.f) 51 | 52 | def fill_bcs(self): 53 | """fill the boundary conditions on phi""" 54 | 55 | # we only deal with a single ghost cell here 56 | 57 | # left 58 | if self.bc_left_type.lower() == "dirichlet": 59 | self.phi[self.ilo-1] = 2 * self.bc_left_val - self.phi[self.ilo] 60 | elif self.bc_left_type.lower() == "neumann": 61 | self.phi[self.ilo-1] = self.phi[self.ilo] - self.dx * self.bc_left_val 62 | else: 63 | raise ValueError("invalid bc_left_type") 64 | 65 | # right 66 | if self.bc_right_type.lower() == "dirichlet": 67 | self.phi[self.ihi+1] = 2 * self.bc_right_val - self.phi[self.ihi] 68 | elif self.bc_right_type.lower() == "neumann": 69 | self.phi[self.ihi+1] = self.phi[self.ihi] - self.dx * self.bc_right_val 70 | else: 71 | raise ValueError("invalid bc_right_type") 72 | 73 | class TooManyIterations(Exception): 74 | pass 75 | 76 | def relax(g, tol=1.e-8, max_iters=200000, analytic=None): 77 | 78 | iter = 0 79 | fnorm = g.source_norm() 80 | if fnorm == 0.0: 81 | fnorm = tol 82 | 83 | r = g.residual_norm() 84 | 85 | res_norm = [] 86 | true_norm = [] 87 | 88 | if tol is None: 89 | test = iter < max_iters 90 | else: 91 | test = iter < max_iter and r > tol * fnorm 92 | 93 | g.fill_bcs() 94 | 95 | while test: 96 | g.phi[g.ilo:g.ihi+1:2] = 0.5 * (-g.dx * g.dx * g.f[g.ilo:g.ihi+1:2] + 97 | g.phi[g.ilo+1:g.ihi+2:2] + g.phi[g.ilo-1:g.ihi:2]) 98 | 99 | g.fill_bcs() 100 | 101 | g.phi[g.ilo+1:g.ihi+1:2] = 0.5 * (-g.dx * g.dx * g.f[g.ilo+1:g.ihi+1:2] + 102 | g.phi[g.ilo+2:g.ihi+2:2] + g.phi[g.ilo:g.ihi:2]) 103 | 104 | g.fill_bcs() 105 | 106 | r = g.residual_norm() 107 | res_norm.append(r) 108 | 109 | if analytic is not None: 110 | true_norm.append(g.norm(g.phi - analytic(g.x))) 111 | 112 | iter += 1 113 | 114 | if tol is None: 115 | test = iter < max_iters 116 | else: 117 | test = iter < max_iter and r > tol * fnorm 118 | 119 | if tol is not None and iter >= max_iters: 120 | raise TooManyIterations(f"too many iteration, niter = {iter}") 121 | 122 | return res_norm, true_norm 123 | 124 | -------------------------------------------------------------------------------- /content/intro.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | This is a collection of notebooks on computational (astro)physics, 5 | used to introduce finite-volume methods for solving the Euler equations. 6 | 7 | Starting at the beginning, these notebooks introduce numerical methods 8 | for derivatives, integration, rooting finding, and ODEs, focusing 9 | on the parts that we will need to solve the equations of hydrodynamics. 10 | 11 | The next set of notebooks begin with the linear advection equation 12 | and then onto the compressible Euler equations. 13 | 14 | By clicking on the "rocket ship" icon, you can launch the notebook 15 | in google colab or binder to run it in the cloud. 16 | 17 | This will also be the basis for the forthcoming AST 390 course at Stony 18 | Brook. 19 | -------------------------------------------------------------------------------- /content/reaction_networks/reactions.md: -------------------------------------------------------------------------------- 1 | Nuclear Reactions 2 | ================= 3 | 4 | Reaction Rates 5 | -------------- 6 | 7 | Nuclear experiments provide us with reaction rates of the form (for a 2-body reaction): 8 | 9 | $$r = \frac{n_A n_B}{1 + \delta_{AB}} \langle \sigma v \rangle$$ 10 | 11 | where $n_A$ is the number density of species A and $n_B$ is the number 12 | density of species B. The denominator ensures that we don't double 13 | count for a species reacting with itself. The term $\langle \sigma v 14 | \rangle$ is the average of the cross section and velocity over the 15 | distribution of velocities in the frame of the reaction. This is what is 16 | measured by experiements. 17 | 18 | Here the rate, $r$, has units of reactions / time / volume. 19 | 20 | 21 | Reaction databases (like ReacLib) often provide fits to $N_A \langle \sigma v 22 | \rangle$, where $N_A$ is Avogadro's number. 23 | 24 | Structure of a Network 25 | ---------------------- 26 | 27 | A reaction network is a set of nuclei and the rates that link them together. Consider 28 | 4 nuclei, $A$, $B$, $C$, and $D$, linked by 2 rates: 29 | 30 | $$A + A \rightarrow B + \gamma$$ 31 | 32 | $$B + C \rightarrow A + D$$ 33 | 34 | we would describe these as a set of ODEs for each species. In terms 35 | of number density, it is straightforward to write down: 36 | 37 | \begin{eqnarray*} 38 | \frac{dn_A}{dt} &=& - 2 \frac{1}{2} n_A^2 \langle \sigma v \rangle_{AA} 39 | + n_B n_C \langle \sigma v \rangle_{BC} \\ 40 | \frac{dn_B}{dt} &=& \frac{1}{2} n_A^2 \langle \sigma v \rangle_{AA} 41 | - n_B n_C \langle \sigma v \rangle_{BC} \\ 42 | \frac{dn_C}{dt} &=& - n_B n_C \langle \sigma v \rangle_{BC} \\ 43 | \frac{dn_D}{dt} &=& n_B n_C \langle \sigma v \rangle_{BC} \\ 44 | \end{eqnarray*} 45 | 46 | Here the first equation says that we lose 2 nuclei $A$ for each $A + A$ reaction 47 | and we gain 1 nuclei $A$ for each $B + C$ reaction. The factor of 1/2 in the first 48 | term is because when $A$ reacts with itself, we don't want to double count the number 49 | of pairs. 50 | 51 | We can instead write this in terms of molar or mass fractions. Mass fractions are defined 52 | as the mass of the species relative to the total mass of all species in a volume, or 53 | 54 | $$X_k = \frac{\rho_k}{\rho}$$ 55 | 56 | These have the property 57 | 58 | $$\sum_k X_k = 1$$ 59 | 60 | Molar fractions are scaled by the atomic weight: 61 | 62 | $$Y_k = \frac{X_k}{A_k}$$ 63 | 64 | where $Y_k$ is the molar fraction of species $k$, $X_k$ is the mass fraction, and $A_k$ 65 | is the atomic weight. Number density is related to mass fraction as: 66 | 67 | $$n_k = \frac{\rho X_k}{m_u A_k}$$ 68 | 69 | where $m_u$ is the atomic mass unit ($1/N_A$). 70 | 71 | Substituting these into the above expression we get 72 | 73 | \begin{eqnarray*} 74 | \frac{dY_A}{dt} &=& - 2 \frac{1}{2} \rho Y_A^2 N_A \langle \sigma v \rangle_{AA} 75 | + \rho Y_B Y_C N_A \langle \sigma v \rangle_{BC} \\ 76 | \frac{dY_B}{dt} &=& \frac{1}{2} \rho Y_A^2 N_A \langle \sigma v \rangle_{AA} 77 | - \rho Y_B Y_C N_A \langle \sigma v \rangle_{BC} \\ 78 | \frac{dY_C}{dt} &=& - \rho Y_B Y_C N_A \langle \sigma v \rangle_{BC} \\ 79 | \frac{dY_D}{dt} &=& \rho Y_B Y_C N_A \langle \sigma v \rangle_{BC} \\ 80 | \end{eqnarray*} 81 | 82 | This is often the form we write the system of ODEs in when we write a network. 83 | 84 | Integrating the Network 85 | ----------------------- 86 | 87 | We often need to integrate this system together with an energy 88 | equation to capture the evolution of the temperature as reactions 89 | progress, since the reaction rates are highly-temperature sensitive. 90 | 91 | But even without an energy equation, this system is difficult to 92 | integrate because there can be a wide range of timescales involved in 93 | the reaction rates, which makes the system a *stiff* system of ODEs. 94 | We need to use different methods from the explicit Runge-Kutta methods 95 | we often use. 96 | 97 | -------------------------------------------------------------------------------- /content/reaction_networks/stiff-ODEs.md: -------------------------------------------------------------------------------- 1 | Integrating Stiff ODEs 2 | ====================== 3 | -------------------------------------------------------------------------------- /content/references.bib: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | @inproceedings{holdgraf_evidence_2014, 5 | address = {Brisbane, Australia, Australia}, 6 | title = {Evidence for {Predictive} {Coding} in {Human} {Auditory} {Cortex}}, 7 | booktitle = {International {Conference} on {Cognitive} {Neuroscience}}, 8 | publisher = {Frontiers in Neuroscience}, 9 | author = {Holdgraf, Christopher Ramsay and de Heer, Wendy and Pasley, Brian N. and Knight, Robert T.}, 10 | year = {2014} 11 | } 12 | 13 | @article{holdgraf_rapid_2016, 14 | title = {Rapid tuning shifts in human auditory cortex enhance speech intelligibility}, 15 | volume = {7}, 16 | issn = {2041-1723}, 17 | url = {http://www.nature.com/doifinder/10.1038/ncomms13654}, 18 | doi = {10.1038/ncomms13654}, 19 | number = {May}, 20 | journal = {Nature Communications}, 21 | author = {Holdgraf, Christopher Ramsay and de Heer, Wendy and Pasley, Brian N. and Rieger, Jochem W. and Crone, Nathan and Lin, Jack J. and Knight, Robert T. and Theunissen, Frédéric E.}, 22 | year = {2016}, 23 | pages = {13654}, 24 | file = {Holdgraf et al. - 2016 - Rapid tuning shifts in human auditory cortex enhance speech intelligibility.pdf:C\:\\Users\\chold\\Zotero\\storage\\MDQP3JWE\\Holdgraf et al. - 2016 - Rapid tuning shifts in human auditory cortex enhance speech intelligibility.pdf:application/pdf} 25 | } 26 | 27 | @inproceedings{holdgraf_portable_2017, 28 | title = {Portable learning environments for hands-on computational instruction using container-and cloud-based technology to teach data science}, 29 | volume = {Part F1287}, 30 | isbn = {978-1-4503-5272-7}, 31 | doi = {10.1145/3093338.3093370}, 32 | abstract = {© 2017 ACM. There is an increasing interest in learning outside of the traditional classroom setting. This is especially true for topics covering computational tools and data science, as both are challenging to incorporate in the standard curriculum. These atypical learning environments offer new opportunities for teaching, particularly when it comes to combining conceptual knowledge with hands-on experience/expertise with methods and skills. Advances in cloud computing and containerized environments provide an attractive opportunity to improve the effciency and ease with which students can learn. This manuscript details recent advances towards using commonly-Available cloud computing services and advanced cyberinfrastructure support for improving the learning experience in bootcamp-style events. We cover the benets (and challenges) of using a server hosted remotely instead of relying on student laptops, discuss the technology that was used in order to make this possible, and give suggestions for how others could implement and improve upon this model for pedagogy and reproducibility.}, 33 | booktitle = {{ACM} {International} {Conference} {Proceeding} {Series}}, 34 | author = {Holdgraf, Christopher Ramsay and Culich, A. and Rokem, A. and Deniz, F. and Alegro, M. and Ushizima, D.}, 35 | year = {2017}, 36 | keywords = {Teaching, Bootcamps, Cloud computing, Data science, Docker, Pedagogy} 37 | } 38 | 39 | @article{holdgraf_encoding_2017, 40 | title = {Encoding and decoding models in cognitive electrophysiology}, 41 | volume = {11}, 42 | issn = {16625137}, 43 | doi = {10.3389/fnsys.2017.00061}, 44 | abstract = {© 2017 Holdgraf, Rieger, Micheli, Martin, Knight and Theunissen. Cognitive neuroscience has seen rapid growth in the size and complexity of data recorded from the human brain as well as in the computational tools available to analyze this data. This data explosion has resulted in an increased use of multivariate, model-based methods for asking neuroscience questions, allowing scientists to investigate multiple hypotheses with a single dataset, to use complex, time-varying stimuli, and to study the human brain under more naturalistic conditions. These tools come in the form of “Encoding” models, in which stimulus features are used to model brain activity, and “Decoding” models, in which neural features are used to generated a stimulus output. Here we review the current state of encoding and decoding models in cognitive electrophysiology and provide a practical guide toward conducting experiments and analyses in this emerging field. Our examples focus on using linear models in the study of human language and audition. We show how to calculate auditory receptive fields from natural sounds as well as how to decode neural recordings to predict speech. The paper aims to be a useful tutorial to these approaches, and a practical introduction to using machine learning and applied statistics to build models of neural activity. The data analytic approaches we discuss may also be applied to other sensory modalities, motor systems, and cognitive systems, and we cover some examples in these areas. In addition, a collection of Jupyter notebooks is publicly available as a complement to the material covered in this paper, providing code examples and tutorials for predictive modeling in python. The aimis to provide a practical understanding of predictivemodeling of human brain data and to propose best-practices in conducting these analyses.}, 45 | journal = {Frontiers in Systems Neuroscience}, 46 | author = {Holdgraf, Christopher Ramsay and Rieger, J.W. and Micheli, C. and Martin, S. and Knight, R.T. and Theunissen, F.E.}, 47 | year = {2017}, 48 | keywords = {Decoding models, Encoding models, Electrocorticography (ECoG), Electrophysiology/evoked potentials, Machine learning applied to neuroscience, Natural stimuli, Predictive modeling, Tutorials} 49 | } 50 | 51 | @book{ruby, 52 | title = {The Ruby Programming Language}, 53 | author = {Flanagan, David and Matsumoto, Yukihiro}, 54 | year = {2008}, 55 | publisher = {O'Reilly Media} 56 | } 57 | -------------------------------------------------------------------------------- /content/rt_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/rt_small.png -------------------------------------------------------------------------------- /content/stars/le_extrap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zingale/comp_astro_tutorial/12a0e378d3539416ca017fcb35426fe5ad63fa9b/content/stars/le_extrap.png -------------------------------------------------------------------------------- /content/stars/stars.md: -------------------------------------------------------------------------------- 1 | Stars 2 | ===== 3 | 4 | Some examples of applying numerical methods to stellar structure 5 | -------------------------------------------------------------------------------- /figures/2dgrid.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import grid_plot as gp 4 | 5 | def simplegrid(): 6 | 7 | nzones = 3 8 | gr = gp.FVGrid2d(nzones, nzones, ng=0) 9 | 10 | # plot a domain without ghostcells 11 | gr.draw_grid() 12 | 13 | 14 | #------------------------------------------------------------------------ 15 | # label 16 | gr.label_cell_center(nzones//2, nzones//2, r"$a_{i,j}$", fontsize="large") 17 | gr.label_cell_center(nzones//2+1, nzones//2, r"$a_{i+1,j}$", fontsize="large") 18 | gr.label_cell_center(nzones//2, nzones//2+1, r"$a_{i,j+1}$", fontsize="large") 19 | 20 | # i+1/2,j interface 21 | gr.mark_cell_left_state_x(nzones//2, nzones//2, r"$a_{i+\myhalf,j,L}$", 22 | color="C0") 23 | gr.mark_cell_right_state_x(nzones//2+1, nzones//2, r"$a_{i+\myhalf,j,R}$", 24 | color="C0") 25 | 26 | 27 | # i,j+1/2 interface 28 | gr.mark_cell_left_state_y(nzones//2, nzones//2, r"$a_{i,j+\myhalf,L}$", 29 | color="C0") 30 | gr.mark_cell_right_state_y(nzones//2, nzones//2+1, r"$a_{i,j+\myhalf,R}$", 31 | color="C0") 32 | 33 | # grid labels 34 | gr.label_center_x(nzones//2-1, r"$i-1$") 35 | gr.label_center_x(nzones//2, r"$i$") 36 | gr.label_center_x(nzones//2+1, r"$i+1$") 37 | 38 | gr.label_center_y(nzones//2-1, r"$j-1$") 39 | gr.label_center_y(nzones//2, r"$j$") 40 | gr.label_center_y(nzones//2+1, r"$j+1$") 41 | 42 | 43 | # axes 44 | gr.clean_axes() 45 | plt.subplots_adjust(left=0.02,right=0.98,bottom=0.02,top=0.98) 46 | 47 | f = plt.gcf() 48 | f.set_size_inches(6.0,6.0) 49 | 50 | plt.savefig("2dgrid.png", dpi=150) 51 | 52 | if __name__== "__main__": 53 | simplegrid() 54 | -------------------------------------------------------------------------------- /figures/advection_states.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pylab as plt 3 | import grid_plot as gp 4 | 5 | def riemann(with_time=True): 6 | 7 | # grid info 8 | xmin = 0.0 9 | xmax = 1.0 10 | 11 | nzones = 1 12 | ng = 0 13 | 14 | gr = gp.FVGrid(nzones, xmin=xmin, xmax=xmax) 15 | 16 | plt.clf() 17 | 18 | #------------------------------------------------------------------------ 19 | # plot a domain without ghostcells 20 | gr.draw_grid() 21 | 22 | gr.label_center(0, r"$i$", fontsize="medium") 23 | 24 | gr.label_edge(0, r"$i-\myhalf$", fontsize="medium") 25 | gr.label_edge(0, r"$i+\myhalf$", fontsize="medium", right_edge=True) 26 | 27 | 28 | plt.arrow(gr.xc[0]+0.05*gr.dx, 0.5, 0.12*gr.dx, 0, 29 | shape='full', head_width=0.05, head_length=0.025, 30 | lw=1, width=0.02, 31 | edgecolor="none", facecolor="r", 32 | length_includes_head=True, zorder=100) 33 | 34 | plt.arrow(gr.xc[0]-0.05*gr.dx, 0.5, -0.12*gr.dx, 0, 35 | shape='full', head_width=0.05, head_length=0.025, 36 | lw=1, width=0.02, 37 | edgecolor="none", facecolor="r", 38 | length_includes_head=True, zorder=100) 39 | 40 | gr.mark_cell_left_state(0, r"$a_{i-\myhalf,R}$", fontsize="large", 41 | color="b") 42 | gr.mark_cell_right_state(0, r"$a_{i+\myhalf,L}$", fontsize="large", 43 | color="b") 44 | 45 | gr.label_cell_center(0, r"$a_i$") 46 | 47 | gr.clean_axes(pad_fac=0.125, ylim=(-0.25, 1.0)) 48 | 49 | f = plt.gcf() 50 | f.set_size_inches(5.0,2.0) 51 | 52 | plt.tight_layout() 53 | 54 | plt.savefig("advection-states.png") 55 | 56 | if __name__== "__main__": 57 | riemann() 58 | riemann(with_time=False) 59 | -------------------------------------------------------------------------------- /figures/array_labels.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pylab as plt 3 | import grid_plot as gp 4 | 5 | def riemann(with_time=True): 6 | 7 | # grid info 8 | xmin = 0.0 9 | xmax = 1.0 10 | 11 | nzones = 2 12 | ng = 0 13 | 14 | gr = gp.FVGrid(nzones, xmin=xmin, xmax=xmax) 15 | 16 | plt.clf() 17 | 18 | #------------------------------------------------------------------------ 19 | # plot a domain without ghostcells 20 | gr.draw_grid() 21 | 22 | gr.label_center(0, r"$i$", fontsize="medium") 23 | gr.label_center(0, r"${\tt a[i]}$", fontsize="medium", extra_voff=-0.3, color="r") 24 | 25 | gr.label_center(1, r"$i+1$", fontsize="medium") 26 | gr.label_center(1, r"${\tt a[i+1]}$", fontsize="medium", extra_voff=-0.3, color="r") 27 | 28 | gr.label_edge(0, r"$i-\myhalf$", fontsize="medium") 29 | gr.label_edge(0, r"${\tt aint[i]}$", fontsize="medium", extra_voff=-0.3, color="r") 30 | 31 | gr.label_edge(1, r"$i+\myhalf$", fontsize="medium") 32 | gr.label_edge(1, r"${\tt aint[i+1]}$", fontsize="medium", extra_voff=-0.3, color="r") 33 | 34 | gr.label_edge(1, r"$i+\mythreehalf$", fontsize="medium", right_edge=True) 35 | gr.label_edge(1, r"${\tt aint[i+2]}$", fontsize="medium", extra_voff=-0.3, color="r", right_edge=True) 36 | 37 | 38 | gr.mark_cell_edge(0, r"$a_{i-\myhalf}$", fontsize="large", color="r") 39 | gr.mark_cell_edge(1, r"$a_{i+\myhalf}$", fontsize="large", color="r") 40 | gr.mark_cell_edge(1, r"$a_{i+\mythreehalf}$", fontsize="large", color="r", right_edge=True) 41 | 42 | gr.label_cell_center(0, r"$a_i$", color="r") 43 | gr.label_cell_center(1, r"$a_{i+1}$", color="r") 44 | 45 | 46 | gr.clean_axes(pad_fac=0.125, ylim=(-0.25, 1.0)) 47 | 48 | f = plt.gcf() 49 | f.set_size_inches(7.0,2.0) 50 | 51 | plt.tight_layout() 52 | 53 | plt.savefig("array-labels.png") 54 | 55 | if __name__== "__main__": 56 | riemann() 57 | riemann(with_time=False) 58 | -------------------------------------------------------------------------------- /figures/ccfd_ghost.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import grid_plot as gp 4 | 5 | # plot a simple finite-difference grid 6 | 7 | #----------------------------------------------------------------------------- 8 | 9 | nzones = 7 10 | ng = 1 11 | 12 | # data that lives on the grid 13 | #a = np.array([0.3, 1.0, 0.9, 0.8, 0.25, 0.15, 0.5, 0.55]) 14 | #a = np.array([0.3, 1.0, 0.9, 0.8, 0.25, 0.1, 0.5, 0.55]) 15 | a = np.array([1.0, 0.9, 0.8, 0.25, 0.1, 0.5, 0.55]) 16 | 17 | gr = gp.FVGrid(nzones, ng) 18 | 19 | aa = gr.scratch_array() 20 | aa[gr.ilo:gr.ihi+1] = a 21 | 22 | cc = gp.CellCentered(gr, aa) 23 | 24 | plt.clf() 25 | 26 | gr.draw_grid(draw_ghost=1, emphasize_end=1) 27 | 28 | gr.label_center(ng+nzones//2, r"$i$") 29 | gr.label_center(ng+nzones//2-1, r"$i-1$") 30 | gr.label_center(ng+nzones//2+1, r"$i+1$") 31 | 32 | gr.label_center(gr.ilo, r"$\mathrm{lo}$") 33 | gr.label_center(gr.ilo-1, r"$\mathrm{lo-1}$") 34 | 35 | gr.label_center(gr.ihi, r"$\mathrm{hi}$") 36 | gr.label_center(gr.ihi+1, r"$\mathrm{hi+1}$") 37 | 38 | # draw the data 39 | for i in range(nzones): 40 | cc.draw_data_point(ng+i, color="r") 41 | 42 | cc.label_data_point(ng+nzones//2, r"$\phi_i$", color="r") 43 | 44 | # label dx 45 | gr.label_dx(gr.ng+nzones//2) 46 | 47 | gr.clean_axes(show_ghost=True, pad_fac=0.02, ylim=(-0.5, 1.6)) 48 | 49 | f = plt.gcf() 50 | f.set_size_inches(10.0,2.5) 51 | 52 | plt.savefig("ccfd_ghost.png", dpi=100) 53 | -------------------------------------------------------------------------------- /figures/derivatives.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib as mpl 3 | import matplotlib.pyplot as plt 4 | from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes 5 | 6 | # old font defaults 7 | mpl.rcParams['mathtext.fontset'] = 'cm' 8 | mpl.rcParams['mathtext.rm'] = 'serif' 9 | 10 | 11 | x_smooth = np.linspace(0.0, np.pi, 500) 12 | f_smooth = np.sin(x_smooth) 13 | 14 | 15 | x = np.linspace(0.0, np.pi, 10) 16 | f = np.sin(x) 17 | 18 | i = 5 19 | x_0 = x[i] 20 | f_0 = f[i] 21 | 22 | plt.plot(x_smooth, f_smooth) 23 | plt.scatter(x, f) 24 | 25 | plt.scatter(x_0, np.sin(x_0), color="r", zorder=10) 26 | 27 | dx = x[1] - x[0] 28 | x_d = np.linspace(x[i]-1.5*dx, x[i]+1.5*dx, 2) 29 | 30 | d_exact = np.cos(x_0) 31 | 32 | d_l = (np.sin(x[i]) - np.sin(x[i-1]))/dx 33 | d_r = (np.sin(x[i+1]) - np.sin(x[i]))/dx 34 | d_c = 0.5*(np.sin(x[i+1]) - np.sin(x[i-1]))/dx 35 | 36 | 37 | # plot a line showing dx 38 | dx = x[2] - x[1] 39 | ann = plt.annotate('', xy=(x[1], f[1]-0.5*dx), xycoords='data', 40 | xytext=(x[2], f[1]-0.5*dx), textcoords='data', 41 | arrowprops=dict(arrowstyle="<->", 42 | connectionstyle="bar,fraction=-0.4", 43 | ec="k", 44 | shrinkA=5, shrinkB=5, 45 | )) 46 | 47 | plt.text(0.5*(x[1]+x[2]), f[1]-0.8*dx, "$\Delta x$", 48 | horizontalalignment="center") 49 | 50 | plt.plot([x[1], x[1]], [f[1]-0.5*dx, f[2]+0.5*dx], ls=":", color="0.5") 51 | plt.plot([x[2], x[2]], [f[1]-0.5*dx, f[2]+0.5*dx], ls=":", color="0.5") 52 | 53 | plt.plot(x_d, d_exact*(x_d - x_0) + f_0, color="0.5", lw=2, ls=":", label="exact") 54 | plt.plot(x_d, d_l*(x_d - x_0) + f_0, label="left-sided") 55 | plt.plot(x_d, d_r*(x_d - x_0) + f_0, label="right-sided") 56 | plt.plot(x_d, d_c*(x_d - x_0) + f_0, label="centered") 57 | 58 | 59 | ax = plt.gca() 60 | 61 | 62 | # origin of the axes through 0 63 | ax.spines['left'].set_position('zero') 64 | ax.spines['right'].set_color('none') 65 | ax.spines['bottom'].set_position('zero') 66 | ax.spines['top'].set_color('none') 67 | ax.spines['left'].set_smart_bounds(True) 68 | ax.spines['bottom'].set_smart_bounds(True) 69 | ax.xaxis.set_ticks_position('bottom') 70 | ax.yaxis.set_ticks_position('left') 71 | 72 | plt.legend(frameon=False, loc="best") 73 | 74 | plt.ylim(-0.1, 1.1) 75 | 76 | plt.tight_layout() 77 | 78 | 79 | plt.savefig("derivs.pdf") 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /figures/fd.py: -------------------------------------------------------------------------------- 1 | 2 | import matplotlib.pyplot as plt 3 | import numpy as np 4 | import grid_plot as gp 5 | 6 | # plot a simple finite-difference grid 7 | 8 | #----------------------------------------------------------------------------- 9 | 10 | nzones = 8 11 | 12 | # data that lives on the grid 13 | #a = np.array([0.3, 1.0, 0.9, 0.8, 0.25, 0.15, 0.5, 0.55]) 14 | a = np.array([0.3, 1.0, 0.9, 0.8, 0.25, 0.1, 0.5, 0.55]) 15 | 16 | gr = gp.FDGrid(nzones) 17 | 18 | 19 | plt.clf() 20 | 21 | gr.draw_grid() 22 | 23 | gr.label_node(nzones//2, r"$i$", fontsize="medium") 24 | gr.label_node(nzones//2-1, r"$i-1$", fontsize="medium") 25 | gr.label_node(nzones//2+1, r"$i+1$", fontsize="medium") 26 | gr.label_node(nzones//2-2, r"$i-2$", fontsize="medium") 27 | gr.label_node(nzones//2+2, r"$i+2$", fontsize="medium") 28 | 29 | 30 | # draw the data 31 | for i in range(nzones): 32 | gr.draw_data(i, a[i], color="r") 33 | 34 | 35 | gr.label_value(nzones//2, a[nzones//2], r"$a_i$", color="r") 36 | 37 | # label dx 38 | gr.label_dx(gr.ng+nzones//2) 39 | 40 | gr.clean_axes(ylim=(-0.5, 1.2)) 41 | 42 | f = plt.gcf() 43 | f.set_size_inches(10.0,3.0) 44 | 45 | plt.savefig("fd_grid_basic.png") 46 | -------------------------------------------------------------------------------- /figures/fd_ghost.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import grid_plot as gp 4 | 5 | # plot a simple finite-difference grid 6 | 7 | #----------------------------------------------------------------------------- 8 | 9 | nzones = 9 10 | 11 | # data that lives on the grid 12 | #a = np.array([0.3, 1.0, 0.9, 0.8, 0.25, 0.15, 0.5, 0.55]) 13 | a = np.array([0.55, 0.3, 1.0, 0.9, 0.8, 0.25, 0.1, 0.5, 0.55]) 14 | 15 | gr = gp.FDGrid(nzones, ng=1) 16 | 17 | plt.clf() 18 | 19 | gr.draw_grid(draw_ghost=1) 20 | 21 | labels = ["-1", "0", "1", "", "i-1", "i", "i+1", "", "N-2", "N-1", "N"] 22 | 23 | for i in range(gr.ilo-gr.ng, gr.ng+gr.nx+1): 24 | if not labels[i] == "": 25 | gr.label_node(i, r"$%s$" % (labels[i]), fontsize="medium") 26 | 27 | # draw the data 28 | for i in range(gr.ilo, gr.ihi+1): 29 | gr.draw_data(i, a[i-gr.ng], color="r") 30 | 31 | 32 | gr.label_value(gr.ilo+4, a[gr.ilo+4-gr.ng], r"$a_i$", color="r") 33 | 34 | # label dx 35 | gr.label_dx(gr.ng+nzones//2) 36 | 37 | gr.clean_axes(pad_fac=0.1, show_ghost=True, ylim=(-0.5, 1.3)) 38 | 39 | f = plt.gcf() 40 | f.set_size_inches(10.0,3.0) 41 | 42 | 43 | plt.savefig("fd_ghost.png") 44 | -------------------------------------------------------------------------------- /figures/fd_ghost_lohi.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import grid_plot as gp 4 | 5 | # plot a simple finite-difference grid 6 | 7 | #----------------------------------------------------------------------------- 8 | 9 | nzones = 9 10 | 11 | # data that lives on the grid 12 | #a = np.array([0.3, 1.0, 0.9, 0.8, 0.25, 0.15, 0.5, 0.55]) 13 | a = np.array([0.55, 0.3, 1.0, 0.9, 0.8, 0.25, 0.1, 0.5, 0.55]) 14 | 15 | gr = gp.FDGrid(nzones, ng=1) 16 | 17 | plt.clf() 18 | 19 | gr.draw_grid(draw_ghost=1) 20 | 21 | labels = ["\mathrm{lo}-1", "\mathrm{lo}", "\mathrm{lo}+1", "", "i-1", "i", "i+1", "", "\mathrm{hi}-1", "\mathrm{hi}", "\mathrm{hi}+1"] 22 | 23 | for i in range(gr.ilo-gr.ng, gr.ng+gr.nx+1): 24 | if not labels[i] == "": 25 | gr.label_node(i, r"$%s$" % (labels[i]), fontsize="medium") 26 | 27 | # draw the data 28 | for i in range(gr.ilo, gr.ihi+1): 29 | gr.draw_data(i, a[i-gr.ng], color="r") 30 | 31 | 32 | gr.label_value(gr.ilo+4, a[gr.ilo+4-gr.ng], r"$a_i$", color="r") 33 | 34 | # label dx 35 | gr.label_dx(gr.ng+nzones//2) 36 | 37 | gr.clean_axes(pad_fac=0.1, show_ghost=True, ylim=(-0.5, 1.3)) 38 | 39 | f = plt.gcf() 40 | f.set_size_inches(10.0,3.0) 41 | 42 | 43 | plt.savefig("fd_ghost_lohi.png") 44 | -------------------------------------------------------------------------------- /figures/fd_lohi.py: -------------------------------------------------------------------------------- 1 | 2 | import matplotlib.pyplot as plt 3 | import numpy as np 4 | import grid_plot as gp 5 | 6 | # plot a simple finite-difference grid 7 | 8 | #----------------------------------------------------------------------------- 9 | 10 | nzones = 8 11 | 12 | # data that lives on the grid 13 | #a = np.array([0.3, 1.0, 0.9, 0.8, 0.25, 0.15, 0.5, 0.55]) 14 | a = np.array([0.3, 1.0, 0.9, 0.8, 0.25, 0.1, 0.5, 0.55]) 15 | 16 | gr = gp.FDGrid(nzones) 17 | 18 | 19 | plt.clf() 20 | 21 | gr.draw_grid() 22 | 23 | gr.label_node(nzones//2, r"$i$", fontsize="medium") 24 | gr.label_node(nzones//2-1, r"$i-1$", fontsize="medium") 25 | gr.label_node(nzones//2+1, r"$i+1$", fontsize="medium") 26 | gr.label_node(nzones//2-2, r"$i-2$", fontsize="medium") 27 | gr.label_node(nzones//2+2, r"$i+2$", fontsize="medium") 28 | 29 | 30 | # draw the data 31 | for i in range(nzones): 32 | gr.draw_data(i, a[i], color="r") 33 | 34 | 35 | gr.label_value(nzones//2, a[nzones//2], r"$a_i$", color="r") 36 | 37 | # label dx 38 | gr.label_dx(gr.ng+nzones//2) 39 | 40 | gr.clean_axes(ylim=(-0.5, 1.2)) 41 | 42 | f = plt.gcf() 43 | f.set_size_inches(10.0,3.0) 44 | 45 | plt.savefig("fd_grid_lohi.png") 46 | -------------------------------------------------------------------------------- /figures/fd_with_function.py: -------------------------------------------------------------------------------- 1 | 2 | import matplotlib.pyplot as plt 3 | import numpy as np 4 | import grid_plot as gp 5 | 6 | 7 | def func(x): 8 | return 0.1*x**3 - 0.25*x + 0.5*x + 0.5 + 0.25*np.sin(3*np.pi*x) 9 | 10 | # plot a simple finite-difference grid 11 | 12 | #----------------------------------------------------------------------------- 13 | 14 | nzones = 8 15 | 16 | # data that lives on the grid 17 | 18 | gr = gp.FDGrid(nzones) 19 | 20 | a = func(gr.xc) 21 | 22 | plt.clf() 23 | 24 | gr.draw_grid() 25 | 26 | gr.label_node(nzones//2, r"$i$", fontsize="medium") 27 | gr.label_node(nzones//2-1, r"$i-1$", fontsize="medium") 28 | gr.label_node(nzones//2+1, r"$i+1$", fontsize="medium") 29 | gr.label_node(nzones//2-2, r"$i-2$", fontsize="medium") 30 | gr.label_node(nzones//2+2, r"$i+2$", fontsize="medium") 31 | 32 | 33 | # draw the data 34 | for i in range(nzones): 35 | gr.draw_data(i, a[i], color="r") 36 | 37 | # draw a line with the "true" data 38 | xfine = np.linspace(gr.xmin, gr.xmax, 1000) 39 | 40 | plt.plot(xfine, func(xfine), color="red", alpha=0.5) 41 | 42 | gr.label_value(nzones//2, a[nzones//2], r"$f_i$", color="r") 43 | gr.label_value(nzones//2+1, a[nzones//2+1], r"$f_{i+1}$", color="r") 44 | gr.label_value(nzones//2-1, a[nzones//2-1], r"$f_{i-1}$", color="r") 45 | 46 | # label dx 47 | gr.label_dx(gr.ng+nzones//2) 48 | 49 | gr.clean_axes(ylim=(-0.5, 1.2)) 50 | 51 | f = plt.gcf() 52 | f.set_size_inches(10.0,3.0) 53 | 54 | plt.savefig("fd_grid.png") 55 | -------------------------------------------------------------------------------- /figures/fv_ghost.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import grid_plot as gp 4 | 5 | # plot a simple finite-difference grid 6 | 7 | #----------------------------------------------------------------------------- 8 | 9 | nzones = 7 10 | ng = 2 11 | 12 | # data that lives on the grid 13 | #a = np.array([0.3, 1.0, 0.9, 0.8, 0.25, 0.15, 0.5, 0.55]) 14 | #a = np.array([0.3, 1.0, 0.9, 0.8, 0.25, 0.1, 0.5, 0.55]) 15 | a = np.array([1.0, 0.9, 0.8, 0.25, 0.1, 0.5, 0.55]) 16 | 17 | gr = gp.FVGrid(nzones, ng) 18 | 19 | aa = gr.scratch_array() 20 | aa[gr.ilo:gr.ihi+1] = a 21 | 22 | cc = gp.PiecewiseConstant(gr, aa) 23 | 24 | plt.clf() 25 | 26 | gr.draw_grid(draw_ghost=1, emphasize_end=1) 27 | 28 | gr.label_center(ng+nzones//2, r"$i$") 29 | gr.label_center(ng+nzones//2-1, r"$i-1$") 30 | gr.label_center(ng+nzones//2+1, r"$i+1$") 31 | 32 | gr.label_center(gr.ilo, r"$\mathrm{lo}$") 33 | gr.label_center(gr.ilo-1, r"$\mathrm{lo-1}$") 34 | gr.label_center(gr.ilo-2, r"$\mathrm{lo-2}$") 35 | 36 | gr.label_center(gr.ihi, r"$\mathrm{hi}$") 37 | gr.label_center(gr.ihi+1, r"$\mathrm{hi+1}$") 38 | gr.label_center(gr.ihi+2, r"$\mathrm{hi+2}$") 39 | 40 | gr.label_edge(ng+nzones//2, r"$i-\sfrac{1}{2}$") 41 | gr.label_edge(ng+nzones//2+1, r"$i+\sfrac{1}{2}$") 42 | 43 | # draw the data 44 | for i in range(nzones): 45 | cc.draw_cell_avg(ng+i, color="r") 46 | 47 | cc.label_cell_avg(ng+nzones//2, r"$\langle a\rangle_i$", color="r") 48 | 49 | # label dx 50 | gr.label_dx(gr.ng+nzones//2) 51 | 52 | gr.clean_axes(show_ghost=True, pad_fac=0.02, ylim=(-0.5, 1.6)) 53 | 54 | f = plt.gcf() 55 | f.set_size_inches(10.0,2.5) 56 | 57 | plt.savefig("fv_ghost.png") 58 | -------------------------------------------------------------------------------- /figures/fvrestrict.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import grid_plot as gp 4 | 5 | # plot two stacked fv grids of different (2x) resolution to show prolongation 6 | 7 | #----------------------------------------------------------------------------- 8 | 9 | nf = 4 10 | nc = nf//2 11 | 12 | grf = gp.FVGrid(nf, voff=2.0) 13 | grc = gp.FVGrid(nc) 14 | 15 | 16 | plt.clf() 17 | 18 | grf.draw_grid() 19 | grc.draw_grid() 20 | 21 | grf.label_center(nf//2-2, r"$i-2$") 22 | grf.label_center(nf//2-1, r"$i-1$") 23 | grf.label_center(nf//2, r"$i$") 24 | grf.label_center(nf//2+1, r"$i+1$") 25 | 26 | grc.label_center(nc//2-1, r"$j-1$") 27 | grc.label_center(nc//2, r"$j$") 28 | 29 | grf.label_cell_center(nf//2-2, r"$\phi_{i-2}^f$") 30 | grf.label_cell_center(nf//2-1, r"$\phi_{i-1}^f$") 31 | grf.label_cell_center(nf//2, r"$\phi_i^f$") 32 | grf.label_cell_center(nf//2+1, r"$\phi_{i+1}^f$") 33 | 34 | grc.label_cell_center(nc//2-1, r"$\phi_{j-1}^{c}$") 35 | grc.label_cell_center(nc//2, r"$\phi_{j}^{c}$") 36 | 37 | 38 | # connect the dots... 39 | 40 | plt.plot([grf.xl[nf//2-2], grf.xl[nf//2-2]], [-0.25, 3.25], ":", color="0.5") 41 | plt.plot([grf.xl[nf//2], grf.xl[nf//2]], [-0.25, 3.25], ":", color="0.5") 42 | plt.plot([grf.xr[nf//2+1], grf.xr[nf//2+1]], [-0.25, 3.25], ":", color="0.5") 43 | 44 | 45 | plt.axis([grf.xmin-0.5*grf.dx,grf.xmax+0.5*grf.dx, -0.5, 3.5]) 46 | plt.axis("off") 47 | 48 | plt.subplots_adjust(left=0.05,right=0.95,bottom=0.05,top=0.95) 49 | 50 | f = plt.gcf() 51 | f.set_size_inches(6.0,5.0) 52 | 53 | plt.savefig("fvrestrict.png", dpi=200) 54 | 55 | -------------------------------------------------------------------------------- /figures/integrals.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib as mpl 3 | import matplotlib.pyplot as plt 4 | from matplotlib.patches import Polygon 5 | 6 | mpl.rcParams['mathtext.fontset'] = 'cm' 7 | mpl.rcParams['mathtext.rm'] = 'serif' 8 | 9 | 10 | def f(x): 11 | """ the function we are integrating """ 12 | return 1.0 + x*0.25*np.sin(np.pi*x) 13 | 14 | 15 | plt.rcParams.update({'xtick.labelsize': 18, 16 | 'ytick.labelsize': 18, 17 | 'font.size': 18}) 18 | 19 | 20 | 21 | def plot_base(xp, fp, xfine, a, b, label_mid=False): 22 | 23 | fmax = fp.max() 24 | 25 | for xl in xp: 26 | plt.plot([xl,xl], [0.0, 1.2*fmax], ls="--", color="0.5", zorder=-1) 27 | 28 | plt.scatter(xp, fp, marker="o", color="r", zorder=100) 29 | 30 | plt.figtext(0.9, 0.05, '$x$', fontsize=20) 31 | plt.figtext(0.1, 0.9, '$y$', fontsize=20) 32 | 33 | ax = plt.gca() 34 | 35 | ax.spines['right'].set_visible(False) 36 | ax.spines['top'].set_visible(False) 37 | ax.xaxis.set_ticks_position('bottom') 38 | 39 | if label_mid: 40 | ax.set_xticks((a, (a+b)/2, b)) 41 | ax.set_xticklabels(('$a$', r'$\frac{(a+b)}{2}$', '$b$')) 42 | else: 43 | ax.set_xticks((a, b)) 44 | ax.set_xticklabels(('$a$', '$b$')) 45 | 46 | ax.set_yticks([]) 47 | 48 | plt.plot(xfine, f(xfine), "r", linewidth=2) 49 | 50 | plt.xlim(np.min(xfine), 1.05*np.max(xfine)) 51 | plt.ylim(ymin = 0) 52 | 53 | 54 | 55 | def rectangle(xp, fp, a, b): 56 | 57 | ax = plt.gca() 58 | 59 | integral = 0.0 60 | 61 | for n in range(len(xp)-1): 62 | 63 | xl = xp[n] 64 | xr = xp[n+1] 65 | 66 | fl = fp[n] 67 | 68 | # shade region 69 | verts = [(xl, 0), (xl, fl), (xr, fl), (xr, 0)] 70 | ax.add_patch(Polygon(verts, facecolor="0.8", edgecolor="k")) 71 | 72 | # and bonus! actually compute the integral in this approximation 73 | integral += (xr - xl) * fl 74 | 75 | return integral 76 | 77 | 78 | def trapezoid(xp, fp, a, b): 79 | 80 | ax = plt.gca() 81 | 82 | integral = 0.0 83 | 84 | for n in range(len(xp)-1): 85 | 86 | xl = xp[n] 87 | xr = xp[n+1] 88 | 89 | # shade region 90 | fl = f(xl) 91 | fr = f(xr) 92 | 93 | verts = [(xl, 0), (xl, fl), (xr, fr), (xr, 0)] 94 | ax.add_patch(Polygon(verts, facecolor="0.8", edgecolor="k")) 95 | 96 | integral += 0.5 * (xr - xl) * (fl + fr) 97 | 98 | return integral 99 | 100 | def simpsons(xp, fp, a, b): 101 | 102 | ax = plt.gca() 103 | 104 | integral = 0.0 105 | 106 | for n in range(0, len(xp)-1, 2): 107 | 108 | # we need to handle the 1 bin case specially 109 | 110 | if len(xp) == 2: 111 | 112 | xl = xp[0] 113 | xr = xp[1] 114 | xm = 0.5 * (xl + xr) 115 | 116 | f0 = f(xl) 117 | f1 = f(xm) 118 | f2 = f(xr) 119 | 120 | else: 121 | f0 = fp[n] 122 | f1 = fp[n+1] 123 | f2 = fp[n+2] 124 | 125 | xl = xp[n] 126 | xr = xp[n+2] 127 | 128 | delta = 0.5*(xr - xl) 129 | 130 | A = (f0 - 2*f1 + f2)/(2*delta**2) 131 | B = -(f2 - 4*f1 + 3*f0)/(2*delta) 132 | C = f0 133 | 134 | xsimp = np.linspace(xl, xr, 100) 135 | fsimp = A * (xsimp - xl)**2 + B * (xsimp - xl) + C 136 | 137 | simpvert = list(zip(xsimp, fsimp)) 138 | 139 | verts = [(xl, 0)] + simpvert + [(xr, 0)] 140 | ax.add_patch(Polygon(verts, facecolor="0.8", edgecolor="k")) 141 | 142 | integral += (xr - xl) / 6.0 * (f0 + 4 * f1 + f2) 143 | 144 | return integral 145 | 146 | def main(): 147 | 148 | N_fine = 200 149 | 150 | # the number of bins to divide [a, b] 151 | N_bins = 6 152 | 153 | xmin = 0.5 154 | xmax = 1.5 155 | 156 | dx_extra = 0.5 157 | 158 | # add a bin on each end of the domain outside of the integral 159 | xmin_plot = xmin - dx_extra 160 | xmax_plot = xmax + dx_extra 161 | 162 | xfine = np.linspace(xmin_plot, xmax_plot, N_fine+2) 163 | 164 | xp = np.linspace(xmin, xmax, N_bins+1) 165 | 166 | # integral range 167 | a = xmin 168 | b = xmax 169 | 170 | # function points 171 | fp = f(xp) 172 | 173 | 174 | # rectangle method 175 | 176 | plt.clf() 177 | 178 | plot_base(xp, fp, xfine, a, b) 179 | I_r = rectangle(xp, fp, a, b) 180 | 181 | plt.savefig(f"rectangle_N{N_bins}.png", bbox_inches="tight") 182 | 183 | # trapezoid method 184 | 185 | plt.clf() 186 | 187 | plot_base(xp, fp, xfine, a, b) 188 | I_t = trapezoid(xp, fp, a, b) 189 | 190 | plt.savefig(f"trapezoid_N{N_bins}.png", bbox_inches="tight") 191 | 192 | 193 | # simpsons method 194 | 195 | plt.clf() 196 | 197 | xp_tmp = list(xp) 198 | fp_tmp = list(fp) 199 | label_mid = False 200 | 201 | # if N_bins is 1, we need an extra point for Simpsons 202 | if N_bins == 1: 203 | xp_tmp.append((a + b)/2) 204 | fp_tmp.append(f((a + b)/2)) 205 | label_mid = True 206 | 207 | plot_base(np.array(xp_tmp), np.array(fp_tmp), xfine, a, b, 208 | label_mid=label_mid) 209 | 210 | 211 | I_s = simpsons(xp, fp, a, b) 212 | 213 | plt.savefig(f"simpsons_N{N_bins}.png", bbox_inches="tight") 214 | 215 | print(f"integral approximations: {I_r}, {I_t}, {I_s}") 216 | 217 | if __name__ == "__main__": 218 | main() 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /figures/mgtower.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import grid_plot as gp 3 | 4 | # plot two stacked fv grids of different (2x) resolution to show prolongation 5 | 6 | #----------------------------------------------------------------------------- 7 | 8 | gr = [] 9 | 10 | nzones = [2, 4, 8, 16] 11 | for nf in nzones: 12 | gr.append(gp.FVGrid(nf, ng=1, voff=2.0*len(gr))) 13 | 14 | 15 | plt.clf() 16 | 17 | for g in gr: 18 | g.draw_grid(emphasize_end=1, draw_ghost=1, edge_ticks=0) 19 | 20 | f = plt.gcf() 21 | f.set_size_inches(7.0,5.0) 22 | 23 | grf = gr[0] 24 | plt.xlim(grf.xmin-1.1*grf.dx,grf.xmax+1.1*grf.dx) 25 | 26 | plt.axis("off") 27 | 28 | plt.subplots_adjust(left=0.05,right=0.95,bottom=0.05,top=0.95) 29 | 30 | plt.savefig("mgtower.png", dpi=200) 31 | 32 | -------------------------------------------------------------------------------- /figures/riemann.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pylab as plt 3 | import grid_plot as gp 4 | 5 | def riemann(with_time=True): 6 | 7 | # grid info 8 | xmin = 0.0 9 | xmax = 1.0 10 | 11 | nzones = 2 12 | ng = 0 13 | 14 | gr = gp.FVGrid(nzones, xmin=xmin, xmax=xmax) 15 | 16 | plt.clf() 17 | 18 | #------------------------------------------------------------------------ 19 | # plot a domain without ghostcells 20 | gr.draw_grid() 21 | 22 | gr.label_center(0, r"$i$", fontsize="medium") 23 | gr.label_center(1, r"$i+1$", fontsize="medium") 24 | 25 | gr.label_edge(1, r"$i+\myhalf$", fontsize="medium") 26 | 27 | 28 | plt.arrow(gr.xc[0]+0.05*gr.dx, 0.5, 0.12*gr.dx, 0, 29 | shape='full', head_width=0.05, head_length=0.025, 30 | lw=1, width=0.02, 31 | edgecolor="none", facecolor="r", 32 | length_includes_head=True, zorder=100) 33 | 34 | plt.arrow(gr.xc[1]-0.1*gr.dx, 0.5, -0.12*gr.dx, 0, 35 | shape='full', head_width=0.05, head_length=0.025, 36 | lw=1, width=0.02, 37 | edgecolor="none", facecolor="r", 38 | length_includes_head=True, zorder=100) 39 | 40 | if with_time: 41 | gr.mark_cell_left_state(1, r"$a_{i+\myhalf,R}^{n+\myhalf}$", fontsize="large", 42 | color="b") 43 | gr.mark_cell_right_state(0, r"$a_{i+\myhalf,L}^{n+\myhalf}$", fontsize="large", 44 | color="b") 45 | else: 46 | gr.mark_cell_left_state(1, r"$a_{i+\myhalf,R}$", fontsize="large", 47 | color="b") 48 | gr.mark_cell_right_state(0, r"$a_{i+\myhalf,L}$", fontsize="large", 49 | color="b") 50 | 51 | gr.label_cell_center(0, r"$a_i$") 52 | gr.label_cell_center(1, r"$a_{i+1}$") 53 | 54 | 55 | gr.clean_axes(pad_fac=0.125, ylim=(-0.25, 1.0)) 56 | 57 | f = plt.gcf() 58 | f.set_size_inches(7.0,2.0) 59 | 60 | plt.tight_layout() 61 | 62 | if with_time: 63 | plt.savefig("riemann-adv.png") 64 | else: 65 | plt.savefig("riemann-adv-mol.png") 66 | 67 | if __name__== "__main__": 68 | riemann() 69 | riemann(with_time=False) 70 | -------------------------------------------------------------------------------- /figures/rk2_plot.py: -------------------------------------------------------------------------------- 1 | # make a plot of what the 2nd-order Runge-Kutta is doing using the 2 | # example dy/dt = -y (this comes from Garcia). 3 | 4 | from __future__ import print_function 5 | 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | import matplotlib as mpl 9 | 10 | mpl.rcParams['mathtext.fontset'] = 'cm' 11 | mpl.rcParams['mathtext.rm'] = 'serif' 12 | 13 | def rhs(y): 14 | """ return dy/dt """ 15 | return -y 16 | 17 | def exact(t): 18 | """ analytic solution """ 19 | return np.exp(-t) 20 | 21 | 22 | # frame 1 -- show the initial condition (y^n) 23 | 24 | y0 = 1.0 25 | t0 = 0.0 26 | 27 | dt = 0.75 28 | 29 | tt = np.linspace(t0, t0+2.0*dt, 100) 30 | 31 | 32 | def start(): 33 | """ default starting point """ 34 | 35 | plt.plot(tt, exact(tt), label="analytic solution", color="k") 36 | 37 | # draw the timeline 38 | plt.plot(tt, 0*tt, color="k") 39 | 40 | # label the current point 41 | plt.scatter([t0], [y0], color="r") 42 | plt.text(t0, y0+0.03, r"$y^n$", 43 | horizontalalignment="left", color="r", fontsize=20) 44 | 45 | plt.plot([t0,t0], [0, y0], ls=":", color="0.5") 46 | plt.text(t0, -0.05, r"$t^n$", 47 | verticalalignment="top", horizontalalignment="center", 48 | fontsize=20) 49 | 50 | 51 | # illustrate where t^n+1 is 52 | plt.plot([t0+dt,t0+dt], [0, y0], ls=":", color="0.5") 53 | plt.text(t0+dt, -0.05, r"$t^{n+1}$", 54 | verticalalignment="top", horizontalalignment="center", 55 | fontsize=20) 56 | 57 | 58 | start() 59 | 60 | plt.axis("off") 61 | 62 | leg = plt.legend(frameon=False, fontsize="medium", loc=1) 63 | 64 | plt.xlim(t0-0.25*dt, t0+1.5*dt) 65 | plt.ylim(-0.05, 1.2) 66 | 67 | plt.tight_layout() 68 | plt.savefig("rk2_initial.png", bbox_inches='tight', dpi=150) 69 | 70 | 71 | # now draw the solution Euler would get 72 | slope = rhs(y0) 73 | tEuler = np.linspace(t0, t0+dt, 2) 74 | yEuler = y0 + slope*(tEuler-t0) 75 | 76 | plt.plot(tEuler, yEuler, label="Euler step") 77 | 78 | plt.scatter([tEuler[1]], [yEuler[1]], color="r") 79 | plt.text(tEuler[1]+0.015, yEuler[1], r"$y^{n+1}$", 80 | horizontalalignment="left", verticalalignment="center", 81 | color="r", fontsize=20) 82 | 83 | 84 | leg = plt.legend(frameon=False, fontsize="medium", loc=1) 85 | 86 | plt.xlim(t0-0.25*dt, t0+1.5*dt) 87 | plt.ylim(-0.05, 1.2) 88 | 89 | plt.tight_layout() 90 | plt.savefig("rk2_Euler.png", bbox_inches='tight', dpi=150) 91 | 92 | 93 | 94 | # restart and show the 1/2 Euler step 95 | plt.clf() 96 | start() 97 | 98 | plt.axis("off") 99 | 100 | # now draw the solution Euler would get 101 | slope = rhs(y0) 102 | tEuler = np.linspace(t0, t0+0.5*dt, 2) 103 | yEuler = y0 + slope*(tEuler-t0) 104 | 105 | plt.plot(tEuler, yEuler, label="half-dt Euler step") 106 | 107 | plt.scatter([tEuler[1]], [yEuler[1]], color="r") 108 | plt.text(tEuler[1]+0.015, yEuler[1], r"$y^{\star}$", 109 | horizontalalignment="left", verticalalignment="center", 110 | color="r", fontsize=20) 111 | 112 | 113 | # illustrate the slope at that position 114 | slope2 = rhs(yEuler[1]) 115 | tSlope = np.linspace(tEuler[1]-0.2*dt, tEuler[1]+0.2*dt, 2) 116 | ySlope = yEuler[1] + slope2*(tSlope - tEuler[1]) 117 | 118 | plt.plot(tSlope, ySlope, label=r"slope at $y^\star$", color="g", ls="--") 119 | 120 | leg = plt.legend(frameon=False, fontsize="medium", loc=1) 121 | 122 | plt.xlim(t0-0.25*dt, t0+1.5*dt) 123 | plt.ylim(-0.05, 1.2) 124 | 125 | plt.tight_layout() 126 | plt.savefig("rk2_halfEuler.png", bbox_inches='tight', dpi=150) 127 | 128 | # do the RK-2 step 129 | ynew = y0 + dt*slope2 130 | 131 | tFinal = np.linspace(t0, t0+dt, 2) 132 | yRK2 = (ynew - y0)*(tFinal-t0)/dt + y0 133 | 134 | plt.plot(tFinal, yRK2, label=r"R-K 2 solution", color="r") 135 | 136 | plt.scatter(tFinal[1], yRK2[1], color="r") 137 | plt.text(tFinal[1]+0.015, yRK2[1], r"$y^{n+1}$", color="r", fontsize=20) 138 | 139 | leg = plt.legend(frameon=False, fontsize="medium", loc=1) 140 | 141 | plt.xlim(t0-0.25*dt, t0+1.5*dt) 142 | plt.ylim(-0.05, 1.2) 143 | 144 | plt.tight_layout() 145 | plt.savefig("rk2_final.png", bbox_inches='tight', dpi=150) 146 | -------------------------------------------------------------------------------- /figures/rk4_plot.py: -------------------------------------------------------------------------------- 1 | # make a plot of what the 4th-order Runge-Kutta is doing using the 2 | # example dy/dt = -y (this comes from Garcia). 3 | 4 | from __future__ import print_function 5 | 6 | 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import matplotlib as mpl 10 | 11 | mpl.rcParams['mathtext.fontset'] = 'cm' 12 | mpl.rcParams['mathtext.rm'] = 'serif' 13 | 14 | 15 | def rhs(y): 16 | """ return dy/dt """ 17 | return -y 18 | 19 | def exact(t): 20 | """ analytic solution """ 21 | return np.exp(-t) 22 | 23 | 24 | # frame 1 -- show the initial condition (y^n) 25 | 26 | y0 = 1.0 27 | t0 = 0.0 28 | 29 | dt = 0.75 30 | 31 | tt = np.linspace(t0, t0+2.0*dt, 100) 32 | 33 | 34 | def start(): 35 | """ default starting point """ 36 | 37 | plt.plot(tt, exact(tt), label="analytic solution", color="k") 38 | 39 | # draw the timeline 40 | plt.plot(tt, 0*tt, color="k") 41 | 42 | # label the current point 43 | plt.scatter([t0], [y0], color="r") 44 | plt.text(t0, y0+0.03, r"$y^n$", 45 | horizontalalignment="left", color="r", fontsize=20) 46 | 47 | plt.plot([t0,t0], [0, y0], ls=":", color="0.5") 48 | plt.text(t0, -0.05, r"$t^n$", 49 | verticalalignment="top", horizontalalignment="center", 50 | fontsize=20) 51 | 52 | 53 | # illustrate where t^n+1 is 54 | plt.plot([t0+dt,t0+dt], [0, y0], ls=":", color="0.5") 55 | plt.text(t0+dt, -0.05, r"$t^{n+1}$", 56 | verticalalignment="top", horizontalalignment="center", 57 | fontsize=20) 58 | 59 | 60 | start() 61 | 62 | plt.axis("off") 63 | 64 | leg = plt.legend(frameon=False, fontsize="medium", loc=1) 65 | 66 | plt.xlim(t0-0.25*dt, t0+1.5*dt) 67 | plt.ylim(-0.05, 1.2) 68 | 69 | plt.tight_layout() 70 | plt.savefig("rk4_initial.png", bbox_inches='tight', dpi=150) 71 | 72 | 73 | # now draw the solution Euler would get 74 | slope = rhs(y0) 75 | tEuler = np.linspace(t0, t0+dt, 2) 76 | yEuler = y0 + slope*(tEuler-t0) 77 | 78 | plt.plot(tEuler, yEuler, label="Euler step") 79 | 80 | plt.scatter([tEuler[1]], [yEuler[1]], color="r") 81 | plt.text(tEuler[1]+0.015, yEuler[1], r"$y^{n+1}$", 82 | horizontalalignment="left", verticalalignment="center", 83 | color="r", fontsize=20) 84 | 85 | 86 | leg = plt.legend(frameon=False, fontsize="medium", loc=1) 87 | 88 | plt.xlim(t0-0.25*dt, t0+1.5*dt) 89 | plt.ylim(-0.05, 1.2) 90 | 91 | plt.tight_layout() 92 | plt.savefig("rk4_Euler.png", bbox_inches='tight', dpi=150) 93 | 94 | 95 | #---------------------------------------------------------------------------- 96 | # show k1 slope 97 | plt.clf() 98 | start() 99 | 100 | plt.axis("off") 101 | 102 | k1 = rhs(y0) 103 | tSlope = np.linspace(t0-0.2*dt, t0+0.2*dt, 2) 104 | ySlope = y0 + k1*(tSlope - t0) 105 | 106 | plt.plot(tSlope, ySlope, label=r"slope", color="g", ls="--", lw=2, zorder=10) 107 | plt.text(t0-0.1*dt, y0, r"$k_1$", color="g", fontsize="large") 108 | 109 | leg = plt.legend(frameon=False, fontsize="medium", loc=1) 110 | 111 | plt.xlim(t0-0.25*dt, t0+1.5*dt) 112 | plt.ylim(-0.05, 1.2) 113 | 114 | plt.tight_layout() 115 | plt.savefig("rk4_k1.png", bbox_inches='tight', dpi=150) 116 | 117 | 118 | #---------------------------------------------------------------------------- 119 | # follow k1 to define k2 120 | 121 | ytmp = y0 + k1*0.5*dt 122 | k2 = rhs(ytmp) 123 | 124 | # draw the k1 half step 125 | plt.plot([t0, t0+0.5*dt], [y0, ytmp], color="b", label="half-dt k1 step") 126 | plt.scatter(t0+0.5*dt, ytmp, color="b") 127 | 128 | # draw slope there 129 | 130 | tSlope = np.linspace(t0+0.5*dt-0.2*dt, t0+0.5*dt+0.2*dt, 2) 131 | ySlope = ytmp + k2*(tSlope - (t0 + 0.5*dt)) 132 | 133 | plt.plot(tSlope, ySlope, color="g", ls="--", lw=2) 134 | plt.text(t0+0.5*dt-0.1*dt, ytmp, r"$k_2$", color="g", 135 | verticalalignment="top", fontsize="large") 136 | 137 | leg = plt.legend(frameon=False, fontsize="medium", loc=1) 138 | 139 | plt.xlim(t0-0.25*dt, t0+1.5*dt) 140 | plt.ylim(-0.05, 1.2) 141 | 142 | plt.tight_layout() 143 | plt.savefig("rk4_k2.png", bbox_inches='tight', dpi=150) 144 | 145 | 146 | #---------------------------------------------------------------------------- 147 | # follow k2 to define k3 148 | ytmp = y0 + k2*0.5*dt 149 | k3 = rhs(ytmp) 150 | 151 | # draw k2 half step 152 | plt.plot([t0, t0+0.5*dt], [y0, ytmp], color="c", label="half-dt k2 step") 153 | plt.scatter(t0+0.5*dt, ytmp, color="c") 154 | 155 | # draw slope there 156 | ySlope = ytmp + k3*(tSlope - (t0 + 0.5*dt)) 157 | plt.plot(tSlope, ySlope, color="g", ls="--", lw=2) 158 | plt.text(t0+0.5*dt+0.05*dt, ytmp, r"$k_3$", color="g", 159 | verticalalignment="bottom", fontsize="large") 160 | 161 | leg = plt.legend(frameon=False, fontsize="medium", loc=1) 162 | 163 | plt.xlim(t0-0.25*dt, t0+1.5*dt) 164 | plt.ylim(-0.05, 1.2) 165 | 166 | plt.tight_layout() 167 | plt.savefig("rk4_k3.png", bbox_inches='tight', dpi=150) 168 | 169 | 170 | #---------------------------------------------------------------------------- 171 | # follow k3 to define k4 172 | ytmp = y0 + k3*dt 173 | k4 = rhs(ytmp) 174 | 175 | # draw k3 full step 176 | plt.plot([t0, t0+dt], [y0, ytmp], color="0.5", label="full-dt k3 step") 177 | plt.scatter(t0+dt, ytmp, color="0.5") 178 | 179 | # draw slope there 180 | tSlope2 = np.linspace(t0+dt-0.2*dt, t0+dt+0.2*dt, 2) 181 | ySlope = ytmp + k4*(tSlope2 - (t0 + dt)) 182 | plt.plot(tSlope2, ySlope, color="g", ls="--", lw=2) 183 | plt.text(t0+dt-0.1*dt, ytmp, r"$k_4$", color="g", 184 | verticalalignment="top", fontsize="large") 185 | 186 | leg = plt.legend(frameon=False, fontsize="medium", loc=1) 187 | 188 | plt.xlim(t0-0.25*dt, t0+1.5*dt) 189 | plt.ylim(-0.05, 1.2) 190 | 191 | plt.tight_layout() 192 | plt.savefig("rk4_k4.png", bbox_inches='tight', dpi=150) 193 | 194 | 195 | 196 | #---------------------------------------------------------------------------- 197 | # final RK-4 step 198 | ynew = y0 + (dt/6.0)*(k1 + 2.0*k2 + 2.0*k3 + k4) 199 | 200 | 201 | # draw full RK-4 step 202 | plt.plot([t0, t0+dt], [y0, ynew], color="r", label="full 4th-order RK step") 203 | plt.scatter(t0+dt, ynew, color="r") 204 | plt.text(t0+1.05*dt, ynew+0.015, r"$y^{n+1}$", color = "r", 205 | horizontalalignment="left", fontsize=20) 206 | 207 | leg = plt.legend(frameon=False, fontsize="medium", loc=1) 208 | 209 | plt.xlim(t0-0.25*dt, t0+1.5*dt) 210 | plt.ylim(-0.05, 1.2) 211 | 212 | plt.tight_layout() 213 | plt.savefig("rk4_final.png", bbox_inches='tight', dpi=150) 214 | -------------------------------------------------------------------------------- /figures/roots_plot.py: -------------------------------------------------------------------------------- 1 | """ 2 | make plots using the root finding methods in roots.py 3 | 4 | M. Zingale (2013-02-14) 5 | """ 6 | 7 | from __future__ import print_function 8 | 9 | import numpy as np 10 | import matplotlib as mpl 11 | import matplotlib.pyplot as plt 12 | from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes, mark_inset 13 | 14 | mpl.rcParams['xtick.labelsize'] = 14 15 | mpl.rcParams['ytick.labelsize'] = 14 16 | mpl.rcParams['mathtext.fontset'] = 'cm' 17 | mpl.rcParams['mathtext.rm'] = 'serif' 18 | 19 | # sample equations to find the roots of 20 | def f(x): 21 | return 0.5*x**3 + np.pi/3.0 * x + 2 22 | 23 | def fprime(x): 24 | return 1.5*x**2 + np.pi/3.0 25 | 26 | 27 | class Root(object): 28 | """ simple class to manage root finding. All method take in a 29 | function and desired tolerance """ 30 | 31 | def __init__(self, fun, tol=1.e-10, fprime=None): 32 | self.f = fun 33 | self.fprime = fprime 34 | 35 | self.tol = tol 36 | 37 | def newton(self, x0): 38 | """ find the root via Newton's method. x0 is the initial guess 39 | for the root """ 40 | 41 | xeval = [] 42 | 43 | # initial change 44 | dx = -self.f(x0)/self.fprime(x0) 45 | xeval.append(x0) 46 | x = x0 + dx 47 | 48 | while abs(dx) > self.tol: 49 | dx = -self.f(x)/self.fprime(x) 50 | xeval.append(x) 51 | x += dx 52 | 53 | return x, xeval 54 | 55 | r = Root(f, fprime=fprime) 56 | 57 | xmin = -3.5 58 | xmax = 3.5 59 | 60 | xfine = np.linspace(xmin, xmax, 200) 61 | 62 | #--------------------------------------------------------------------------- 63 | # plot Newton 64 | 65 | x0 = 3.0 66 | root, xeval = r.newton(x0) 67 | 68 | print(xeval) 69 | 70 | for n, x in enumerate(xeval[:-1]): 71 | plt.clf() 72 | plt.plot(xfine, r.f(xfine)) 73 | 74 | plt.scatter(np.array(xeval[0:n+1]), 75 | r.f(np.array(xeval[0:n+1])), 76 | marker="o", s=25, color="r", zorder=100) 77 | 78 | plt.plot(xfine, r.fprime(x)*(xfine-x) + r.f(x), color="0.5") 79 | 80 | xintercept = -r.f(x)/r.fprime(x) + x 81 | 82 | plt.scatter([xintercept], [0], marker="x", s=25, color="r", zorder=100) 83 | 84 | if n%2 == 0: 85 | plt.text(x, r.f(x)-0.3, "{}".format(n), 86 | color="r", fontsize="16", 87 | verticalalignment="top", horizontalalignment="center", zorder=1000) 88 | else: 89 | plt.text(x, r.f(x)+0.3, "{}".format(n), 90 | color="r", fontsize="16", 91 | verticalalignment="bottom", horizontalalignment="center", zorder=1000) 92 | 93 | F = plt.gcf() 94 | 95 | plt.text(0.4, 0.08, f"iteration: {n}", 96 | transform = F.transFigure, fontsize="12", color="r") 97 | 98 | plt.text(0.4, 0.05, f"initial guess = {x}", 99 | transform = F.transFigure, fontsize="12", color="r") 100 | 101 | plt.text(0.4, 0.02, f"new guess = {xeval[n+1]}", 102 | transform = F.transFigure, fontsize="12", color="r") 103 | 104 | # axes through origin 105 | ax = plt.gca() 106 | ax.spines['left'].set_position('zero') 107 | ax.spines['right'].set_color('none') 108 | ax.spines['bottom'].set_position('zero') 109 | ax.spines['top'].set_color('none') 110 | #ax.spines['left'].set_smart_bounds(True) 111 | #ax.spines['bottom'].set_smart_bounds(True) 112 | ax.xaxis.set_ticks_position('bottom') 113 | ax.yaxis.set_ticks_position('left') 114 | 115 | plt.xlim(xmin, 1.1*xmax) 116 | plt.ylim(-10.0,20) 117 | 118 | 119 | f = plt.gcf() 120 | f.set_size_inches(7.20,7.20) 121 | 122 | 123 | plt.tight_layout() 124 | 125 | plt.savefig(f"newton_{n:02d}.png") 126 | -------------------------------------------------------------------------------- /figures/vcycle.py: -------------------------------------------------------------------------------- 1 | import networkx as nx 2 | import matplotlib.pyplot as plt 3 | 4 | G = nx.MultiDiGraph() 5 | G.position = {} 6 | G.labels = {} 7 | 8 | nlevels = 5 9 | dx = 0.2 10 | dy = 0.2 11 | 12 | fig = plt.figure() 13 | ax = fig.add_subplot(111) 14 | 15 | # down 16 | 17 | for n in range(nlevels-1): 18 | node = f"d{n}" 19 | print(node) 20 | G.add_node(node) 21 | G.position[node] = (n*dx, -n*dy) 22 | if n == 0: 23 | G.labels[node] = r"$\nabla^2 \phi^h = f^h$" 24 | else: 25 | G.labels[node] = rf"$\nabla^2 e^{{{2**n}h}} = r^{{{2**n}h}}$" 26 | 27 | # bottom 28 | node = "bottom" 29 | G.add_node(node) 30 | G.position[node] = ((nlevels-1)*dx, -(nlevels-1)*dy) 31 | G.labels[node] = "bottom solve" 32 | 33 | # up 34 | 35 | for n in range(nlevels-1): 36 | node = f"u{nlevels-2-n}" 37 | print(node) 38 | G.add_node(node) 39 | G.position[node] = ((nlevels + n)*dx, -(nlevels-2-n)*dy) 40 | if n == nlevels-2: 41 | G.labels[node] = r"$\nabla^2 \phi^h = f^h$" 42 | else: 43 | G.labels[node] = rf"$\nabla^2 e^{{{2**(nlevels-2-n)}h}} = r^{{{2**(nlevels-2-n)}h}}$" 44 | 45 | 46 | # now do the edges 47 | for n in range(nlevels-2): 48 | G.add_edges_from([(f"d{n}", f"d{n+1}")]) 49 | 50 | G.add_edges_from([(f"d{nlevels-2}", "bottom")]) 51 | G.add_edges_from([("bottom", f"u{nlevels-2}")]) 52 | 53 | for n in range(nlevels-2): 54 | start = f"u{nlevels-2-n}" 55 | stop = f"u{nlevels-3-n}" 56 | print(start, stop) 57 | G.add_edges_from([(start, stop)]) 58 | 59 | print(G.position) 60 | 61 | nx.draw(G, G.position, # plot the element at the correct position 62 | node_color="C1", alpha=1.0, 63 | node_shape="o", node_size=500, 64 | width=2, linewidths=2.0, ax=ax) 65 | 66 | edges = [e for e in G.edges] 67 | edge_labels = {} 68 | for e in edges: 69 | if e[0].startswith("d"): 70 | edge_labels[(e[0], e[1])] = "restrict" 71 | else: 72 | edge_labels[(e[0], e[1])] = "prolong" 73 | 74 | nx.draw_networkx_edge_labels(G, G.position, edge_labels=edge_labels, 75 | font_size="12", font_color="C0") 76 | 77 | nx.draw_networkx_labels(G, G.position, labels=G.labels) 78 | 79 | ax.axis("off") 80 | fig.set_size_inches(8, 8) 81 | 82 | fig.tight_layout() 83 | fig.savefig("vcycle.png", dpi=100) 84 | -------------------------------------------------------------------------------- /projects/sedov/riemann_approximate.py: -------------------------------------------------------------------------------- 1 | """ 2 | Solve riemann shock tube problem for a general equation of state using 3 | the method of Colella and Glaz. Use a two shock approximation, and 4 | linearly interpolation between the head and tail of a rarefaction to 5 | treat rarefactions. 6 | 7 | The Riemann problem for the Euler's equation produces 4 states, 8 | separated by the three characteristics (u - cs, u, u + cs): 9 | 10 | 11 | l_1 t l_2 l_3 12 | \ ^ . / 13 | \ *L | . *R / 14 | \ | . / 15 | \ | . / 16 | L \ | . / R 17 | \ | . / 18 | \ |. / 19 | \|./ 20 | ----------+----------------> x 21 | 22 | l_1 = u - cs eigenvalue 23 | l_2 = u eigenvalue (contact) 24 | l_3 = u + cs eigenvalue 25 | 26 | only density jumps across l_2 27 | 28 | References: 29 | 30 | CG: Colella & Glaz 1985, JCP, 59, 264. 31 | 32 | CW: Colella & Woodward 1984, JCP, 54, 174. 33 | 34 | Fry: Fryxell et al. 2000, ApJS, 131, 273. 35 | 36 | Toro: Toro 1999, ``Riemann Solvers and Numerical Methods for Fluid 37 | Dynamcs: A Practical Introduction, 2nd Ed.'', Springer-Verlag 38 | """ 39 | 40 | import numpy as np 41 | import sys 42 | 43 | class State: 44 | """ a simple object to hold a primitive variable state """ 45 | 46 | def __init__(self, p=1.0, u=0.0, rho=1.0): 47 | self.p = p 48 | self.u = u 49 | self.rho = rho 50 | 51 | def __str__(self): 52 | return f"rho: {self.rho}; u: {self.u}; p: {self.p}" 53 | 54 | 55 | def riemann(left_state, right_state, gamma): 56 | 57 | solution = State() 58 | 59 | # some parameters 60 | riemann_tol = 1.e-5 61 | nriem = 15 62 | 63 | smlrho = 1.e-10 64 | smallp = 1.e-10 65 | smallu = 1.e-10 66 | 67 | rho_l = left_state.rho 68 | u_l = left_state.u 69 | p_l = max(left_state.p, smallp) 70 | 71 | rho_r = right_state.rho 72 | u_r = right_state.u 73 | p_r = max(right_state.p, smallp) 74 | 75 | # specific volume 76 | tau_l = 1./max(rho_l, smlrho) 77 | tau_r = 1./max(rho_r, smlrho) 78 | 79 | c_l = np.sqrt(gamma*p_l*rho_l) 80 | c_r = np.sqrt(gamma*p_r*rho_r) 81 | 82 | # construct first guess for secant iteration by assuming that the 83 | # nonlinear wave speed is equal to the sound speed -- the resulting 84 | # expression is the same as Toro, Eq. 9.28 in the Primitive Variable 85 | # Riemann Solver (PVRS). See also Fry Eq. 72. 86 | pstar1 = p_r - p_l - c_r*(u_r - u_l) 87 | pstar1 = p_l + pstar1*(c_l/(c_l + c_r)) 88 | pstar1 = max(smallp, pstar1) 89 | 90 | # calculate nonlinear wave speeds for the left and right moving 91 | # waves based on the first guess for the pressure jump. Again, 92 | # there is a left and a right wave speed. Compute this using CG 93 | # Eq. 34. 94 | 95 | # note -- we simplify a lot here, assuming constant gamma 96 | w_l1 = pstar1 + 0.5*(gamma - 1.0)*(pstar1 + p_l) 97 | w_l1 = np.sqrt(rho_l*abs(w_l1)) 98 | 99 | w_r1 = pstar1 + 0.5*(gamma - 1.0)*(pstar1 + p_r) 100 | w_r1 = np.sqrt(rho_r*abs(w_r1)) 101 | 102 | # construct second guess for the pressure using the nonlinear wave 103 | # speeds from the first guess. This is basically the same thing we 104 | # did to get pstar1, except now we are using the better wave speeds 105 | # instead of the sound speed. 106 | pstar2 = p_r - p_l - w_r1*(u_r - u_l) 107 | pstar2 = p_l + pstar2*w_l1/(w_l1 + w_r1) 108 | pstar2 = max(smallp, pstar2) 109 | 110 | # begin the secant iteration -- see CG Eq. 17 for details. We will 111 | # continue to interate for convergence until the error falls below 112 | # tol (in which case, things are good), or we hit nriem iterations 113 | # (in which case we have a problem, and we spit out an error). 114 | has_converged = False 115 | 116 | for n in range(nriem): 117 | 118 | # new nonlinear wave speeds, using CG Eq. 34 119 | w_l = pstar2 + 0.5*(gamma - 1.)*(pstar2 + p_l) 120 | w_l = np.sqrt(rho_l*abs(w_l)) 121 | 122 | w_r = pstar2 + 0.5*(gamma - 1.)*(pstar2 + p_r) 123 | w_r = np.sqrt(rho_r*abs(w_r)) 124 | 125 | # compute the velocities in the "star" state -- using CG 126 | # Eq. 18 -- ustar_l2 and ustar_r2 are the velocities they define 127 | # there. ustar_l1 and ustar_l2 seem to be the velocities at the 128 | # last time, since pstar1 is the old 'star' pressure, and 129 | # w_l1 is the old wave speed. 130 | ustar_l1 = u_l - (pstar1 - p_l)/w_l1 131 | ustar_r1 = u_r + (pstar1 - p_r)/w_r1 132 | ustar_l2 = u_l - (pstar2 - p_l)/w_l 133 | ustar_r2 = u_r + (pstar2 - p_r)/w_r 134 | 135 | delu1 = ustar_l1 - ustar_r1 136 | delu2 = ustar_l2 - ustar_r2 137 | 138 | scratch = delu2 - delu1 139 | 140 | if abs(pstar2 - pstar1) <= smallp: 141 | scratch = 0. 142 | 143 | if abs(scratch) < smallu: 144 | delu2 = 0. 145 | scratch = 1. 146 | 147 | # pressure at the "star" state -- using CG Eq. 18 148 | pstar = pstar2 - delu2*(pstar2 - pstar1)/scratch 149 | pstar = max(smallp, pstar) 150 | 151 | # check for convergence of iteration 152 | pres_err = abs(pstar - pstar2)/pstar 153 | if pres_err < riemann_tol: 154 | has_converged = True 155 | break 156 | 157 | # reset variables for next iteration 158 | pstar1 = pstar2 159 | pstar2 = pstar 160 | 161 | w_l1 = w_l 162 | w_r1 = w_r 163 | 164 | 165 | if not has_converged: 166 | print("Nonconvergence in subroutine rieman!") 167 | print("Pressure error = ", pres_err) 168 | print("pL = ", p_l, " pR = ", p_r) 169 | print("uL = ", u_l, " uR = ", u_r) 170 | print("cL = ", c_l, " c_r = ", c_r) 171 | print("Terminating execution") 172 | sys.exit("stopping") 173 | 174 | # end of secant iteration 175 | 176 | # calculate fluid velocity for the "star" state -- this comes from 177 | # the shock jump equations, Fry Eq. 68 and 69. The ustar velocity 178 | # can be computed using either the jump eq. for a left moving or 179 | # right moving shock -- we use the average of the two. 180 | 181 | scratch = u_l - (pstar - p_l)/w_l 182 | scratch2 = u_r + (pstar - p_r)/w_r 183 | ustar = 0.5*(scratch + scratch2) 184 | 185 | if ustar < 0: 186 | ustar_sgn = -1.0 187 | elif ustar == 0.0: 188 | ustar_sgn = 0.0 189 | else: 190 | ustar_sgn = 1.0 191 | 192 | # decide which state is located at the zone iterface based on 193 | # the values of the wave speeds. This is just saying that if 194 | # ustar > 0, then the state is U_L. if ustar < 0, then the 195 | # state on the axis is U_R. 196 | scratch = 0.5*(1.0 + ustar_sgn) 197 | scratch2 = 0.5*(1.0 - ustar_sgn) 198 | 199 | ps = p_l*scratch + p_r*scratch2 200 | us = u_l*scratch + u_r*scratch2 201 | vs = tau_l*scratch + tau_r*scratch2 202 | 203 | rhos = 1.0/vs 204 | rhos = max(smlrho, rhos) 205 | 206 | vs = 1.0/rhos 207 | ws = w_l*scratch + w_r*scratch2 208 | ces = np.sqrt(gamma*ps*vs) 209 | 210 | # compute rhostar, using the shock jump condition (Fry Eq. 80) 211 | vstar = vs - (pstar - ps)/(ws*ws) 212 | rhostr = 1.0/ vstar 213 | cestar = np.sqrt(gamma*pstar*vstar) 214 | 215 | # compute some factors, Fry Eq. 81 and 82 216 | wes = ces - ustar_sgn*us 217 | westar = cestar - ustar_sgn*ustar 218 | 219 | scratch = ws*vs - ustar_sgn*us 220 | 221 | if pstar - ps >= 0.0: 222 | wes = scratch 223 | westar = scratch 224 | 225 | # compute correct state for rarefaction fan by linear interpolation 226 | scratch = max(wes - westar, wes + westar) 227 | scratch = max(scratch, smallu) 228 | 229 | scratch = (wes + westar)/scratch 230 | 231 | scratch = 0.5*(1.0 + scratch) 232 | scratch2 = 1.0 - scratch 233 | 234 | rhoav = scratch*rhostr + scratch2*rhos 235 | uav = scratch*ustar + scratch2*us 236 | pav = scratch*pstar + scratch2*ps 237 | 238 | if westar >= 0.0: 239 | rhoav = rhostr 240 | uav = ustar 241 | pav = pstar 242 | 243 | if wes < 0.0: 244 | rhoav = rhos 245 | uav = us 246 | pav = ps 247 | 248 | # now compute the fluxes 249 | solution.rho = rhoav 250 | solution.u = uav 251 | solution.p = pav 252 | 253 | return solution 254 | -------------------------------------------------------------------------------- /projects/sedov/riemann_exact.py: -------------------------------------------------------------------------------- 1 | """An exact Riemann solver for the Euler equations with a gamma-law 2 | gas. The left and right states are stored as State objects. We then 3 | create a RiemannProblem object with the left and right state: 4 | 5 | > rp = RiemannProblem(left_state, right_state) 6 | 7 | Next we solve for the star state: 8 | 9 | > rp.find_star_state() 10 | 11 | Finally, we sample the solution to find the interface state, which 12 | is returned as a State object: 13 | 14 | > q_int = rp.sample_solution() 15 | """ 16 | 17 | import numpy as np 18 | import scipy.optimize as optimize 19 | 20 | class State: 21 | """ a simple object to hold a primitive variable state """ 22 | 23 | def __init__(self, p=1.0, u=0.0, rho=1.0): 24 | self.p = p 25 | self.u = u 26 | self.rho = rho 27 | 28 | def __str__(self): 29 | return f"rho: {self.rho}; u: {self.u}; p: {self.p}" 30 | 31 | class RiemannProblem: 32 | """ a class to define a Riemann problem. It takes a left 33 | and right state. Note: we assume a constant gamma """ 34 | 35 | def __init__(self, left_state, right_state, gamma=1.4): 36 | self.left = left_state 37 | self.right = right_state 38 | self.gamma = gamma 39 | 40 | self.ustar = None 41 | self.pstar = None 42 | 43 | def __str__(self): 44 | return f"pstar = {self.pstar}, ustar = {self.ustar}" 45 | 46 | def u_hugoniot(self, p, side, shock=False): 47 | """define the Hugoniot curve, u(p).""" 48 | 49 | if side == "left": 50 | state = self.left 51 | s = 1.0 52 | elif side == "right": 53 | state = self.right 54 | s = -1.0 55 | 56 | c = np.sqrt(self.gamma*state.p/state.rho) 57 | 58 | if shock: 59 | # shock 60 | beta = (self.gamma+1.0)/(self.gamma-1.0) 61 | u = state.u + s*(2.0*c/np.sqrt(2.0*self.gamma*(self.gamma-1.0)))* \ 62 | (1.0 - p/state.p)/np.sqrt(1.0 + beta*p/state.p) 63 | 64 | else: 65 | if p < state.p: 66 | # rarefaction 67 | u = state.u + s*(2.0*c/(self.gamma-1.0))* \ 68 | (1.0 - (p/state.p)**((self.gamma-1.0)/(2.0*self.gamma))) 69 | else: 70 | # shock 71 | beta = (self.gamma+1.0)/(self.gamma-1.0) 72 | u = state.u + s*(2.0*c/np.sqrt(2.0*self.gamma*(self.gamma-1.0)))* \ 73 | (1.0 - p/state.p)/np.sqrt(1.0 + beta*p/state.p) 74 | 75 | return u 76 | 77 | def find_star_state(self, p_min=0.001, p_max=1000.0): 78 | """ root find the Hugoniot curve to find ustar, pstar """ 79 | 80 | # we need to root-find on 81 | self.pstar = optimize.brentq( 82 | lambda p: self.u_hugoniot(p, "left") - self.u_hugoniot(p, "right"), 83 | p_min, p_max) 84 | self.ustar = self.u_hugoniot(self.pstar, "left") 85 | 86 | def find_2shock_star_state(self, p_min=0.001, p_max=1000.0): 87 | """ root find the Hugoniot curve to find ustar, pstar """ 88 | 89 | # we need to root-find on 90 | self.pstar = optimize.brentq( 91 | lambda p: self.u_hugoniot(p, "left", shock=True) - self.u_hugoniot(p, "right", shock=True), 92 | p_min, p_max) 93 | self.ustar = self.u_hugoniot(self.pstar, "left", shock=True) 94 | 95 | def shock_solution(self, sgn, state): 96 | """return the interface solution considering a shock""" 97 | 98 | p_ratio = self.pstar/state.p 99 | c = np.sqrt(self.gamma*state.p/state.rho) 100 | 101 | # Toro, eq. 4.52 / 4.59 102 | S = state.u + sgn*c*np.sqrt(0.5*(self.gamma + 1.0)/self.gamma*p_ratio + 103 | 0.5*(self.gamma - 1.0)/self.gamma) 104 | 105 | # are we to the left or right of the shock? 106 | if (self.ustar < 0 and S < 0) or (self.ustar > 0 and S > 0): 107 | # R/L region 108 | solution = state 109 | else: 110 | # * region -- get rhostar from Toro, eq. 4.50 / 4.57 111 | gam_fac = (self.gamma - 1.0)/(self.gamma + 1.0) 112 | rhostar = state.rho * (p_ratio + gam_fac)/(gam_fac * p_ratio + 1.0) 113 | solution = State(rho=rhostar, u=self.ustar, p=self.pstar) 114 | 115 | return solution 116 | 117 | def rarefaction_solution(self, sgn, state): 118 | """return the interface solution considering a rarefaction wave""" 119 | 120 | # find the speed of the head and tail of the rarefaction fan 121 | 122 | # isentropic (Toro eq. 4.54 / 4.61) 123 | p_ratio = self.pstar/state.p 124 | c = np.sqrt(self.gamma*state.p/state.rho) 125 | cstar = c*p_ratio**((self.gamma-1.0)/(2*self.gamma)) 126 | 127 | lambda_head = state.u + sgn*c 128 | lambda_tail = self.ustar + sgn*cstar 129 | 130 | gam_fac = (self.gamma - 1.0)/(self.gamma + 1.0) 131 | 132 | if (sgn > 0 and lambda_head < 0) or (sgn < 0 and lambda_head > 0): 133 | # R/L region 134 | solution = state 135 | 136 | elif (sgn > 0 and lambda_tail > 0) or (sgn < 0 and lambda_tail < 0): 137 | # * region, we use the isentropic density (Toro 4.53 / 4.60) 138 | solution = State(rho = state.rho*p_ratio**(1.0/self.gamma), 139 | u = self.ustar, p = self.pstar) 140 | 141 | else: 142 | # we are in the fan -- Toro 4.56 / 4.63 143 | rho = state.rho * (2/(self.gamma + 1.0) - 144 | sgn*gam_fac*state.u/c)**(2.0/(self.gamma-1.0)) 145 | u = 2.0/(self.gamma + 1.0) * ( -sgn*c + 0.5*(self.gamma - 1.0)*state.u) 146 | p = state.p * (2/(self.gamma + 1.0) - 147 | sgn*gam_fac*state.u/c)**(2.0*self.gamma/(self.gamma-1.0)) 148 | solution = State(rho=rho, u=u, p=p) 149 | 150 | return solution 151 | 152 | def sample_solution(self): 153 | """given the star state (ustar, pstar), find the state on the interface""" 154 | 155 | if self.ustar < 0: 156 | # we are in the R* or R region 157 | state = self.right 158 | sgn = 1.0 159 | else: 160 | # we are in the L* or L region 161 | state = self.left 162 | sgn = -1.0 163 | 164 | # is the non-contact wave a shock or rarefaction? 165 | if self.pstar > state.p: 166 | # compression! we are a shock 167 | solution = self.shock_solution(sgn, state) 168 | 169 | else: 170 | # rarefaction 171 | solution = self.rarefaction_solution(sgn, state) 172 | 173 | return solution 174 | 175 | 176 | if __name__ == "__main__": 177 | 178 | q_l = State(rho=1.0, u=0.0, p=1.0) 179 | q_r = State(rho=0.125, u=0.0, p=0.1) 180 | 181 | rp = RiemannProblem(q_l, q_r, gamma=1.4) 182 | 183 | rp.find_star_state() 184 | q_int = rp.sample_solution() 185 | print(q_int) 186 | -------------------------------------------------------------------------------- /projects/sedov/sedov.py: -------------------------------------------------------------------------------- 1 | # this solves the 1-d spherical Euler equations, using a 2 | # method-of-lines integration approach, building on the 1-d solver we 3 | # did in the tutorial 4 | 5 | import matplotlib.pyplot as plt 6 | import numpy as np 7 | import riemann_exact as re 8 | import riemann_approximate as ra 9 | 10 | import sys 11 | 12 | class FluidVars: 13 | """A simple container that holds the integer indicies we will use to 14 | refer to the different fluid components""" 15 | def __init__(self, gamma=1.4, C=0.8): 16 | self.nvar = 3 17 | 18 | # conserved variables 19 | self.urho = 0 20 | self.umx = 1 21 | self.uener = 2 22 | 23 | # primitive variables 24 | self.qrho = 0 25 | self.qu = 1 26 | self.qp = 2 27 | 28 | # EOS gamma 29 | self.gamma = gamma 30 | 31 | # CFL number 32 | self.C = C 33 | 34 | class FVGrid: 35 | """The main finite-volume grid class for holding our fluid state.""" 36 | 37 | def __init__(self, nr, ng, rmin=0.0, rmax=1.0, fvars=None, 38 | small_dens=1.e-10, small_pres=1.e-20): 39 | 40 | self.small_dens = small_dens 41 | self.small_pres = small_pres 42 | 43 | self.rmin = rmin 44 | self.rmax = rmax 45 | self.ng = ng 46 | self.nr = nr 47 | 48 | self.lo = ng 49 | self.hi = ng+nr-1 50 | 51 | # physical coords -- cell-centered 52 | self.dr = (rmax - rmin)/(nr) 53 | self.rl = rmin + (np.arange(nr+2*ng)-ng)*self.dr 54 | self.rr = rmin + (np.arange(nr+2*ng)-ng+1.0)*self.dr 55 | self.r = rmin + (np.arange(nr+2*ng)-ng+0.5)*self.dr 56 | 57 | self.v = fvars 58 | 59 | def scratch_array(self, nc=1): 60 | """ return a scratch array dimensioned for our grid """ 61 | return np.squeeze(np.zeros((self.nr+2*self.ng, nc), dtype=np.float64)) 62 | 63 | def fill_BCs(self, atmp): 64 | """we do reflecting at r = 0 and outflow at r = rmax""" 65 | 66 | # lower boundary 67 | for i in range(self.ng): 68 | atmp[self.lo-1-i, self.v.urho] = atmp[self.lo+i, self.v.urho] 69 | atmp[self.lo-1-i, self.v.umx] = -atmp[self.lo+i, self.v.umx] 70 | atmp[self.lo-1-i, self.v.uener] = atmp[self.lo+i, self.v.uener] 71 | 72 | for n in range(self.v.nvar): 73 | atmp[self.hi+1:, n] = atmp[self.hi, n] 74 | 75 | def cons_to_prim(g, U, ): 76 | """take a conservative state U and return the corresponding primitive 77 | variable state as a new array.""" 78 | q = g.scratch_array(nc=g.v.nvar) 79 | 80 | q[:, g.v.qrho] = np.maximum(U[:, g.v.urho], g.small_dens) 81 | q[:, g.v.qu] = U[:, g.v.umx]/U[:, g.v.urho] 82 | rhoe = U[:, g.v.uener] - 0.5*q[:, g.v.qrho]*q[:, g.v.qu]**2 83 | q[:, g.v.qp] = np.maximum(rhoe*(g.v.gamma - 1.0), g.small_pres) 84 | 85 | return q 86 | 87 | def states(g, U): 88 | 89 | q = cons_to_prim(g, U) 90 | 91 | # construct the slopes 92 | dq = g.scratch_array(nc=g.v.nvar) 93 | 94 | for n in range(g.v.nvar): 95 | dl = g.scratch_array() 96 | dr = g.scratch_array() 97 | dc = g.scratch_array() 98 | 99 | dl[g.lo-1:g.hi+2] = q[g.lo-1:g.hi+2,n] - q[g.lo-2:g.hi+1,n] 100 | dr[g.lo-1:g.hi+2] = q[g.lo:g.hi+3,n] - q[g.lo-1:g.hi+2,n] 101 | dc[g.lo-1:g.hi+2] = 0.5 * (q[g.lo:g.hi+3,n] - q[g.lo-2:g.hi+1,n]) 102 | 103 | # these where's do a minmod() 104 | d1 = 2.0 * np.where(np.fabs(dl) < np.fabs(dr), dl, dr) 105 | d2 = np.where(np.fabs(dc) < np.fabs(d1), dc, d1) 106 | dq[:, n] = np.where(dl*dr > 0.0, d2, 0.0) 107 | 108 | # now make the states 109 | q_l = g.scratch_array(nc=g.v.nvar) 110 | q_l[g.lo:g.hi+2, :] = q[g.lo-1:g.hi+1, :] + 0.5*dq[g.lo-1:g.hi+1, :] 111 | 112 | q_r = g.scratch_array(nc=g.v.nvar) 113 | q_r[g.lo:g.hi+2, :] = q[g.lo:g.hi+2, :] - 0.5*dq[g.lo:g.hi+2, :] 114 | 115 | return q_l, q_r 116 | 117 | def cons_flux(state, v): 118 | """ given an interface state, return the conservative flux""" 119 | flux = np.zeros((v.nvar), dtype=np.float64) 120 | 121 | flux[v.urho] = state.rho * state.u 122 | 123 | # note in spherical coords, we don't add the pressure to the 124 | # momentum flux, because it is not a divergence 125 | flux[v.umx] = flux[v.urho] * state.u 126 | 127 | flux[v.uener] = (0.5 * state.rho * state.u**2 + 128 | state.p/(v.gamma - 1.0) + state.p) * state.u 129 | return flux 130 | 131 | def make_flux_divergence(g, U): 132 | 133 | # get the states 134 | q_l, q_r = states(g, U) 135 | 136 | # now solve the Riemann problem 137 | flux = g.scratch_array(nc=g.v.nvar) 138 | 139 | # store the interface pressure for the pressure gradient 140 | p_int = g.scratch_array() 141 | 142 | for i in range(g.lo, g.hi+2): 143 | sl = ra.State(rho=q_l[i, g.v.qrho], u=q_l[i, g.v.qu], p=q_l[i, g.v.qp]) 144 | sr = ra.State(rho=q_r[i, g.v.qrho], u=q_r[i, g.v.qu], p=q_r[i, g.v.qp]) 145 | 146 | #rp = re.RiemannProblem(sl, sr, gamma=g.v.gamma) 147 | #rp.find_2shock_star_state(p_min=1.e-10, p_max=1.e10) 148 | #q_int = rp.sample_solution() 149 | 150 | q_int = ra.riemann(sl, sr, g.v.gamma) 151 | flux[i, :] = cons_flux(q_int, g.v) 152 | p_int[i] = q_int.p 153 | if (p_int[i] < 0): 154 | sys.exit(f"p < 0 at i = {i}") 155 | if (i == g.lo): 156 | flux[i, :] = 0.0 157 | 158 | A = g.scratch_array(nc=g.v.nvar) 159 | for n in range(g.v.nvar): 160 | A[g.lo:g.hi+1, n] = (1/g.r[g.lo:g.hi+1]**2) * ( 161 | g.rl[g.lo:g.hi+1]**2 * flux[g.lo:g.hi+1, n] - 162 | g.rr[g.lo+1:g.hi+2]**2 * flux[g.lo+1:g.hi+2, n]) / g.dr 163 | 164 | # now add the pressure gradient 165 | 166 | A[g.lo:g.hi+1, g.v.umx] -= (p_int[g.lo+1:g.hi+2] - p_int[g.lo:g.hi+1]) / g.dr 167 | 168 | return A 169 | 170 | def timestep(g, U): 171 | 172 | # compute the sound speed 173 | q = cons_to_prim(g, U) 174 | c = g.scratch_array() 175 | c[g.lo:g.hi+1] = np.sqrt(g.v.gamma * 176 | q[g.lo:g.hi+1, g.v.qp] / 177 | q[g.lo:g.hi+1, g.v.qrho]) 178 | 179 | dt = g.v.C * g.dr / (np.abs(q[g.lo:g.hi+1, g.v.qu]) + 180 | c[g.lo:g.hi+1]).max() 181 | return dt 182 | 183 | def mol_solve(nx, C=0.4, init_shrink=0.01, change_max=1.1, 184 | tmax=1.0, init_cond=None, method="ssprk3"): 185 | """Perform 2nd order MOL integration of the Euler equations. 186 | You need to pass in a function foo(grid) that returns the 187 | initial conserved fluid state.""" 188 | 189 | v = FluidVars(C=C) 190 | 191 | grid = FVGrid(nx, 2, fvars=v) 192 | 193 | U = init_cond(grid) 194 | 195 | t = 0.0 196 | 197 | dt = init_shrink * timestep(grid, U) 198 | 199 | while t < tmax: 200 | 201 | q = cons_to_prim(grid, U) 202 | print(" rho: ", q[grid.lo:grid.hi+1, grid.v.qrho].min(), q[grid.lo:grid.hi+1, grid.v.qrho].max()) 203 | print(" u: ", q[grid.lo:grid.hi+1, grid.v.qu].min(), q[grid.lo:grid.hi+1, grid.v.qu].max()) 204 | print(" p: ", q[grid.lo:grid.hi+1, grid.v.qp].min(), q[grid.lo:grid.hi+1, grid.v.qp].max()) 205 | 206 | if t + dt > tmax: 207 | dt = tmax - t 208 | 209 | 210 | if method == "rk2": 211 | 212 | grid.fill_BCs(U) 213 | 214 | k1 = make_flux_divergence(grid, U) 215 | 216 | U_tmp = grid.scratch_array(nc=v.nvar) 217 | for n in range(v.nvar): 218 | U_tmp[:, n] = U[:, n] + 0.5 * dt * k1[:, n] 219 | 220 | grid.fill_BCs(U_tmp) 221 | 222 | k2 = make_flux_divergence(grid, U_tmp) 223 | 224 | for n in range(v.nvar): 225 | U[:, n] += dt * k2[:, n] 226 | 227 | elif method == "ssprk3": 228 | 229 | grid.fill_BCs(U) 230 | k1 = make_flux_divergence(grid, U) 231 | 232 | U1 = grid.scratch_array(nc=v.nvar) 233 | for n in range(v.nvar): 234 | U1[:, n] = U[:, n] + dt * k1[:, n] 235 | 236 | grid.fill_BCs(U1) 237 | 238 | k2 = make_flux_divergence(grid, U1) 239 | 240 | U2 = grid.scratch_array(nc=v.nvar) 241 | for n in range(v.nvar): 242 | U2[:,n] = U[:,n] + 0.25 * dt * k1[:,n] + 0.25 * dt * k2[:,n] 243 | 244 | grid.fill_BCs(U2) 245 | 246 | k3 = make_flux_divergence(grid, U2) 247 | 248 | for n in range(v.nvar): 249 | U[:,n] += (dt/6) * k1[:,n] + (dt/6) * k2[:,n] + (2*dt/3) * k3[:,n] 250 | 251 | elif method == "rk4": 252 | 253 | U_tmp = grid.scratch_array(nc=v.nvar) 254 | 255 | grid.fill_BCs(U) 256 | k1 = make_flux_divergence(grid, U) 257 | 258 | for n in range(v.nvar): 259 | U_tmp[:, n] = U[:, n] + 0.5 * dt * k1[:, n] 260 | 261 | grid.fill_BCs(U_tmp) 262 | k2 = make_flux_divergence(grid, U_tmp) 263 | 264 | for n in range(v.nvar): 265 | U_tmp[:, n] = U[:, n] + 0.5 * dt * k2[:, n] 266 | 267 | grid.fill_BCs(U_tmp) 268 | k3 = make_flux_divergence(grid, U_tmp) 269 | 270 | for n in range(v.nvar): 271 | U_tmp[:, n] = U[:, n] + dt * k3[:, n] 272 | 273 | grid.fill_BCs(U_tmp) 274 | k4 = make_flux_divergence(grid, U_tmp) 275 | 276 | for n in range(v.nvar): 277 | U[:, n] += dt/6.0 * (k1[:, n] + 2.0*k2[:, n] + 2.0*k3[:, n] + k4[:, n]) 278 | 279 | 280 | 281 | #for n in range(v.nvar): 282 | # U[:, n] += dt * k1[:, n] 283 | 284 | # prevent rho from being negative 285 | #np.clip(U[:, grid.v.qrho], 1.e-10, None) 286 | 287 | t += dt 288 | print(t) 289 | 290 | dt = min(1.1*dt, timestep(grid, U)) 291 | 292 | 293 | return grid, U 294 | 295 | def sedov(g): 296 | 297 | U = g.scratch_array(nc=g.v.nvar) 298 | 299 | # setup initial conditions -- this is Sod's problem 300 | rho_ambient = 1.0 301 | p_ambient = 1.e-5 302 | u_ambient = 0.0 303 | 304 | r_0 = 1.0/128 305 | 306 | E_expl = 1.0 307 | p_expl = (g.v.gamma - 1.0) * E_expl / (4.0/3.0 * np.pi * r_0**3) 308 | 309 | idx_l = g.r < r_0 310 | idx_r = g.r >= r_0 311 | 312 | U[idx_l, g.v.urho] = rho_ambient 313 | U[idx_l, g.v.umx] = rho_ambient * u_ambient 314 | U[idx_l, g.v.uener] = p_expl / (g.v.gamma - 1.0) + 0.5 * rho_ambient * u_ambient**2 315 | 316 | U[idx_r, g.v.urho] = rho_ambient 317 | U[idx_r, g.v.umx] = rho_ambient * u_ambient 318 | U[idx_r, g.v.uener] = p_ambient / (g.v.gamma - 1.0) + 0.5 * rho_ambient * u_ambient**2 319 | 320 | return U 321 | 322 | if __name__ == "__main__": 323 | 324 | g, U = mol_solve(256, C=0.4, tmax=0.01, init_cond=sedov, method="rk4") 325 | 326 | print(g.rl[g.lo:g.lo+4]) 327 | print(g.r[g.lo:g.lo+4]) 328 | print(g.rr[g.lo:g.lo+4]) 329 | 330 | sod_exact = np.genfromtxt("sod-exact.out", skip_header=2, names=True) 331 | 332 | v = FluidVars() 333 | 334 | q = cons_to_prim(g, U) 335 | fig = plt.figure() 336 | 337 | ax = fig.add_subplot(311) 338 | ax.plot(g.r[g.lo:g.hi+1], q[g.lo:g.hi+1,v.qrho], marker="x", color="C0") 339 | #ax.plot(sod_exact["x"], sod_exact["rho"], color="C1") 340 | 341 | ax = fig.add_subplot(312) 342 | ax.plot(g.r[g.lo:g.hi+1], q[g.lo:g.hi+1,v.qu], marker="x", color="C0") 343 | #ax.plot(sod_exact["x"], sod_exact["u"], color="C1") 344 | 345 | ax = fig.add_subplot(313) 346 | ax.plot(g.r[g.lo:g.hi+1], q[g.lo:g.hi+1,v.qp], marker="x", color="C0") 347 | #ax.plot(sod_exact["x"], sod_exact["p"], color="C1") 348 | 349 | fig.set_size_inches((6, 10)) 350 | fig.savefig("test.png") 351 | -------------------------------------------------------------------------------- /projects/sedov/sod-exact.out: -------------------------------------------------------------------------------- 1 | # Exact solution for the Sod problem at t = 0.2 s, using Toro's exact Riemann 2 | # solver (Ch. 4) and gamma = 1.4 3 | x rho u p e 4 | 0.003906 1.000000 0.000000 1.000000 2.500000 5 | 0.011719 1.000000 0.000000 1.000000 2.500000 6 | 0.019531 1.000000 0.000000 1.000000 2.500000 7 | 0.027344 1.000000 0.000000 1.000000 2.500000 8 | 0.035156 1.000000 0.000000 1.000000 2.500000 9 | 0.042969 1.000000 0.000000 1.000000 2.500000 10 | 0.050781 1.000000 0.000000 1.000000 2.500000 11 | 0.058594 1.000000 0.000000 1.000000 2.500000 12 | 0.066406 1.000000 0.000000 1.000000 2.500000 13 | 0.074219 1.000000 0.000000 1.000000 2.500000 14 | 0.082031 1.000000 0.000000 1.000000 2.500000 15 | 0.089844 1.000000 0.000000 1.000000 2.500000 16 | 0.097656 1.000000 0.000000 1.000000 2.500000 17 | 0.105469 1.000000 0.000000 1.000000 2.500000 18 | 0.113281 1.000000 0.000000 1.000000 2.500000 19 | 0.121094 1.000000 0.000000 1.000000 2.500000 20 | 0.128906 1.000000 0.000000 1.000000 2.500000 21 | 0.136719 1.000000 0.000000 1.000000 2.500000 22 | 0.144531 1.000000 0.000000 1.000000 2.500000 23 | 0.152344 1.000000 0.000000 1.000000 2.500000 24 | 0.160156 1.000000 0.000000 1.000000 2.500000 25 | 0.167969 1.000000 0.000000 1.000000 2.500000 26 | 0.175781 1.000000 0.000000 1.000000 2.500000 27 | 0.183594 1.000000 0.000000 1.000000 2.500000 28 | 0.191406 1.000000 0.000000 1.000000 2.500000 29 | 0.199219 1.000000 0.000000 1.000000 2.500000 30 | 0.207031 1.000000 0.000000 1.000000 2.500000 31 | 0.214844 1.000000 0.000000 1.000000 2.500000 32 | 0.222656 1.000000 0.000000 1.000000 2.500000 33 | 0.230469 1.000000 0.000000 1.000000 2.500000 34 | 0.238281 1.000000 0.000000 1.000000 2.500000 35 | 0.246094 1.000000 0.000000 1.000000 2.500000 36 | 0.253906 1.000000 0.000000 1.000000 2.500000 37 | 0.261719 1.000000 0.000000 1.000000 2.500000 38 | 0.269531 0.978445 0.025727 0.969954 2.478304 39 | 0.277344 0.951706 0.058279 0.933048 2.450988 40 | 0.285156 0.925555 0.090831 0.897353 2.423823 41 | 0.292969 0.899982 0.123383 0.862834 2.396810 42 | 0.300781 0.874977 0.155935 0.829460 2.369948 43 | 0.308594 0.850532 0.188487 0.797199 2.343237 44 | 0.316406 0.826635 0.221039 0.766019 2.316678 45 | 0.324219 0.803279 0.253591 0.735890 2.290270 46 | 0.332031 0.780454 0.286144 0.706783 2.264013 47 | 0.339844 0.758151 0.318696 0.678668 2.237908 48 | 0.347656 0.736360 0.351248 0.651518 2.211954 49 | 0.355469 0.715073 0.383800 0.625304 2.186152 50 | 0.363281 0.694282 0.416352 0.599999 2.160501 51 | 0.371094 0.673977 0.448904 0.575577 2.135001 52 | 0.378906 0.654150 0.481456 0.552012 2.109653 53 | 0.386719 0.634792 0.514008 0.529278 2.084456 54 | 0.394531 0.615896 0.546560 0.507353 2.059410 55 | 0.402344 0.597452 0.579112 0.486210 2.034516 56 | 0.410156 0.579452 0.611664 0.465827 2.009773 57 | 0.417969 0.561889 0.644216 0.446181 1.985182 58 | 0.425781 0.544755 0.676769 0.427249 1.960742 59 | 0.433594 0.528041 0.709321 0.409010 1.936453 60 | 0.441406 0.511739 0.741873 0.391443 1.912316 61 | 0.449219 0.495843 0.774425 0.374526 1.888330 62 | 0.457031 0.480345 0.806977 0.358240 1.864495 63 | 0.464844 0.465236 0.839529 0.342565 1.840812 64 | 0.472656 0.450510 0.872081 0.327481 1.817280 65 | 0.480469 0.436160 0.904633 0.312971 1.793900 66 | 0.488281 0.426319 0.927453 0.303130 1.777600 67 | 0.496094 0.426319 0.927453 0.303130 1.777600 68 | 0.503906 0.426319 0.927453 0.303130 1.777600 69 | 0.511719 0.426319 0.927453 0.303130 1.777600 70 | 0.519531 0.426319 0.927453 0.303130 1.777600 71 | 0.527344 0.426319 0.927453 0.303130 1.777600 72 | 0.535156 0.426319 0.927453 0.303130 1.777600 73 | 0.542969 0.426319 0.927453 0.303130 1.777600 74 | 0.550781 0.426319 0.927453 0.303130 1.777600 75 | 0.558594 0.426319 0.927453 0.303130 1.777600 76 | 0.566406 0.426319 0.927453 0.303130 1.777600 77 | 0.574219 0.426319 0.927453 0.303130 1.777600 78 | 0.582031 0.426319 0.927453 0.303130 1.777600 79 | 0.589844 0.426319 0.927453 0.303130 1.777600 80 | 0.597656 0.426319 0.927453 0.303130 1.777600 81 | 0.605469 0.426319 0.927453 0.303130 1.777600 82 | 0.613281 0.426319 0.927453 0.303130 1.777600 83 | 0.621094 0.426319 0.927453 0.303130 1.777600 84 | 0.628906 0.426319 0.927453 0.303130 1.777600 85 | 0.636719 0.426319 0.927453 0.303130 1.777600 86 | 0.644531 0.426319 0.927453 0.303130 1.777600 87 | 0.652344 0.426319 0.927453 0.303130 1.777600 88 | 0.660156 0.426319 0.927453 0.303130 1.777600 89 | 0.667969 0.426319 0.927453 0.303130 1.777600 90 | 0.675781 0.426319 0.927453 0.303130 1.777600 91 | 0.683594 0.426319 0.927453 0.303130 1.777600 92 | 0.691406 0.265574 0.927453 0.303130 2.853541 93 | 0.699219 0.265574 0.927453 0.303130 2.853541 94 | 0.707031 0.265574 0.927453 0.303130 2.853541 95 | 0.714844 0.265574 0.927453 0.303130 2.853541 96 | 0.722656 0.265574 0.927453 0.303130 2.853541 97 | 0.730469 0.265574 0.927453 0.303130 2.853541 98 | 0.738281 0.265574 0.927453 0.303130 2.853541 99 | 0.746094 0.265574 0.927453 0.303130 2.853541 100 | 0.753906 0.265574 0.927453 0.303130 2.853541 101 | 0.761719 0.265574 0.927453 0.303130 2.853541 102 | 0.769531 0.265574 0.927453 0.303130 2.853541 103 | 0.777344 0.265574 0.927453 0.303130 2.853541 104 | 0.785156 0.265574 0.927453 0.303130 2.853541 105 | 0.792969 0.265574 0.927453 0.303130 2.853541 106 | 0.800781 0.265574 0.927453 0.303130 2.853541 107 | 0.808594 0.265574 0.927453 0.303130 2.853541 108 | 0.816406 0.265574 0.927453 0.303130 2.853541 109 | 0.824219 0.265574 0.927453 0.303130 2.853541 110 | 0.832031 0.265574 0.927453 0.303130 2.853541 111 | 0.839844 0.265574 0.927453 0.303130 2.853541 112 | 0.847656 0.265574 0.927453 0.303130 2.853541 113 | 0.855469 0.125000 0.000000 0.100000 2.000000 114 | 0.863281 0.125000 0.000000 0.100000 2.000000 115 | 0.871094 0.125000 0.000000 0.100000 2.000000 116 | 0.878906 0.125000 0.000000 0.100000 2.000000 117 | 0.886719 0.125000 0.000000 0.100000 2.000000 118 | 0.894531 0.125000 0.000000 0.100000 2.000000 119 | 0.902344 0.125000 0.000000 0.100000 2.000000 120 | 0.910156 0.125000 0.000000 0.100000 2.000000 121 | 0.917969 0.125000 0.000000 0.100000 2.000000 122 | 0.925781 0.125000 0.000000 0.100000 2.000000 123 | 0.933594 0.125000 0.000000 0.100000 2.000000 124 | 0.941406 0.125000 0.000000 0.100000 2.000000 125 | 0.949219 0.125000 0.000000 0.100000 2.000000 126 | 0.957031 0.125000 0.000000 0.100000 2.000000 127 | 0.964844 0.125000 0.000000 0.100000 2.000000 128 | 0.972656 0.125000 0.000000 0.100000 2.000000 129 | 0.980469 0.125000 0.000000 0.100000 2.000000 130 | 0.988281 0.125000 0.000000 0.100000 2.000000 131 | 0.996094 0.125000 0.000000 0.100000 2.000000 132 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | jupyter-book 2 | matplotlib 3 | numpy 4 | scipy 5 | sympy 6 | pynucastro 7 | sphinx_design 8 | -------------------------------------------------------------------------------- /style.txt: -------------------------------------------------------------------------------- 1 | toggle cells: 2 | 3 | https://jupyterbook.org/interactive/hiding.html 4 | --------------------------------------------------------------------------------