shallow water equations solver

This commit is contained in:
Magnus Ulimoen 2020-05-19 20:05:31 +02:00
parent d7a7b8563b
commit 51397ff34d
12 changed files with 1033 additions and 1 deletions

View File

@ -12,11 +12,15 @@ members = [
"multigrid",
"euler",
"maxwell",
"shallow_water",
]
[profile.bench]
debug = true
[profile.release]
debug = true
[patch]
[patch.crates-io]
hdf5 = { git = "https://github.com/mulimoen/hdf5-rust.git", branch = "master" }

View File

@ -1,6 +1,6 @@
# What is it?
This is a test at combining rust+WASM+WebGL+SBP. The prototype can be seen [here (Maxwell solver)](https://ulimoen.dev/physics/websbp/maxwell) and [here (Nonlinear Euler solver)](https://ulimoen.dev/physics/websbp/euler).
This is a test at combining rust+WASM+WebGL+SBP. The prototypes can be seen [here (Maxwell solver)](https://ulimoen.dev/physics/websbp/maxwell), [here (Nonlinear Euler solver)](https://ulimoen.dev/physics/websbp/euler), and [here (shallow water equations)](https://ulimoen.dev/physics/websbp/shallowWater).
# Building
Run `make_wasm.py` or `make_wasm.py -r` for the release version.

10
shallow_water/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "shallow-water"
version = "0.1.0"
authors = ["Magnus Ulimoen <flymagnus@gmail.com>"]
edition = "2018"
[dependencies]
ndarray = "0.13.1"
sbp = { path = "../sbp" }
log = "0.4.8"

6
shallow_water/misc/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
*.aux
*.fdb_latexmk
*.fls
*.log
*.pdf
*.synctex.gz

102
shallow_water/misc/fluxes.py Executable file
View File

@ -0,0 +1,102 @@
#! /usr/bin/env python3
import sympy as sp
from sympy.utilities.codegen import codegen
x, y, t = sp.symbols("x,y,t")
rho, g = sp.symbols("rho,g", positive=True, real=True)
# Ignoring rho, assumed to be constant
eta = sp.Symbol("eta", positive=True, real=True)
etau, etav = sp.symbols("etau,etav", real=True)
u = etau / eta
v = etav / eta
q = sp.Matrix([eta, etau, etav])
E = sp.Matrix([eta * u, eta * u ** 2 + g * eta ** 2 / 2, eta * u * v])
F = sp.Matrix([eta * v, eta * u * v, eta * v ** 2 + g * eta ** 2 / 2])
q = sp.Matrix([eta, etau, etav])
A = E.jacobian(q)
sp.pprint(A)
B = F.jacobian(q)
sp.pprint(B)
f = sp.symbols("f")
coriolis = sp.Matrix([0, -f * v * eta, +f * u * eta])
sp.pprint(coriolis)
b = sp.symbols("b")
frictional = sp.Matrix([0, b * u * eta, b * v * eta])
sp.pprint(frictional)
print("A:")
# We can also diagonalise the transpose,
# which still gives the same (linearised) system.
# This results in a much nicer formulation for A+ and A-
A = A.T
S, D = A.diagonalize()
print("Diagonals:")
sp.pprint(D[0, 0])
sp.pprint(D[1, 1])
sp.pprint(D[2, 2])
print("S:")
sp.pprint(S)
print("SI:")
sp.pprint(sp.simplify(S.inv()))
m = abs(etau / eta) + abs(sp.sqrt(eta ** (3)) * sp.sqrt(g) / eta)
L = D + sp.Matrix([[m, 0, 0], [0, m, 0], [0, 0, m]])
Aplus = sp.simplify(S * L * S.inv()) / 2
Aplus = Aplus.T
L = D - sp.Matrix([[m, 0, 0], [0, m, 0], [0, 0, m]])
Aminus = sp.simplify(S * L * S.inv()) / 2
Aminus = Aminus.T
print("A plus:")
sp.pprint(Aplus)
print("A minus:")
sp.pprint(Aminus)
print("A S:")
sp.pprint(sp.simplify(Aminus - Aplus))
assert sp.simplify(S * D * S.inv() - A) == sp.Matrix([[0, 0, 0], [0, 0, 0], [0, 0, 0]])
print("B:")
S, D = B.diagonalize()
print("Diagonals:")
sp.pprint(D[0, 0])
sp.pprint(D[1, 1])
sp.pprint(D[2, 2])
print("S:")
sp.pprint(S)
print("SI:")
sp.pprint(sp.simplify(S.inv()))
assert sp.simplify(S * D * S.inv() - B) == sp.Matrix([[0, 0, 0], [0, 0, 0], [0, 0, 0]])
m = abs(etav / eta) + abs(sp.sqrt(eta ** (3)) * sp.sqrt(g) / eta)
L = D + sp.Matrix([[m, 0, 0], [0, m, 0], [0, 0, m]])
Bplus = sp.simplify(S * L * S.inv()) / 2
L = D - sp.Matrix([[m, 0, 0], [0, m, 0], [0, 0, m]])
Bminus = sp.simplify(S * L * S.inv()) / 2
print("B plus:")
sp.pprint(Bplus)
print("B minus:")
sp.pprint(Bminus)
print("B S:")
sp.pprint(sp.simplify(Bminus - Bplus))
assert sp.simplify((Bplus + Bminus) - B) == sp.Matrix([[0, 0, 0], [0, 0, 0], [0, 0, 0]])
breakpoint()
code = codegen(
[("Aplus", Aplus), ("Aminus", Aminus), ("Bplus", Bplus), ("Bminus", Bminus)], "rust"
)
with open("autogen.rs", "w") as f:
f.write(code[0][1])

View File

@ -0,0 +1,155 @@
\documentclass[british]{scrartcl}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{babel}
\usepackage{amsmath}
\usepackage{amsthm}
\usepackage{physics}
\usepackage{cleveref}
\usepackage{csquotes}
\newtheorem{definition}{Definition}
\title{Shallow Water Equations}
\author{Magnus Ulimoen}
\newcommand{\ve}[1]{\mathbf{#1}}
\newcommand{\kron}{\otimes}
\begin{document}
\maketitle
\section{Introduction}
This is an attempt to solve the shallow water equations (SWE) using the SBP-SAT method. Equations and assumptions are laid out in this document.
\section{Formulation}
The SWE takes the conservative form (assuming constant density)
\begin{gather}
\label{eq:conservative_formulation}
\pdv{\ve{q}}{t} + \pdv{\ve{E}}{x} + \pdv{\ve{F}}{y} = 0\\
\ve{q} = \begin{bmatrix} \eta \\ \eta u \\ \eta v \end{bmatrix}, \hspace{1em}
\ve{E} = \begin{bmatrix} \eta u \\ \eta u^2 + \frac{1}{2} g \eta^2 \\ \eta u v\end{bmatrix}, \hspace{1em}
\ve{F} = \begin{bmatrix} \eta v \\ \eta u v \\ \eta v^2 + \frac{1}{2} g \eta^2 \end{bmatrix}
\end{gather}
where $\eta$ is height above surface, $u$ fluid motion along $x$, $v$ fluid motion along $y$, $g$ acceleration due to gravity.
The equation is discretised in space on a regular cartesian grid, and the operators $\pdv{x}$ is approximated using the SBP operator $D_1$ (likewise for $\pdv{y}$). This operator obeys
\begin{definition}
An operator $D_1$ approximating $\pdv{x}$ on the form
\begin{gather*}
D_1 = H^{-1} \left( Q + \frac{1}{2} B \right)
\end{gather*}
where
\begin{gather*}
Q + Q^T = 0, \\
B = -e_0 e_0^T + e_n e_n^T, \\
H = H^T, \\
x^T H x > 0\,\,\, \forall x \neq 0
\end{gather*}
is an SBP operator
\end{definition}
Applying this to a discretised version of \cref{eq:conservative_formulation}:
\begin{gather}
\pdv{\ve{q}}{t} + D_x \ve{E} + D_y \ve{F} = 0
\end{gather}
where
\begin{gather}
D_x = I_3 \kron I_y \kron D_1\\
D_y = I_3 \kron D_1 \kron I_x
\end{gather}
and $\ve{q}$ is a \enquote{flattening} of the vector (x, y, the three fields). This formulation can be discretised in time using some appropriate scheme (eg. Runge Kutta 4).
\subsection{Energy stability}
To ensure stability, we must ensure no variable grows without bounds. First the continous case, taking the inner product left and right with q (and shifting stuff):
\begin{gather}
\left(q, \pdv{q}{t}\right) + \left(\pdv{q}{t}, q\right)
= - \left(q, \pdv{\ve{E}}{x} + \pdv{\ve{F}}{y}\right)
- \left(\pdv{\ve{E}}{x} + \pdv{\ve{F}}{y}, q\right)
= \frac{1}{2}\pdv{q^2}{t}
\end{gather}
Let us linearise this equation, with
\begin{gather}
A = \pdv{E}{q}, \hspace{1em} B = \pdv{F}{q}
\end{gather}
which gives us
\begin{align}
\nonumber \left(q, \pdv{q}{t}\right) &= - \left( q, A \pdv{q}{x} + B \pdv{q}{x} \right) \\
\nonumber &= {\left(q_x, A q\right)} - {\left[ q^T A q \right]}_{x_0}^{x_n}
+ {\left(q, B q_y \right)} - {\left[ q^T B q \right]}_{y_0}^{y_n} \\
&= {\left(A^T q_x, q\right)} - {\left[ q^T A q \right]}_{x_0}^{x_n}
+ {\left(B^T q, q_y \right)} - {\left[ q^T B q \right]}_{y_0}^{y_n}
\end{align}
and the following
\begin{align}
\nonumber \frac{1}{2}\pdv{q^2}{t} &= \left( q, \pdv{q}{t} \right) + \left( \pdv{q}{t} \right) \\
&= (q_x, Aq) - (Aq_x, q) - \left[q^T (A + A^T) q\right] + (q_y, B q) - (B q_y, q) - [q^T(B + B^T)q]
\end{align}
By symmetrising and changing variables ($\hat{q} = Sq$) we can find a suitable form which allows all the $(q, F) - (F, q)$ forms to disappear. It might not be fully symmetrisable in both $x,y$ simultaneously, but this can be skippped\ldots
Change of coordinates can be done within the integral,
\begin{gather}
q^T A q_x, \hspace{1em} \hat{q} = Sq, \hspace{1em} S^T \hat{q} = q \\
q^T A q_x = {(S^T \hat{q})}^T A (S^T \hat{q}) = \hat{q}^T S S^T D S S^T \hat{q} = \hat{q}D\hat{q}
\end{gather}
This does not change anything within the integral, and shows the symmetrisation necessary to make the two forms cancel. The energy is therefore bounded by the boundary terms (which are omitted in the continous form). It is enough to bound
\begin{gather}
A^- \text{on the right} \\
A^+ \text{on the left} \\
B^- \text{on the top} \\
B^+ \text{on the bottom}
\end{gather}
\subsubsection{Discrete case}
In this section we will determine the penalty parameter $\tau$ for all directions.
\begin{gather}
\pdv{q}{t} = - D_x (Aq) - D_y (Aq) \\
\end{gather}
\begin{align}
{\left(q, \pdv{q}{t}\right)}_H &= q^T (H_y \kron H_x) \pdv{q}{t} \\
&= - q^T H D_x (Aq) - q^T H D_y (Aq) \\
&= -q^T (H_y \kron H_x) (I_y \kron H^{-1} (Q + \frac{1}{2}B )) (I_y \kron A) q + q^T \\
&- q^T (H_y \kron H_x) (H^{-1} (Q + \frac{1}{2}B ) \kron I_x) (B \kron I_x) q + q^T
\end{align}
Let us just do this in one dimension, it is mostly transferable anyway\ldots
\begin{align}
q^T H \pdv{q}{t} &= -q^T H H^{-1} (Q + \frac{1}{2}B) A q \\
&= - q^T (Q + \frac{1}{2} B ) A q
\end{align}
And the transpose
\begin{align}
{\left(\pdv{q}{t}\right)}^T H q = -{(Aq)}^T H D_1 q = -q^T A^T (Q + \frac{1}{2}B) q
\end{align}
Tranpose of this gives (can always transpose a scalar)
\begin{align}
q^T {(A^T (Q + \frac{1}{2}B))}^T q = q^T (Q^T + \frac{1}{2}B^T) A q
\end{align}
The sum of these two expressions
\begin{gather}
\frac{1}{2}\pdv{\norm{q}^2_H}{t} = q^T (Q + Q^T + \frac{1}{2}(B + B^T)) A q \\
= q^T B A q = -q_0^T A q_0 + q_n^T A q_n
\end{gather}
We can thus control the energy rate by controlling $q_0^T A q_0$ and $q_n^T A q_n$ (that is, we set the boundaries). We do this by adding the following SAT\@.
\begin{gather}
SAT = \tau_0 H^{-1} e_0 e_0^T A_- (q - v) + \tau_n H^{-1} e_n e_n^T A_+ (q - v)
\end{gather}
Adding this to the energy form above (setting $v=0$ (data independence))
\begin{gather}
E = -q_0^T A q_0 + \tau_0 q_0^T A_- q_0 + \tau_0 q_0^T A_-^T q_0
+ q_n^T A q_n + \ldots \\
= -q_0^T (A - 2\tau_0 A_-) q_0 + \ldots
\end{gather}
This sets the following requirements
\begin{gather}
\tau_0 \ge \frac{1}{2} \\
\tau_n \le -\frac{1}{2}
\end{gather}
\end{document}

View File

@ -0,0 +1,89 @@
#![allow(non_snake_case)]
/*
* Code generated with sympy 1.7.dev
*
* See http://www.sympy.org/ for more information.
*
* This file is part of 'project'
*/
use super::Float;
pub fn Aplus(eta: Float, etau: Float, etav: Float, g: Float) -> [[Float; 3]; 3] {
[
[
(eta.powf(3.0 / 2.0) * g.sqrt() + etau.abs()) / (2.0 * eta),
1.0 / 2.0,
0.0,
],
[
eta * g / 2.0 - etau.powi(2) / (2.0 * eta.powi(2)),
(eta.powf(3.0 / 2.0) * g.sqrt() + 2.0 * etau + etau.abs()) / (2.0 * eta),
0.0,
],
[
-etau * etav / (2.0 * eta.powi(2)),
etav / (2.0 * eta),
(eta.powf(3.0 / 2.0) * g.sqrt() + etau + etau.abs()) / (2.0 * eta),
],
]
}
pub fn Aminus(eta: Float, etau: Float, etav: Float, g: Float) -> [[Float; 3]; 3] {
[
[
-(eta.powf(3.0 / 2.0) * g.sqrt() + etau.abs()) / (2.0 * eta),
1.0 / 2.0,
0.0,
],
[
eta * g / 2.0 - etau.powi(2) / (2.0 * eta.powi(2)),
(-eta.powf(3.0 / 2.0) * g.sqrt() + 2.0 * etau - etau.abs()) / (2.0 * eta),
0.0,
],
[
-etau * etav / (2.0 * eta.powi(2)),
etav / (2.0 * eta),
(-eta.powf(3.0 / 2.0) * g.sqrt() + etau - etau.abs()) / (2.0 * eta),
],
]
}
pub fn Bplus(eta: Float, etau: Float, etav: Float, g: Float) -> [[Float; 3]; 3] {
[
[
(eta.powf(3.0 / 2.0) + etav.abs()) / (2.0 * eta),
0.0,
1.0 / 2.0,
],
[
-etau * etav / (2.0 * eta.powi(2)),
(eta.powf(3.0 / 2.0) * g.sqrt() + etav + etav.abs()) / (2.0 * eta),
etau / (2.0 * eta),
],
[
eta * g / 2.0 - etav.powi(2) / (2.0 * eta.powi(2)),
0.0,
(eta.powf(3.0 / 2.0) * g.sqrt() + 2.0 * etav + etav.abs()) / (2.0 * eta),
],
]
}
pub fn Bminus(eta: Float, etau: Float, etav: Float, g: Float) -> [[Float; 3]; 3] {
[
[
-(eta.powf(3.0 / 2.0) + etav.abs()) / (2.0 * eta),
0.0,
1.0 / 2.0,
],
[
-etau * etav / (2.0 * eta.powi(2)),
(-eta.powf(3.0 / 2.0) * g.sqrt() + etav - etav.abs()) / (2.0 * eta),
etau / (2.0 * eta),
],
[
eta * g / 2.0 - etav.powi(2) / (2.0 * eta.powi(2)),
0.0,
(-eta.powf(3.0 / 2.0) * g.sqrt() + 2.0 * etav - etav.abs()) / (2.0 * eta),
],
]
}

322
shallow_water/src/lib.rs Normal file
View File

@ -0,0 +1,322 @@
use ndarray::prelude::*;
use sbp::*;
mod characteristics;
use characteristics::{Aminus, Aplus, Bminus, Bplus};
const G: Float = 1.0;
#[derive(Clone, Debug)]
pub struct Field(Array3<Float>);
impl<'a> Into<ArrayView3<'a, Float>> for &'a Field {
fn into(self) -> ArrayView3<'a, Float> {
self.0.view()
}
}
impl<'a> Into<ArrayViewMut3<'a, Float>> for &'a mut Field {
fn into(self) -> ArrayViewMut3<'a, Float> {
self.0.view_mut()
}
}
impl Field {
fn new(ny: usize, nx: usize) -> Self {
Self(Array3::zeros((3, ny, nx)))
}
fn eta(&self) -> ArrayView2<Float> {
self.0.slice(s![0, .., ..])
}
fn etau(&self) -> ArrayView2<Float> {
self.0.slice(s![1, .., ..])
}
fn etav(&self) -> ArrayView2<Float> {
self.0.slice(s![2, .., ..])
}
/*
fn eta_mut(&mut self) -> ArrayViewMut2<Float> {
self.0.slice_mut(s![0, .., ..])
}
fn etau_mut(&mut self) -> ArrayViewMut2<Float> {
self.0.slice_mut(s![1, .., ..])
}
fn etav_mut(&mut self) -> ArrayViewMut2<Float> {
self.0.slice_mut(s![2, .., ..])
}
*/
fn components_mut(
&mut self,
) -> (
ArrayViewMut2<Float>,
ArrayViewMut2<Float>,
ArrayViewMut2<Float>,
) {
self.0
.multi_slice_mut((s![0, .., ..], s![1, .., ..], s![2, .., ..]))
}
}
pub struct System {
fnow: Field,
fnext: Field,
x: (Float, Float, usize),
y: (Float, Float, usize),
op: Box<dyn operators::UpwindOperator2d>,
k: [Field; 4],
}
impl System {
pub fn new(x: (Float, Float, usize), y: (Float, Float, usize)) -> Self {
let field = Field::new(y.2, x.2);
Self {
fnow: field.clone(),
fnext: field.clone(),
x,
y,
op: Box::new(operators::Upwind9),
k: [field.clone(), field.clone(), field.clone(), field],
}
}
pub fn nx(&self) -> usize {
self.x.2
}
pub fn ny(&self) -> usize {
self.y.2
}
pub fn eta(&self) -> ArrayView2<Float> {
self.fnow.eta()
}
pub fn etau(&self) -> ArrayView2<Float> {
self.fnow.etau()
}
pub fn etav(&self) -> ArrayView2<Float> {
self.fnow.etav()
}
pub fn components_mut(
&mut self,
) -> (
ArrayViewMut2<Float>,
ArrayViewMut2<Float>,
ArrayViewMut2<Float>,
) {
self.fnow.components_mut()
}
fn max_dt(&self) -> Float {
0.1 * ((self.x.1 - self.x.0) / self.x.2 as Float)
.min((self.y.1 - self.y.0) / self.y.2 as Float)
}
pub fn advance(&mut self) {
let max_dt = self.max_dt();
let op = &self.op;
let rhs = move |next: &mut Field, now: &Field, _t: Float| {
let (mut next_eta, mut next_etau, mut next_etav) = next.components_mut();
next_eta.fill(0.0);
next_etau.fill(0.0);
next_etav.fill(0.0);
let nx = next_eta.shape()[1];
let ny = next_eta.shape()[0];
if false {
let eta = now.eta();
for j in 0..ny {
for i in 0..nx {
if eta[(j, i)] <= 0.0 {
panic!("{} {}", j, i);
}
}
}
}
let mut temp = Array2::<Float>::zeros((ny, nx));
// E flux
let mut temp_dx = temp.clone();
azip!((dest in &mut temp, etau in now.etau()) {
*dest = *etau;
});
op.diffxi(temp.view(), temp_dx.view_mut());
next_eta.scaled_add(-1.0, &temp_dx);
azip!((dest in &mut temp, eta in now.eta(), etau in now.etau()) {
*dest = etau.powi(2)/eta + G*eta.powi(2)/2.0
});
op.diffxi(temp.view(), temp_dx.view_mut());
next_etau.scaled_add(-1.0, &temp_dx);
azip!((dest in &mut temp, eta in now.eta(), etau in now.etau(), etav in now.etav()) {
*dest = etau*etav/eta
});
op.diffxi(temp.view(), temp_dx.view_mut());
next_etav.scaled_add(-1.0, &temp_dx);
// F flux
let mut temp_dy = temp_dx;
azip!((dest in &mut temp, etav in now.etav()) {
*dest = *etav;
});
op.diffeta(temp.view(), temp_dy.view_mut());
next_eta.scaled_add(-1.0, &temp_dy);
azip!((dest in &mut temp, eta in now.eta(), etau in now.etau(), etav in now.etav()) {
*dest = etau*etav/eta;
});
op.diffeta(temp.view(), temp_dy.view_mut());
next_etau.scaled_add(-1.0, &temp_dy);
azip!((dest in &mut temp, eta in now.eta(), etav in now.etav()) {
*dest = etav.powi(2)/eta + G*eta.powi(2)/2.0
});
op.diffeta(temp.view(), temp_dy.view_mut());
next_etav.scaled_add(-1.0, &temp_dy);
// Upwind dissipation
if false {
let mut temp_dx = temp_dy;
azip!((dest in &mut temp, eta in now.eta(), etau in now.etau()) {
*dest = -(eta.powf(3.0/2.0)*G.sqrt() + etau.abs())/eta
});
op.dissxi(temp.view(), temp_dx.view_mut());
azip!((dest in &mut next_eta, eta in now.eta(), diss in &temp_dx) {
*dest -= eta*diss;
});
azip!((dest in &mut next_etau, etau in now.etau(), diss in &temp_dx) {
*dest -= etau*diss;
});
azip!((dest in &mut next_etav, etav in now.etav(), diss in &temp_dx) {
*dest -= etav*diss;
});
let mut temp_dy = temp_dx;
azip!((dest in &mut temp, eta in now.eta(), etav in now.etav()) {
*dest = -(eta.powf(3.0/2.0)*G.sqrt() + etav.abs())/eta
});
op.disseta(temp.view(), temp_dy.view_mut());
azip!((dest in &mut next_eta, eta in now.eta(), diss in &temp_dy) {
*dest -= eta*diss;
});
azip!((dest in &mut next_etau, etau in now.etau(), diss in &temp_dy) {
*dest -= etau*diss;
});
azip!((dest in &mut next_etav, etav in now.etav(), diss in &temp_dy) {
*dest -= etav*diss;
});
}
// SAT boundaries
#[derive(Debug)]
enum Direction {
North,
South,
East,
West,
}
for dir in &[
Direction::North,
Direction::South,
Direction::East,
Direction::West,
] {
let mut dest;
let this;
let other;
match dir {
Direction::North => {
dest = next.0.slice_mut(s![.., ny - 1, ..]);
this = now.0.slice(s![.., ny - 1, ..]);
other = now.0.slice(s![.., 0_usize, ..]);
}
Direction::South => {
dest = next.0.slice_mut(s![.., 0_usize, ..]);
this = now.0.slice(s![.., 0_usize, ..]);
other = now.0.slice(s![.., ny - 1, ..]);
}
Direction::East => {
dest = next.0.slice_mut(s![.., .., nx - 1]);
this = now.0.slice(s![.., .., nx - 1]);
other = now.0.slice(s![.., .., 0_usize]);
}
Direction::West => {
dest = next.0.slice_mut(s![.., .., 0_usize]);
this = now.0.slice(s![.., .., 0_usize]);
other = now.0.slice(s![.., .., nx - 1]);
}
}
for ((mut dest, this), other) in dest
.axis_iter_mut(Axis(1))
.zip(this.axis_iter(Axis(1)))
.zip(other.axis_iter(Axis(1)))
{
let tau = match dir {
Direction::North => 1.0,
Direction::South => -1.0,
Direction::East => 1.0,
Direction::West => -1.0,
};
let hinv = match dir {
Direction::North | Direction::South => {
if op.is_h2eta() {
(ny - 2) as Float / op.heta()[0]
} else {
(ny - 1) as Float / op.heta()[0]
}
}
Direction::East | Direction::West => {
if op.is_h2xi() {
(nx - 2) as Float / op.hxi()[0]
} else {
(nx - 1) as Float / op.hxi()[0]
}
}
};
let v = (this[0], this[1], this[2]);
let g = (other[0], other[1], other[2]);
let mat = match dir {
Direction::West => Aplus(v.0, v.1, v.2, G),
Direction::East => Aminus(v.0, v.1, v.2, G),
Direction::North => Bminus(v.0, v.1, v.2, G),
Direction::South => Bplus(v.0, v.1, v.2, G),
};
let q = [v.0 - g.0, v.1 - g.1, v.2 - g.2];
let mut res = [0.0; 3];
for j in 0..3 {
for i in 0..3 {
res[j] += mat[j][i] * q[i];
}
}
for i in 0..3 {
dest[i] += tau * hinv * res[i];
}
}
}
log::trace!("Iteration complete");
};
integrate::integrate::<integrate::Rk4, _, _>(
rhs,
&self.fnow,
&mut self.fnext,
&mut 0.0,
max_dt,
&mut self.k[..],
);
std::mem::swap(&mut self.fnow, &mut self.fnext)
}
}
#[test]
fn test_advance() {
let mut sys = System::new((0.0, 1.0, 50), (0.0, 1.0, 50));
sys.fnow.components_mut().0.fill(1.0);
for _ in 0..10 {
sys.advance();
}
println!("{:?}", sys.fnow);
}

View File

@ -16,3 +16,6 @@ sbp = { path = "../sbp", features = ["f32"] }
ndarray = "0.13.0"
euler = { path = "../euler" }
maxwell = { path = "../maxwell" }
shallow-water = { path = "../shallow_water" }
console_log = "0.2.0"
log = "0.4.8"

View File

@ -13,6 +13,14 @@ pub fn set_panic_hook() {
console_error_panic_hook::set_once();
}
#[wasm_bindgen]
pub fn set_console_logger() {
use std::sync::Once;
static LOG_INIT: Once = Once::new();
LOG_INIT.call_once(|| console_log::init_with_level(log::Level::Trace).unwrap());
}
#[wasm_bindgen]
pub struct MaxwellUniverse(maxwell::System<operators::Upwind4>);
@ -119,3 +127,55 @@ fn start_and_advance_upwind_euler() {
universe.advance_upwind(0.01);
}
}
#[wasm_bindgen]
pub struct ShallowWaterUniverse(shallow_water::System);
#[wasm_bindgen]
impl ShallowWaterUniverse {
#[wasm_bindgen(constructor)]
pub fn new(height: usize, width: usize) -> Self {
let x = (0.0, 1.0, width);
let y = (0.0, 1.0, height);
Self(shallow_water::System::new(x, y))
}
pub fn init(&mut self, x0: f32, y0: f32) {
let nx = self.0.nx();
let ny = self.0.ny();
let x = ndarray::Array1::linspace(0.0, 1.0, nx);
let y = ndarray::Array1::linspace(0.0, 1.0, ny);
let (mut eta, mut etau, mut etav) = self.0.components_mut();
let sigma = 0.1;
for j in 0..ny {
for i in 0..nx {
let r = f32::hypot(x[i] - x0, y[j] - y0);
let f = 1.0 / (sigma * (2.0 * sbp::consts::PI).sqrt())
* f32::exp(-0.5 * (r / sigma).powi(2));
eta[(j, i)] = 1.0 - 0.1 * f;
etau[(j, i)] = eta[(j, i)] * 0.0;
etav[(j, i)] = eta[(j, i)] * 0.0;
}
}
}
pub fn advance(&mut self) {
self.0.advance()
}
pub fn get_eta_ptr(&self) -> *const u8 {
self.0.eta().as_ptr() as *const u8
}
pub fn get_etau_ptr(&self) -> *const u8 {
self.0.etau().as_ptr() as *const u8
}
pub fn get_etav_ptr(&self) -> *const u8 {
self.0.etav().as_ptr() as *const u8
}
}

View File

@ -0,0 +1,15 @@
<!doctype html>
<!--https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Adding_2D_content_to_a_WebGL_context-->
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="by-hand" />
<meta name="viewpost" context="width=device-width, inital-scale=1.0, user-scalable=yes" />
<title>Shallow Water solver (ΣBP)</title>
<link rel="stylesheet" type="text/css" href="../style.css">
<script async type="module" src="main.js"></script>
</head>
<body>
<canvas id="glCanvas"></canvas>
</body>
</html>

View File

@ -0,0 +1,266 @@
import { ShallowWaterUniverse, default as init, set_panic_hook as setPanicHook, set_console_logger as setConsoleLogger } from "../sbp_web.js";
/**
* Initialises and runs the Maxwell solver,
* plotting the solution to a canvas using webgl
*/
(async function run() {
const wasm = await init("../sbp_web_bg.wasm");
setPanicHook();
setConsoleLogger();
const canvas = document.getElementById("glCanvas");
const gl = canvas.getContext("webgl");
if (gl === null) {
console.error("Unable to initialise WebGL");
return;
}
const vsSource = String.raw`
#version 100
attribute mediump float aX;
attribute mediump float aY;
attribute mediump float aField;
uniform vec4 uBbox;
varying mediump float vField;
void main() {
vField = aField;
mediump float x = (aX - uBbox.x)*uBbox.y;
mediump float y = (aY - uBbox.z)*uBbox.w;
gl_Position = vec4(2.0*x - 1.0, 2.0*y - 1.0, 1.0, 1.0);
}
`;
const fsSource = String.raw`
#version 100
varying mediump float vField;
uniform int uChosenField;
void main() {
mediump float r = 0.0;
mediump float g = 0.0;
mediump float b = 0.0;
if (uChosenField == 0) {
r = 0.5*vField;
} else if (uChosenField == 1) {
g = (vField + 1.0)/2.0;
} else {
b = vField + 0.5;
}
gl_FragColor = vec4(r, g, b, 1.0);
}
`;
const vsShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vsShader, vsSource);
gl.compileShader(vsShader);
if (!gl.getShaderParameter(vsShader, gl.COMPILE_STATUS)) {
console.error(`Could not compile shader: ${gl.getShaderInfoLog(vsShader)}`);
return;
}
const fsShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fsShader, fsSource);
gl.compileShader(fsShader);
if (!gl.getShaderParameter(fsShader, gl.COMPILE_STATUS)) {
console.error(`Could not compile shader: ${gl.getShaderInfoLog(fsShader)}`);
return;
}
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vsShader);
gl.attachShader(shaderProgram, fsShader);
gl.linkProgram(shaderProgram);
gl.validateProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
console.error(`Unable to link shader program: ${gl.getProgramInfoLog(shaderProgram)}`);
return;
}
gl.useProgram(shaderProgram);
// A nice pink to show missing values
gl.clearColor(1.0, 0.753, 0.796, 1.0);
gl.clearDepth(1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.disable(gl.CULL_FACE);
console.info("Successfully set OpenGL state");
const width = 70;
const height = 70;
const x = new Float32Array(width * height);
const y = new Float32Array(width * height);
for (let j = 0; j < height; j += 1) {
for (let i = 0; i < width; i += 1) {
const n = width*j + i;
x[n] = i / (width - 1.0);
y[n] = j / (height - 1.0);
}
}
const universe = new ShallowWaterUniverse(height, width);
// Transfer x, y to cpu, prepare fBuffer
const xBuffer = gl.createBuffer();
const yBuffer = gl.createBuffer();
const fBuffer = gl.createBuffer();
{
const numcomp = 1;
const type = gl.FLOAT;
const normalise = false;
const stride = 0;
const offset = 0;
let loc = gl.getAttribLocation(shaderProgram, "aX");
gl.bindBuffer(gl.ARRAY_BUFFER, xBuffer);
gl.vertexAttribPointer(loc, numcomp, type, normalise, stride, offset);
gl.enableVertexAttribArray(loc);
gl.bufferData(gl.ARRAY_BUFFER, x, gl.STATIC_DRAW);
loc = gl.getAttribLocation(shaderProgram, "aY");
gl.bindBuffer(gl.ARRAY_BUFFER, yBuffer);
gl.vertexAttribPointer(loc, numcomp, type, normalise, stride, offset);
gl.enableVertexAttribArray(loc);
gl.bufferData(gl.ARRAY_BUFFER, y, gl.STATIC_DRAW);
loc = gl.getAttribLocation(shaderProgram, "aField");
gl.bindBuffer(gl.ARRAY_BUFFER, fBuffer);
gl.vertexAttribPointer(loc, numcomp, type, normalise, stride, offset);
gl.enableVertexAttribArray(loc);
}
// Create triangles covering the domain
const positions = new Int16Array((width-1)*(height-1)*2*3);
for (let j = 0; j < height - 1; j += 1) {
for (let i = 0; i < width - 1; i += 1) {
const n = 2*3*((width-1)*j + i);
positions[n+0] = width*j + i;
positions[n+1] = width*j + i+1;
positions[n+2] = width*(j+1) + i;
positions[n+3] = width*j + i+1;
positions[n+4] = width*(j+1) + i;
positions[n+5] = width*(j+1) + i+1;
}
}
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, positions, gl.STATIC_DRAW);
const bbox = [+Number(Infinity), -Number(Infinity), +Number(Infinity), -Number(Infinity)];
for (let i = 0; i < width*height; i += 1) {
bbox[0] = Math.min(bbox[0], x[i]);
bbox[1] = Math.max(bbox[1], x[i]);
bbox[2] = Math.min(bbox[2], y[i]);
bbox[3] = Math.max(bbox[3], y[i]);
}
{
const loc = gl.getUniformLocation(shaderProgram, "uBbox");
gl.uniform4f(loc, bbox[0], 1.0/(bbox[1] - bbox[0]), bbox[2], 1.0/(bbox[3] - bbox[2]));
}
const TIMEFACTOR = 10000.0;
const MAX_DT = 1.0/Math.max(width, height);
let t = 0;
let firstDraw = true;
let warnTime = -1;
const chosenField = {
"uLocation": gl.getUniformLocation(shaderProgram, "uChosenField"),
"value": 2,
"cycle": function cycle() {
if (this.value === 0) {
this.value = 1;
} else if (this.value === 1) {
this.value = 2;
} else {
this.value = 0;
}
gl.uniform1i(this.uLocation, this.value);
}
};
chosenField.cycle();
universe.init(0.5, 0.5);
/**
* Integrates and draws the next iteration
* @param {Time} timeOfDraw Time of drawing
*/
function drawMe(timeOfDraw) {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
let dt = (timeOfDraw - t) / TIMEFACTOR;
t = timeOfDraw;
if (firstDraw || dt <= 0.0) {
firstDraw = false;
dt = MAX_DT;
} else {
if (dt >= MAX_DT) {
warnTime += 1;
if (warnTime !== -2) {
console.warn("Can not keep up with framerate");
}
dt = MAX_DT;
}
}
let fieldPtr;
if (chosenField.value === 0) {
fieldPtr = universe.get_eta_ptr();
} else if (chosenField.value === 1) {
fieldPtr = universe.get_etau_ptr();
} else {
fieldPtr = universe.get_etav_ptr();
}
const field = new Float32Array(wasm.memory.buffer, fieldPtr, width*height);
gl.bufferData(gl.ARRAY_BUFFER, field, gl.DYNAMIC_DRAW);
{
const offset = 0;
const type = gl.UNSIGNED_SHORT;
const vertexCount = positions.length;
gl.drawElements(gl.TRIANGLES, vertexCount, type, offset);
}
universe.advance();
window.requestAnimationFrame(drawMe);
}
// https://stackoverflow.com/questions/4288253/html5-canvas-100-width-height-of-viewport#8486324
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
}
window.addEventListener("keyup", event => {
if (event.key === "c") {
chosenField.cycle();
}
}, {"passive": true});
window.addEventListener("resize", resizeCanvas, false);
window.addEventListener("click", event => {
// Must adjust for bbox and transformations for x/y
const mousex = event.clientX / window.innerWidth;
const mousey = event.clientY / window.innerHeight;
universe.init(mousex, 1.0 - mousey);
}, {"passive": true});
resizeCanvas();
window.requestAnimationFrame(drawMe);
}());