summaryrefslogblamecommitdiffstats
path: root/hacks/euler2d.tex
blob: 700ad3176e0bc6615d652c6c950973f75c1a2fe6 (plain) (tree)
















































































































































































































































































































































                                                                             
\documentclass[12pt]{article}

%\usepackage{fullpage}
\usepackage{amsmath,amssymb}

\begin{document}

\title{Two Dimensional Euler Simulation}

\author{
S.J. Montgomery-Smith\\
Department of Mathematics\\
University of Missouri\\
Columbia, MO 65211, U.S.A.\\
stephen@math.missouri.edu\\
http://www.math.missouri.edu/\~{}stephen}

\date{September 10, 2000}

\maketitle

This document describes a program I wrote to simulate the 
two dimensional Euler Equation --- a program that is part
of the {\tt xlock} screensaver as the {\tt euler2d}
mode.  A similar explanation may also be found in the
book by Chorin \cite{C}.

\section{The Euler Equation}

The Euler Equation describes the motion of an incompressible
fluid that has no viscosity.  If the fluid is contained
in a domain $\Omega$ with boundary $\partial \Omega$, then
the equation is in the vector field $u$ (the velocity)
and the
scalar field $p$ (the pressure):
\begin{eqnarray*}
\frac{\partial}{\partial t} u &=& -u \cdot \nabla u + \nabla p \\
\nabla \cdot u &=& 0 \\
u \cdot n &=& 0 \quad \text{on $\partial \Omega$}
\end{eqnarray*}
where $n$ is the unit normal to $\partial \Omega$.

\section{Vorticity}

It turns out that it can be easier write these equations
in terms of the vorticity.  In two dimensions the vorticity
is the scalar $w = \partial u_2/\partial x - \partial u_1/\partial y$.
The equation for vorticity becomes
\[ \frac{\partial}{\partial t} w = -u \cdot \nabla w .\]
A solution to this equation can be written as follows.  The velocity
$u$ causes a flow, that is, a function $\varphi(t,x)$ that tells where
the particle initially at $x$ ends up at time $t$, that is
\[
\frac\partial{\partial t} \varphi(t,x)
= u(t,\varphi(t,x)) .\]
Then the equation
for $w$ tells us that the vorticity is ``pushed'' around by the flow,
that is, $w(t,\varphi(t,x)) = w(0,x)$.

\section{The Biot-Savart Kernel}

Now, once we have the vorticity, we can recover the velocity $u$ by
solving the equation
\begin{eqnarray*}
\partial u_2/\partial x - \partial u_1/\partial y &=& w \\
\nabla \cdot u &=& 0 \\
u \cdot n &=& 0 \quad \text{on $\partial \Omega$}.
\end{eqnarray*}
This equation is solved by using a Biot-Savart kernel $K(x,y)$:
$$ u(x) = \int_\Omega K(x,y) w(y) \, dy .$$
The function $K$ depends upon the choice of domain.  First let us consider
the case when $\Omega$ is the whole plane (in which case the boundary
condition $u \cdot n = 0$ is replaced by saying that $u$ decays at infinity).
Then
\begin{equation*}
K(x,y) = K_1(x,y) = c \frac{(x-y)^\perp}{|x-y|^2} .
\end{equation*}
Here $x^\perp = (-x_2,x_1)$, and $c$ is a constant, probably something
like $1/2\pi$.  In any case we will set it to be one, which in effect
is rescaling the time variable, so we don't need to worry about it.

We can use this as a basis to find $K$ on the unit disk
$\Omega = \Delta = \{x:|x|<1\}$.  It turns out to be
\begin{equation*}
K_2(x,y) = K_1(x,y) - K_1(x,y^*) ,
\end{equation*}
where $y^* = y/|y|^2$ is called the reflection of $y$ about the
boundary of the unit disk.

Another example is if we have a bijective analytic function
$p:\Delta \to {\mathbb C}$, and we let $\Omega = p(\Delta)$.
(Here we think of $\Delta$ as a subset of $\mathbb C$, that is,
we are identifying the plane with the set of complex numbers.)
In that case we get
\[ K_p(p(x),p(y)) = K_2(x,y)/|p'(x)|^2 .\]
Our simulation considers the last case.  Examples of such
analytic functions include series 
$p(x) = x + \sum_{n=2}^\infty c_n x^n$, where
$\sum_{n=2}^\infty n |c_n| \le 1$.
(Thanks to David Ullrich for pointing this out to me.)

\section{The Simulation}

Now let's get to decribing the simulation.  We assume a rather
unusual initial distribution for the vorticity --- that the
vorticity is a finite sum of dirac delta masses.
\[ w(0,x) = \sum_{k=1}^N w_k \delta(x-x_k(0)) .\]
Here $x_k(0)$ is the initial place where the points
of vorticity are concentrated, with values $w_k$.  
Then at time $t$, the vorticity becomes
\[ w(t,x) = \sum_{k=1}^N w_k \delta(x-x_k(t)) .\]
The points of fluid $x_k(t)$ are pushed by the
flow, that is, $x_k(t) = \varphi(t,x_k(0))$, or
\[ \frac{\partial}{\partial t} x_k(t) = u(t,x_k(t)) .\]
Putting this all together, we finally obtain the equations
\[ \frac{\partial}{\partial t} x_k = \alpha_k \]
where
\[ \alpha_k   = \sum_{l=1}^N w_l K(x_k,x_l) .\]
This is the equation that our simulation solves.

In fact, in our case, where the domain is $p(\Delta)$,
the points are described by points
$\tilde x_k$, where $x_k = p(\tilde x_k)$.  Then
the equations become
\begin{eqnarray}
\label{tildex-p1}
\frac{\partial}{\partial t} \tilde x_k &=& \tilde\alpha_k \\
\label{tildex-p2}
\tilde\alpha_k &=& \frac1{|p'(\tilde x_k)|^2}
     \sum_{l=1}^N w_l K_2(\tilde x_k,\tilde x_l) .
\end{eqnarray}

We solve this $2N$ system of equations using standard
numerical methods, in our case, using the second order midpoint method
for the first step, and thereafter using the second order Adams-Bashforth 
method.  (See for example the book
by Burden and Faires \cite{BF}).

\section{The Program - Data Structures}

The computer program solves equation (\ref{tildex-p1}), and displays
the results on the screen, with a boundary.  All the information
for solving the equation and displaying the output is countained
in the structure {\tt euler2dstruct}.  Let us describe some of
the fields in {\tt euler2dstruct}.  
The points $\tilde x_k$ are contained 
in {\tt double *x}: with the coordinates of
$\tilde x_k$ being the two numbers
{\tt x[2*k+0]}, {\tt x[2*k+1]}.  The values $w_k$ are contained
in {\tt double *w}.  The total number of points is
{\tt int N}.  (But only the first {\tt int Nvortex} points
have $w_k \ne 0$.)  The coefficients of the analytic function
(in our case a polynomial) $p$
are contained in {\tt double p\_coef[2*(deg\_p-1)]} --- here
{\tt deg\_p} is the degree of $p$, and the real and imaginary
parts of the coefficient
$c_n$ is contained in {\tt p\_coef[2*(n-2)+0]} and {\tt p\_coef[2*(n-2)+1]}.

\section{Data Initialization}

The program starts in the function {\tt init\_euler2d}.  After allocating
the memory for the data, and initialising some of the temporary variables
required for the numerical solving program, it randomly assigns the
coefficients of $p$, making sure that $\sum_{n=2}^{\tt deg\_p} n |c_n| = 1$.
Then the program figures out how to draw the boundary, and what rescaling
of the data is required to draw it on the screen.  (This uses the
function {\tt calc\_p} which calculates $p(x)$.)

Next, it randomly assigns the initial values of $\tilde x_k$.  We want
to do this in such a way so that the points are uniformly spread over the
domain.  Let us first consider the case when the domain is the unit circle
$\Delta$.  In that case the proportion of points that we would expect
inside the circle of radius $r$ would be proportional to $r^2$.  So
we do it as follows:
\[ r = \sqrt{R_{0,1}},\quad \theta = R_{-\pi,\pi}, \quad
   \tilde x_k = r (\cos \theta, \sin \theta) .\]
Here, and in the rest of this discussion, $R_{a,b}$ is a function
that returns a random variable uniformly distributed over the interval
$[a,b]$.

This works fine for $\Delta$, but for $p(\Delta)$, the points 
$p(\tilde x_k)$ are not uniformly distributed over $p(\Delta)$,
but are distributed with a density proportional to
$1/|p'(\tilde x_k)|^2$.  So to restore the uniform density we need
to reject this value of $\tilde x_k$ with probability proportional
to $|p'(\tilde x_k)|^2$.  Noticing that the condition 
$\sum_{n=2}^{\tt deg\_p} n |c_n| = 1$ implies that 
$|p'(\tilde x_k)| \le 2$, we
do this by rejecting if $|p'(\tilde x_k)|^2 < R_{0,4}$.
(This makes use of the function {\tt calc\_mod\_dp2} which calculates
$|p'(x)|^2$.)

\section{Solving the Equation}

The main loop of the program is in the function {\tt draw\_euler2d}.
Most of the drawing operations are contained in this function, and
the numerical aspects are sent to the function {\tt ode\_solve}.
But there is an aspect of this that I would like
to discuss in the next section, and so we will look at a simple method for 
numerically solving differential equations.

The Euler Method
(nothing to do with the Euler Equation), is as
follows.  Pick a small number $h$ --- the time step (in
the program call {\tt delta\_t}).  Then we approximate
the solution of the equation:
\begin{equation}
\label{method-simple}
\tilde x_k(t+h) = \tilde x_k(t) + h \tilde\alpha_k(t) .
\end{equation}
The more sophisticated methods we use are variations of 
the Euler Method, and so the discussion in the following section
still applies.

In the program, the quantities $\tilde\alpha_k$, given by
equations (\ref{tildex-p2}) are calculated by the function
{\tt derivs}
(which in turns calls {\tt calc\_all\_mod\_dp2} to
calculate $|p'(\tilde x_k)|^2$ at all the points).


\section{Subtle Perturbation}

Added later: the scheme described here seems to not be that effective,
so now it is not used.

One problem using a numerical scheme such as the Euler Method occurs
when the points $\tilde x_k$ get close to the boundary
of $\Delta$.  In that case, it is possible that the new
points will be pushed outside of the boundary.  Even if they 
are not pushed out of the boundary, they may be much closer
or farther from the boundary than they should be.  
Our system of equations is very sensitive to how close points
are to the boundary --- points with non-zero vorticity
(``vortex points'') that are close to the boundary travel
at great speed alongside the boundary, with speed that is
inversely proportional to the distance from the boundary.

A way to try to mitigate this problem is something that I call
``subtle perturbation.''
We map the points in 
the unit disk to points in the plane using the map
\begin{equation*}
F(x) = f(|x|) \frac x{|x|} ,
\end{equation*}
where $f:[0,1]\to[0,\infty]$ is an increasing continuous
bijection.  It turns out that a good choice is
\begin{equation*}
f(t) = -\log(1-t) .
\end{equation*}
(The reason for this is that points close to each other 
that are a distance
about $r$ from the boundary will be pushed around so that
their distance from each other is about multiplied by the
derivative of $\log r$, that is, $1/r$.)
Note that the inverse of this function is given by
\begin{equation*}
F^{-1}(x) = f^{-1}(|x|) \frac x{|x|} ,
\end{equation*}
where 
\begin{equation*}
f^{-1}(t) = 1-e^{-t} .
\end{equation*}

So what we could do is the following: instead of working with
the points $\tilde x_k$, we could work instead with the points
$y_k = F(\tilde x_k)$.  In effect this is what we do.
Instead of performing the computation (\ref{method-simple}),
we do the calculation
\begin{equation*}
y_k = F(\tilde x_k(t)) + h {\cal A}(\tilde x_k) \tilde\alpha_k(t) 
\end{equation*}
where
${\cal A}(x)$ is the matrix of partial derivatives of $F$:
\begin{equation*}
{\cal A}(x) = 
\frac{f(|x|)}{|x|}
\left[
\begin{matrix}
1 & 0\\
0 & 1
\end{matrix}
\right]
+ \frac1{|x|}
  \left(\frac{f'(|x|)}{|x|} - \frac{f(|x|)}{|x|^2}\right)
\left[
\begin{matrix}
x_{1}^2   & x_{1} x_{2}\\
x_{1} x_{2} & x_{2}^2
\end{matrix}
\right],
\end{equation*}
and then compute
\begin{equation*}
\tilde x_k(t+h) = F^{-1}(y_k).
\end{equation*}
These calculations are done in the function {\tt perturb}, if
the quantity {\tt SUBTLE\_PERTURB} is set.

\section{Drawing the Points}

As we stated earlier, most of the drawing functions are contained
in the function {\tt draw\_euler2d}.  If the variable 
{\tt hide\_vortex} is set (and the function {\tt init\_euler2d}
will set this with probability $3/4$), then we only display
the points $\tilde x_k$ for ${\tt Nvortex} < k \le N$.  If 
{\tt hide\_vortex} is not set, then the ``vortex points''
$\tilde x_k$ ($1 \le k \le {\tt Nvortex}$) are displayed in white.
In fact the points $p(\tilde x_k)$ are what are put onto the screen,
and for this we make use of the function {\tt calc\_all\_p}.

\section{Addition to Program: Changing the Power Law}

A later addition to the program adds an option {\tt eulerpower},
which allows one to change the power law that describes how
the vortex points influence other points.  In effect, if this
option is set with the value $m$, then the Biot-Savart Kernel
is replace by
$$ K_1(x,y) = \frac{(x-y)^\perp}{|x-y|^{m+1}}, $$
and
$$ K_2(x,y) = K_1(x,y) - |y|^{1-m} K_1(x,y) .$$
So for example, setting $m=2$ corresponds to the 
quasi-geostrophic equation.  (I haven't yet figured out
what $K_p$ should be, so if $m \ne 1$ we use the unit circle
as the boundary.)

\begin{thebibliography}{9}

\bibitem{BF} Richard L. Burden, J. Douglas Faires, Numerical Analysis,
sixth edition, Brooks/Cole, 1996.

\bibitem{C} Alexandre J. Chorin, Vorticity and Turbulence,
Applied Mathematical Sciences, Vol 103, Springer Verlag, 1994.

\end{thebibliography}

\end{document}