Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

steady Navier–Stokes by natural parameter continuation #145

Merged
merged 43 commits into from
Apr 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
0d8cf4f
complete but not working
gdmcbain Mar 21, 2019
4887b95
correct essential boundary conditions in residual
gdmcbain Mar 21, 2019
0b45be5
added df_dlmbda for pacopy
gdmcbain Mar 21, 2019
3821380
display stream-lines in callback
gdmcbain Mar 21, 2019
0bcb096
increased maximum Re, refined mesh
gdmcbain Mar 21, 2019
4e8b31b
increased number of stream-lines; showed whole channel
gdmcbain Mar 21, 2019
d2ed5f7
deleted methods assuming values of nodal coordinates in favour of L2 …
gdmcbain Mar 21, 2019
4083307
dim=2 #142
gdmcbain Mar 21, 2019
7ad7f10
tweaked Re and stream-line plotting parameters
gdmcbain Mar 21, 2019
4782b8e
Merge branch 'master' into ns
gdmcbain Mar 21, 2019
82b6a56
correct sign on nonlinear terms!
gdmcbain Mar 21, 2019
8771c2b
moved method .make_vector after those it depends on
gdmcbain Mar 21, 2019
9fdb034
DOC: renamed .solve method .creeping
gdmcbain Mar 21, 2019
01c3951
DOC: .split
gdmcbain Mar 21, 2019
889a43e
Revert "correct sign on nonlinear terms!"
gdmcbain Mar 22, 2019
e1ea97b
reinforce boundary conditions at each Newton iteration
gdmcbain Mar 22, 2019
1aaa7f0
get the indices of the gradient of a vector the right way around!!
gdmcbain Mar 22, 2019
5105752
plot at last Re rather than first
gdmcbain Mar 22, 2019
9acfde7
DOC: started on steady Navier–Stokes by natural parameter continuation
gdmcbain Mar 22, 2019
fad8fd4
links for ns.rst
gdmcbain Mar 22, 2019
21ae9c0
DOC: .mesh_plot
gdmcbain Mar 22, 2019
f528882
save stream-line plot at each Reynolds number (pending nschloe/pacopy#5)
gdmcbain Mar 22, 2019
85eab39
derived residual and jacobian
gdmcbain Mar 22, 2019
10a2396
scrap final plot as solution hasn't been saved
gdmcbain Mar 22, 2019
af585d7
Merge branch 'master' into ns
gdmcbain Mar 25, 2019
770caf5
Merge branch 'master' into ns
gdmcbain Mar 26, 2019
bf829ae
generate_mesh returns Mesh object nschloe/pygmsh#258 #154
gdmcbain Apr 1, 2019
7a1a869
replace deprecated with add_physical_surface add_physical nschloe/pyg…
gdmcbain Apr 1, 2019
a181c06
merge
gdmcbain Apr 4, 2019
b5e6a4a
Merge branch 'master' into ns
gdmcbain Apr 17, 2019
5af5a3b
Newton-Krylov-Navier-Stokes as modification of direct #169
gdmcbain Apr 17, 2019
3173870
rearrange imports
gdmcbain Apr 17, 2019
888282f
delete unused variables
gdmcbain Apr 17, 2019
1b43157
nkns is for another branch
gdmcbain Apr 17, 2019
a371d4a
rearrange imports
gdmcbain Apr 17, 2019
741fb74
pass in name for figure as an argument to callback
gdmcbain Apr 17, 2019
2be0c3c
plot at specified milestones in Reynolds number nschloe/pacopy#6
gdmcbain Apr 23, 2019
47e5674
numbered ns 27
gdmcbain Apr 23, 2019
6841ba4
figures for stream-lines now that Re milestones are fixed
gdmcbain Apr 23, 2019
78c2071
implicit einsum for Navier–Stokes acceleration linear form #145
gdmcbain Apr 29, 2019
954d429
DOC: explaining the linear form for Navier–Stokes acceleration #145
gdmcbain Apr 29, 2019
bc331ce
sum einsum for Navier–Stokes acceleration forms #145
gdmcbain Apr 29, 2019
22d6185
merge
gdmcbain Apr 29, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/examples/ex23.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _bratu:

Bratu–Gelfand
-------------

Expand Down
2 changes: 2 additions & 0 deletions docs/examples/ex24.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _backwardfacingstep0:

Backward-facing step
--------------------

Expand Down
255 changes: 255 additions & 0 deletions docs/examples/ex27.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
from skfem import *
from skfem.models.poisson import vector_laplace, laplace
from skfem.models.general import divergence, rot

from functools import partial
from itertools import cycle, islice
from typing import Tuple, Iterable

from matplotlib.pyplot import subplots
from matplotlib.tri import Triangulation
import numpy as np
from scipy.sparse import bmat, block_diag, csr_matrix

from pygmsh import generate_mesh
from pygmsh.built_in import Geometry

from pacopy import natural


@linear_form
def acceleration(v, dv, w):
"""Compute the vector (v, u . grad u) for given velocity u

passed in via w after having been interpolated onto its quadrature
points.

In Cartesian tensorial indicial notation, the integrand is

.. math::

u_j u_{i,j} v_i.

"""
u, du = w.w, w.dw
return sum(np.einsum('j...,ij...->i...', u, du) * v)


@bilinear_form
def acceleration_jacobian(u, du, v, dv, w):
"""Compute (v, w . grad u + u . grad w) for given velocity w

passed in via w after having been interpolated onto its quadrature
points.

In Cartesian tensorial indicial notation, the integrand is

.. math::

(w_j du_{i,j} + u_j dw_{i,j}) v_i

"""
return sum((np.einsum('j...,ij...->i...', w.w, du)
+ np.einsum('j...,ij...->i...', u, w.dw)) * v)


class BackwardFacingStep:

element = {'u': ElementVectorH1(ElementTriP2()),
'p': ElementTriP1()}

def __init__(self,
length: float = 35.,
lcar: float = 1.):

self.mesh = self.make_mesh(self.make_geom(length, lcar))
self.basis = {variable: InteriorBasis(self.mesh, e, intorder=3)
for variable, e in self.element.items()}
self.basis['inlet'] = FacetBasis(self.mesh, self.element['u'],
facets=self.mesh.boundaries['inlet'])
self.basis['psi'] = InteriorBasis(self.mesh, ElementTriP2())
self.D = np.setdiff1d(
self.basis['u'].get_dofs().all(),
self.basis['u'].get_dofs(self.mesh.boundaries['outlet']).all())

A = asm(vector_laplace, self.basis['u'])
B = asm(divergence, self.basis['u'], self.basis['p'])
self.S = bmat([[A, -B.T],
[-B, None]]).tocsr()
self.I = np.setdiff1d(np.arange(self.S.shape[0]), self.D)

def make_geom(self, length: float, lcar: float) -> Geometry:
# Barkley et al (2002, figure 3 a - c)
geom = Geometry()

points = []
for point in [[0, -1, 0],
[length, -1, 0],
[length, 1, 0],
[-1, 1, 0],
[-1, 0, 0],
[0, 0, 0]]:
points.append(geom.add_point(point, lcar))

lines = []
for termini in zip(points,
islice(cycle(points), 1, None)):
lines.append(geom.add_line(*termini))

for k, label in [([1], 'outlet'),
([2], 'ceiling'),
([3], 'inlet'),
([0, 4, 5], 'floor')]:
geom.add_physical(list(np.array(lines)[k]), label)

geom.add_physical(
geom.add_plane_surface(geom.add_line_loop(lines)), 'domain')

return geom

@staticmethod
def make_mesh(geom: Geometry) -> MeshTri:
return MeshTri.from_meshio(generate_mesh(geom, dim=2))

def inlet_dofs(self):
inlet_dofs_ = self.basis['u'].get_dofs(self.mesh.boundaries['inlet'])
inlet_dofs_ = self.basis['inlet'].get_dofs(
self.mesh.boundaries['inlet'])
return np.concatenate([inlet_dofs_.nodal[f'u^{1}'],
inlet_dofs_.facet[f'u^{1}']])

@staticmethod
def parabolic(x, y):
"""return the plane Poiseuille parabolic inlet profile"""
return ((4 * y * (1. - y), np.zeros_like(y)))

def make_vector(self):
"""prepopulate solution vector with Dirichlet conditions"""
uvp = np.zeros(self.basis['u'].N + self.basis['p'].N)
I = self.inlet_dofs()
uvp[I] = L2_projection(self.parabolic, self.basis['inlet'], I)
return uvp

def split(self, solution: np.ndarray) -> Tuple[np.ndarray,
np.ndarray]:
"""return velocity and pressure separately"""
return np.split(solution, [self.basis['u'].N])

def streamfunction(self, velocity: np.ndarray) -> np.ndarray:
A = asm(laplace, self.basis['psi'])
psi = np.zeros(self.basis['psi'].N)
D = self.basis['psi'].get_dofs(self.mesh.boundaries['floor']).all()
I = self.basis['psi'].complement_dofs(D)
vorticity = asm(rot, self.basis['psi'],
w=[self.basis['psi'].interpolate(velocity[i::2])
for i in range(2)])
psi[I] = solve(*condense(A, vorticity, I=I))
return psi

def mesh_plot(self):
"""return Axes showing boundary of mesh"""
termini = self.mesh.facets[:, np.concatenate(list(
self.mesh.boundaries.values()))]
_, ax = subplots()
ax.plot(*self.mesh.p[:, termini], color='k')
return ax

def triangulation(self):
return Triangulation(
self.mesh.p[0, :], self.mesh.p[1, :], self.mesh.t.T)

def streamlines(self, psi: np.ndarray, n: int = 11, ax=None):
if ax is None:
ax = self.mesh_plot()
n_streamlines = n
plot = partial(ax.tricontour,
self.triangulation(),
psi[self.basis['psi'].nodal_dofs.flatten()],
linewidths=.5)
for levels, color, style in [
(np.linspace(0, 2/3, n_streamlines),
'k',
['dashed'] + ['solid']*(n_streamlines - 2) + ['dashed']),
(np.linspace(2/3, max(psi), n_streamlines)[0:],
'r', 'solid'),
(np.linspace(min(psi), 0, n_streamlines)[:-1],
'g', 'solid')]:
plot(levels=levels, colors=color, linestyles=style)

ax.set_aspect(1.)
ax.axis('off')
return ax

def inner(self, u: np.ndarray, v: np.ndarray) -> float:
"""return the inner product of two solutions

using just the velocity, ignoring the pressure

"""
return self.split(u)[0] @ self.split(v)[0]

def norm2_r(self, u: np.ndarray) -> float:
return self.inner(u, u)

def N(self, uvp: np.ndarray) -> np.ndarray:
u = self.basis['u'].interpolate(self.split(uvp)[0])
return np.hstack([asm(acceleration, self.basis['u'], w=u),
np.zeros(self.basis['p'].N)])

def f(self, uvp: np.ndarray, reynolds: float) -> np.ndarray:
"""Return the residual of a given velocity-pressure solution"""
out = self.S @ uvp + reynolds * self.N(uvp)

out[self.D] = uvp[self.D] - self.make_vector()[self.D]
return out

def df_dlmbda(self, uvp: np.ndarray, reynolds: float) -> np.ndarray:
out = self.N(uvp)
out[self.D] = 0.
return out

def jacobian_solver(self,
uvp: np.ndarray,
reynolds: float,
rhs: np.ndarray) -> np.ndarray:
duvp = self.make_vector() - uvp
u = self.basis['u'].interpolate(self.split(uvp)[0])
duvp[self.I] = solve(*condense(
self.S +
reynolds
* block_diag([asm(acceleration_jacobian, self.basis['u'], w=u),
csr_matrix((self.basis['p'].N,)*2)]),
rhs, duvp, I=self.I))
return duvp


bfs = BackwardFacingStep(lcar=.2)


def callback(_: int,
reynolds: float,
uvp: np.ndarray,
name: str,
milestones=Iterable[float]):
"""Echo the Reynolds number and plot streamlines at milestones"""
print(f'Re = {reynolds}')

if reynolds in milestones:
ax = bfs.streamlines(bfs.streamfunction(bfs.split(uvp)[0]))
ax.set_title(f'Re = {reynolds}')
ax.get_figure().savefig(f'{name}-{reynolds}-psi.png',
bbox_inches="tight", pad_inches=0)


if __name__ == '__main__':

from os.path import splitext
from sys import argv

milestones = [50., 150., 450., 750.]
natural(bfs, bfs.make_vector(), 0.,
partial(callback,
name=splitext(argv[0])[0],
milestones=milestones),
lambda_stepsize0=50.,
milestones=milestones)
74 changes: 74 additions & 0 deletions docs/examples/ex27.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
.. _navierstokes:

Navier–Stokes equations
-----------------------

.. note::
This example requires the external packages `pygmsh <https://pypi.org/project/pygmsh/>`_ and `pacopy <https://pypi.org/project/pacopy/>`_.

In this example, `pacopy <https://pypi.org/project/pacopy/>`_ is used to extend the example :ref:`backwardfacingstep0` from creeping flow over a backward-facing step to finite Reynolds number; as in the example on the bifurcation of the Bratu–Gelfand problem :ref:`bratu`, this means defining a residual for the nonlinear problem and its derivatives with respect to the solution and to the parameter (here Reynolds number).

Compared to the Stokes equations of example :ref:`backwardfacingstep0`, the Navier–Stokes equation has one additional term. If the problem is nondimensionalized using a characteristic length (here the height of the step) and velocity (the average over the inlet), this term appears multiplied by the Reynolds number, which thus serves as a convenient parameter for numerical continuation.

.. math::

-\nabla^2 \mathbf u + \nabla p - \mathrm{Re} \mathbf u \cdot\nabla\mathbf u = 0

\nabla\cdot\mathbf u = 0.

The weak formulation can be written

.. math::

(\nabla\mathbf v, \nabla\mathbf u) - (\nabla\cdot\mathbf v, p)
+ (q, \nabla\cdot\mathbf u)
- \mathrm{Re} (\mathbf v, \mathbf u\cdot\nabla\mathbf u) = 0.

In shorthand,

.. math::

F (u; \mathrm{Re}) = S (u) - \mathrm{Re} N (u) = 0

where the linear (creeping or Stokes) part is

.. math::

S (u) = \sum_j \begin{bmatrix}
(\nabla v_i, \nabla v_j) & -(\nabla\cdot v_i, q_j) \\
(q_i, \nabla\cdot v_j) & 0
\end{bmatrix}\begin{Bmatrix} \mathbf u_j \\ p_j\end{Bmatrix}

and the nonlinear part is

.. math::

N(u) = (\mathbf v, \mathbf u\cdot\nabla\mathbf u).


The Jacobian is

.. math::

J(u; \mathrm{Re}) \equiv \frac{\partial F}{\partial u} = -\mathrm{Re} N'(u)

which is

.. math::
N' (u) =
(\mathbf v,
\mathbf u\cdot\nabla\mathbf\delta u
+ \delta\mathbf u\cdot\nabla\mathbf u).

The stream-lines are plotted at Re = 150, 450, and 750 (as in figures 3 *a*–*c* of Barkley, Gomes, & Henderson 2002), these values being specified as ``milestones`` for :func:`~pacopy.natural`.

.. figure:: ex27-ex27-150.0-psi.png

.. figure:: ex27-ex27-450.0-psi.png

.. figure:: ex27-ex27-750.0-psi.png

The stream-lines at Re = 150, 450, and 750.

.. literalinclude:: ex27.py
:linenos: