├── .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 | [](https://creativecommons.org/licenses/by-nc-sa/4.0/)
2 | [](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 | "\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 | "\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 | ""
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 | 
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 | 
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 | 
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 | 
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 | 
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 | "\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 | ""
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 | "\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 |
--------------------------------------------------------------------------------