Dear Fipy developers,


I am currently trying to implement a 2D shallow water model in Fipy. This has worked out fine so far, but now I am facing some instability, and don't really know how to resolve that. The model is taken from


http://journals.ametsoc.org/doi/pdf/10.1175/1520-0485%281991%29021%3C1581%3ACEGA%3E2.0.CO%3B2
(eq. 6.1 - 6.3)


and I am trying to reproduce figure 17 (same parameters as in the paper). I implemented the PDE system as follows:



# VARIABLE SETUP
height = CellVariable(mesh=mesh, name='Column height', hasOld=True)
xVelocity = CellVariable(mesh=mesh, name='U', value=0., hasOld=True)
yVelocity = CellVariable(mesh=mesh, name='V', value=0., hasOld=True)
fVelocity = FaceVariable(mesh=mesh, name="Face velocity", rank=1, value=0.)

[ set initial conditions... ]

# EQUATION SETUP
coriolisTermX = ImplicitSourceTerm(var=yVelocity,coeff=.5*mesh.y)
coriolisTermY = ImplicitSourceTerm(var=xVelocity,coeff=.5*mesh.y)
if par["Ah"] != 0:
    xVelocityEq = TransientTerm(var=xVelocity) + \
                    ConvectionTerm(coeff=fVelocity,var=xVelocity) - \
                    coriolisTermX - \
                    DiffusionTerm(coeff=par["Ah"],var=xVelocity) + \
                    height.grad.dot([1.,0.]) \
                    == 0
    yVelocityEq = TransientTerm(var=yVelocity) + \
                    ConvectionTerm(coeff=fVelocity,var=yVelocity) - \
                    coriolisTermY - \
                    DiffusionTerm(coeff=par["Ah"],var=yVelocity) + \
                    height.grad.dot([0.,1.]) \
                    == 0
heightEq = TransientTerm(var=height) + \
            ConvectionTerm(coeff=fVelocity,var=height) \
            == 0
eqSystem = xVelocityEq & yVelocityEq & heightEq # couple equations

# BOUNDARY CONDITIONS
xVelocity.constrain(0., mesh.exteriorFaces)
yVelocity.constrain(0., mesh.exteriorFaces)
height.faceGrad.constrain(0., mesh.exteriorFaces)


and eventually solve it by calling

t = 0.
while t < (par["t1"] * DAY_TO_T):
    xVelocity.updateOld()
    yVelocity.updateOld()
    height.updateOld()
    residual = 1
    while residual > par["absTol"]:
        fVelocity[0] = xVelocity.arithmeticFaceValue
        fVelocity[1] = yVelocity.arithmeticFaceValue
        fVelocity[..., mesh.exteriorFaces.value] = 0.
        residual = eqSystem.sweep(dt = dt)
    t += dt



As you see, I am treating each velocity component as its own variable and then couple all equations together. The system is then solved by sweeping and setting the face velocity in every iteration, since I need it for the convection terms. But when I run this with the parameters from the paper, the model becomes unstable, and all water eventually piles up in one corner of the domain. Thus, a couple of questions:


* Is it really necessary to have the face velocity as a separate variable? Could I not somehow use the cell velocities and achieve better results (solve for the convection coefficient implicitly)?

* Did I translate the equations correctly? I was not sure if the convection terms really represent the form from the paper.

* Which type of convection term should I use? I do not really care much about speed, I just want an accurate solution.

* Is the height.grad term correct? Could I somehow write this as an (implicit) source, or is this done automatically?

* Should I implement a SIMPLE algorithm (as in the cavity flow example)?


I have attached the full version of the script and the parameter file I used. This should be runnable without further packages.


Thanks a bunch!

Dion Häfner
#!/usr/bin/python
# -*- coding: utf8 -*-

"""
Solves the adjustment problem in 2D as described by Killworth et al. (1991):
CROSS-EQUATORIAL GEOSTROPHIC ADJUSTMENT
http://journals.ametsoc.org/doi/pdf/10.1175/1520-0485%281991%29021%3C1581%3ACEGA%3E2.0.CO%3B2

Author:
        Dion Häfner ([email protected])

Usage:
        $ python adjustment.py <inifile> <options>

Reccomended options: (for speed)
        --inline --pysparse
"""


# IMPORT PACKAGES
import matplotlib as mpl
mpl.use('Agg')
from fipy import *
from fipy.tools import numerix
from fipy.meshes.uniformGrid2D import UniformGrid2D
import sys
import os
try:
    import seaborn as sns
    sns.set("talk")
    have_seaborn = True
except ImportError:
    have_seaborn = False
import numpy as np
try:
    import ConfigParser as configparser
except ImportError:
    import configparser
import sys

# PARSE COMMAND LINE
if len(sys.argv) < 2 or not os.path.isfile(sys.argv[1]):
    print("Usage: $ python adjustment.py <inifile> <options>")
    sys.exit()

# DEFINE CUSTOM "TYPES"
def floatlist(x):
    xlist = x.split()
    return list(map(float,xlist))

def intlist(x):
    xlist = x.split()
    return list(map(int,xlist))

# SET VALID PARAMETER KEYS
PARAMETER_KEYS = (
    ("general", (("name",str),)),
    ("domain", (("L",floatlist), ("N",intlist))),
    ("initial", (("Variable",str),("Val1",float),("Val2",float),("Y",floatlist))),
    ("parameters", (("Ah",float),("g",float),("beta",float),("H",float))),
    ("solver", (("t1",float), ("dtIncreaseFactor",float),
                ("dtDecreaseFactor",float),("absTol",float),
                ("timeStepDuration",float),("maxIterations",int))),
    ("output", (("outputEvery",float),))
)

# PARSE PARAMETER FILE
par = {}
config = configparser.RawConfigParser()
config.read(sys.argv[1])

for section, keys in PARAMETER_KEYS:
    for key, key_type in keys:
        try:
            par[key] = key_type(config.get(section, key))
        except ValueError:
            raise ValueError("Key {0} in section {1} must be of type {2}"\
                    .format(key,section,key_type))

# CONVERSION FACTORS
MS_TO_V = 1./np.sqrt(par["g"]*par["H"])
DAY_TO_T = np.sqrt(2*par["beta"]/MS_TO_V)*(3600*24)
KM_TO_X = np.sqrt(2*par["beta"]*MS_TO_V)*1000

# CREATE OUTPUT FOLDER
OUTPUT_PATH = "output/{0}".format(par["name"])
if not os.path.exists(OUTPUT_PATH):
    os.makedirs(OUTPUT_PATH)

# MESH SETUP
dLx = par["L"][0]/par["N"][0]* KM_TO_X
dLy = par["L"][1]/par["N"][1]* KM_TO_X
mesh = UniformGrid2D(nx=par["N"][0], ny=par["N"][1], dx=dLx, dy=dLy,
            origin=((-par["L"][0]/2 * KM_TO_X,),(-par["L"][1]/2 * KM_TO_X,)))

# VARIABLE SETUP
height = CellVariable(mesh=mesh, name='Column height', hasOld=True)
xVelocity = CellVariable(mesh=mesh, name='U', value=0., hasOld=True)
yVelocity = CellVariable(mesh=mesh, name='V', value=0., hasOld=True)
fVelocity = FaceVariable(mesh=mesh, name="Face velocity", rank=1, value=0.)

# INITIAL CONDITIONS
x, y = mesh.cellCenters
segment = np.logical_and(par["Y"][0]*KM_TO_X <= y, y <= par["Y"][1]*KM_TO_X)
if par["Variable"] == "height":
    height.setValue(par["Val2"])
    height.setValue(par["Val1"], where=segment)
elif par["Variable"] == "U":
    xVelocity.setValue(par["Val2"])
    xVelocity.setValue(par["Val1"], where=segment)
elif par["Variable"] == "V":
    yVelocity.setValue(par["Val2"])
    yVelocity.setValue(par["Val1"], where=segment)
else:
    raise ValueError("Initial condition parameter 'Variable' must either be "
                    "'height', 'U', or 'V'")

# EQUATION SETUP
coriolisTermX = ImplicitSourceTerm(var=yVelocity,coeff=.5*mesh.y)
coriolisTermY = ImplicitSourceTerm(var=xVelocity,coeff=.5*mesh.y)
if par["Ah"] != 0: # WITH FRICTION
    xVelocityEq = TransientTerm(var=xVelocity) + \
                    ConvectionTerm(coeff=fVelocity,var=xVelocity) - \
                    coriolisTermX - \
                    DiffusionTerm(coeff=par["Ah"],var=xVelocity) + \
                    height.grad.dot([1.,0.]) \
                    == 0
    yVelocityEq = TransientTerm(var=yVelocity) + \
                    ConvectionTerm(coeff=fVelocity,var=yVelocity) - \
                    coriolisTermY - \
                    DiffusionTerm(coeff=par["Ah"],var=yVelocity) + \
                    height.grad.dot([0.,1.]) \
                    == 0
elif par["Ah"] == 0: # WITHOUT FRICTION
    xVelocityEq = TransientTerm(var=xVelocity) + \
                    ConvectionTerm(coeff=fVelocity,var=xVelocity) - \
                    coriolisTermX + \
                    height.grad.dot([1.,0.]) \
                    == 0
    yVelocityEq = TransientTerm(var=yVelocity) + \
                    ConvectionTerm(coeff=fVelocity,var=yVelocity) - \
                    coriolisTermY + \
                    height.grad.dot([0.,1.]) \
                    == 0
heightEq = TransientTerm(var=height) + \
            ConvectionTerm(coeff=fVelocity,var=height) \
            == 0
eqSystem = xVelocityEq & yVelocityEq & heightEq # couple equations

# BOUNDARY CONDITIONS
xVelocity.constrain(0., mesh.exteriorFaces)
yVelocity.constrain(0., mesh.exteriorFaces)
height.faceGrad.constrain(0., mesh.exteriorFaces)

# SET UP VIEWERS
if have_seaborn:
    cmap = sns.diverging_palette(220, 10, as_cmap=True)
else:
    cmap = plt.get_cmap("coolwarm")
mplViewers = []
for var in (height,xVelocity,yVelocity):
    mplViewers.append(MatplotlibViewer(vars=(var),
        xmin=-par["L"][0]/2 * KM_TO_X, xmax=par["L"][0]/2 * KM_TO_X,
        ymin=-par["L"][1]/2 * KM_TO_X, ymax=par["L"][1]/2 * KM_TO_X,
        colorbar=True, cmap=cmap))
viewer = MultiViewer(mplViewers)
fViewer = MatplotlibVectorViewer(vars=fVelocity,
        xmin=-par["L"][0]/2 * KM_TO_X, xmax=par["L"][0]/2 * KM_TO_X,
        ymin=-par["L"][1]/2 * KM_TO_X, ymax=par["L"][1]/2 * KM_TO_X,
        sparsity=100, scale=.5)
vtkViewer = VTKCellViewer(vars=(height, xVelocity, yVelocity))

# CONCENIENCE FUNCTION TO PLOT ALL VIEWERS AT ONCE
def output(t):
    for v in viewer.viewers:
        v.plot("{0}/{1}-{2:.2f}days.png"\
                .format(OUTPUT_PATH,v.title,t/DAY_TO_T))
    fViewer.quiver()
    fViewer.plot("{0}/velocity-{1:.2f}days.png".format(OUTPUT_PATH,t/DAY_TO_T))
    vtkViewer.plot("{0}/day{1:.2f}.vtu".format(OUTPUT_PATH,t/DAY_TO_T))

# PLOT INITIAL STATE
output(0)

# MAIN TIME LOOP
t = 0.
dt = par["timeStepDuration"]
while t < (par["t1"] * DAY_TO_T):
    xVelocity.updateOld()
    yVelocity.updateOld()
    height.updateOld()
    residual = 1
    j = 0
    # ITERATIVE SOLUTION LOOP
    while residual > par["absTol"]:
        fVelocity[0] = xVelocity.arithmeticFaceValue
        fVelocity[1] = yVelocity.arithmeticFaceValue
        fVelocity[..., mesh.exteriorFaces.value] = 0.
        residual = eqSystem.sweep(dt = dt)
        j += 1
        if j > par["maxIterations"]:
            dt /= par["dtDecreaseFactor"]
            print("\nREDUCING DT")
            output_now = False
            j = 0
        sys.stdout.write("Residual: {0:.2e}\r".format(residual))
    t += dt

    # PRINT OUTPUT
    if output_now:
        output(t)
        output_now = False
        dt = dtTemp * par["dtIncreaseFactor"]

    # CHECK IF OUTPUT IS DUE AFTER NEXT TIME STEP
    nextOutput = t % (par["outputEvery"] * DAY_TO_T)
    outputTimestep = (par["outputEvery"] * DAY_TO_T) - nextOutput
    if outputTimestep < par["dtIncreaseFactor"]*dt:
        output_now = True
        dtTemp = dt
        dt = outputTimestep + 1E-4
        print("\nLIMITING DT\n Current time: {0:.1e}\tTime step: {1:.1e}"\
                .format(t,dt))
    else:
        output_now = False
        dt *= par["dtIncreaseFactor"]
        print("\nINCREASING DT\n Current time: {0:.1e}\tTime step: {1:.1e}"\
                .format(t,dt))
[general]
name = killworth

[domain]
L = 4000 2000
N = 80 40

[initial]
# possible values: height, U, V
Variable = height
Val1 = 1
Val2 = 0.5
# Val1 between the two Y values, everywhere else Val2
Y = -1000 -75

[parameters]
g = 0.01
beta = 2E-11
H = 400
Ah = 0.02

[solver]
t1 = 100
dtIncreaseFactor = 1.2
dtDecreaseFactor = 2
absTol = 1E-10
timeStepDuration = 0.1
maxIterations = 10

[output]
# print output every n days
outputEvery = .5

_______________________________________________
fipy mailing list
[email protected]
http://www.ctcms.nist.gov/fipy
  [ NIST internal ONLY: https://email.nist.gov/mailman/listinfo/fipy ]

Reply via email to