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

Moving meshes final #431

Draft
wants to merge 37 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
0789602
first attempt at mesh movement timeloop
jshipton Feb 16, 2023
a9f390a
progress: moving mesh Galewsky script runs and produces an adapted in…
jshipton Feb 16, 2023
f215579
Progress: runs but is blowing up. Much tidying needed
jshipton Feb 16, 2023
e314d47
oops, forgot these files
jshipton Feb 24, 2023
f8e282d
hack circulation form a bit for now
jshipton Mar 9, 2023
756be5e
oops, I forgot that the vector_invariant_form already included a grad…
jshipton Mar 9, 2023
d90fb4d
fix minus sign
jshipton Mar 18, 2023
5f49d46
a couple of hard coded constant_jacobian=False args
jshipton Mar 18, 2023
b85f770
Merge branch 'main' of https://github.com/firedrakeproject/gusto into…
jshipton May 17, 2023
905e417
these LVPs need constant_Jacobian=False
jshipton May 18, 2023
0d38a79
some fixes to setup script
jshipton May 18, 2023
54b1f05
IT WORKS!
jshipton Jun 6, 2023
34305f0
remove debugging prints from timeloop
jshipton Jun 15, 2023
782ade1
remove another debugging print
jshipton Jun 15, 2023
164c31a
tidy code and fix memory issues
jshipton Jun 15, 2023
04569b0
enable adapting to field
jshipton Jun 19, 2023
8394235
put some info in the directory name
jshipton Jun 19, 2023
4ce1683
remove factor of a half
jshipton Aug 24, 2023
71bcb2a
refactor mesh movement timeloop so that the new mesh is only computed…
jshipton Aug 24, 2023
166d2b2
Merge branch 'main' of https://github.com/firedrakeproject/gusto into…
jshipton Aug 24, 2023
cd0232f
fix import error related to merge
jshipton Aug 24, 2023
af283f9
fix some lint
jshipton Aug 24, 2023
f9ee380
tiny lint fix
jshipton Aug 24, 2023
e2451b1
get perp from the right place - not sure how this works on main!
jshipton Aug 25, 2023
5f5b6a1
removing hack to set up diagnostics earlier as this breaks tests
jshipton Aug 25, 2023
2c36527
I think this fixes the tests but might break mesh movement - check si…
jshipton Aug 25, 2023
40e9116
more lint fixing
jshipton Aug 25, 2023
389bde7
more fixes for merge with main
jshipton Aug 25, 2023
dc238c8
I think this works with the new definition of the transport forms
jshipton Aug 25, 2023
809ceed
make solver non-convergence warning go to logger
jshipton Aug 26, 2023
dda98e9
fix linting of moving mesh example file
jshipton Aug 26, 2023
2d9807d
not quite sure how this worked without the definition of the ke_form
jshipton Aug 26, 2023
5c09458
Merge branch 'main' into moving_meshes_final
jshipton Aug 26, 2023
17a7b5d
don't kill the testing by adding a stupidly long example without the …
jshipton Aug 26, 2023
6118eb2
Merge branch 'moving_meshes_final' of https://github.com/firedrakepro…
jshipton Aug 26, 2023
473d872
import sys to fix running moving mesh example through pytest
jshipton Aug 27, 2023
bd5eb9b
set up cubed sphere galewsky with moving mesh
jshipton Aug 27, 2023
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
223 changes: 223 additions & 0 deletions examples/shallow_water/mm_ot_sw_galewsky_jet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
from gusto import *
from firedrake import IcosahedralSphereMesh, \
Constant, ge, le, exp, cos, \
conditional, interpolate, SpatialCoordinate, VectorFunctionSpace, \
Function, assemble, dx, pi, CellNormal

import numpy as np
import sys

day = 24.*60.*60.
dt = 240.

if '--running-tests' in sys.argv:
ref_level = 3
dt = 480.
tmax = 1440.
cubed_sphere = False
else:
# setup resolution and timestepping parameters
cubed_sphere = True
if cubed_sphere:
ncells = 24
dt = 200.
else:
ref_level = 4
dt = 240.
tmax = 6*day

# setup shallow water parameters
R = 6371220.
H = 10000.
parameters = ShallowWaterParameters(H=H)

perturb = True
if perturb:
dirname = "mm_ot_sw_galewsky_jet_perturbed_dt%s" % dt
else:
dirname = "mm_ot_sw_galewsky_jet_unperturbed"

# Domain
if cubed_sphere:
dirname += "_cs%s" % ncells
mesh = GeneralCubedSphereMesh(radius=R,
num_cells_per_edge_of_panel=ncells,
degree=2)
family = "RTCF"
else:
dirname += "_is%s" % ref_level
mesh = IcosahedralSphereMesh(radius=R,
refinement_level=ref_level, degree=2)
family = "BDM"
domain = Domain(mesh, dt, family, 1, move_mesh=True)

# Equation
Omega = parameters.Omega
x = SpatialCoordinate(domain.mesh)
fexpr = 2*Omega*x[2]/R
eqns = ShallowWaterEquations(domain, parameters, fexpr=fexpr,
u_transport_option="vector_invariant_form")

# I/O
output = OutputParameters(dirname=dirname,
dumplist_latlon=['D', 'PotentialVorticity',
'RelativeVorticity'])

pv = PotentialVorticity()
diagnostic_fields = [pv, RelativeVorticity()]
io = IO(domain, output, diagnostic_fields=diagnostic_fields)

# Transport schemes
transported_fields = [TrapeziumRule(domain, "u"),
SSPRK3(domain, "D")]

transport_methods = [DGUpwind(eqns, "u"), DGUpwind(eqns, "D")]


# Mesh movement
def update_pv():
pv()


def reinterpolate_coriolis():
domain.k = interpolate(x/R, domain.mesh.coordinates.function_space())
domain.outward_normals.interpolate(CellNormal(domain.mesh))
eqns.prescribed_fields("coriolis").interpolate(fexpr)


monitor = MonitorFunction("PotentialVorticity", adapt_to="gradient")
mesh_generator = OptimalTransportMeshGenerator(domain.mesh,
monitor,
pre_meshgen_callback=update_pv,
post_meshgen_callback=reinterpolate_coriolis)

# Time stepper
stepper = MeshMovement(eqns, io, transported_fields,
spatial_methods=transport_methods,
mesh_generator=mesh_generator)

# initial conditions
u0 = stepper.fields("u")
D0 = stepper.fields("D")

# get lat lon coordinates
theta, lamda = latlon_coords(mesh)

# expressions for meridional and zonal velocity
u_max = 80.0
theta0 = pi/7.
theta1 = pi/2. - theta0
en = np.exp(-4./((theta1-theta0)**2))
u_zonal_expr = (u_max/en)*exp(1/((theta - theta0)*(theta - theta1)))
u_zonal = conditional(ge(theta, theta0), conditional(le(theta, theta1), u_zonal_expr, 0.), 0.)
u_merid = 0.0

# get cartesian components of velocity
uexpr = sphere_to_cartesian(mesh, u_zonal, u_merid)
u0.project(uexpr, form_compiler_parameters={'quadrature_degree': 12})

Rc = Constant(R)
g = Constant(parameters.g)


def D_integrand(th):
# Initial D field is calculated by integrating D_integrand w.r.t. theta
# Assumes the input is between theta0 and theta1.
# Note that this function operates on vectorized input.
from scipy import exp, sin, tan
f = 2.0*parameters.Omega*sin(th)
u_zon = (80.0/en)*exp(1.0/((th - theta0)*(th - theta1)))
return u_zon*(f + tan(th)*u_zon/R)


def Dval(X):
# Function to return value of D at X
from scipy import integrate

# Preallocate output array
val = np.zeros(len(X))

angles = np.zeros(len(X))

# Minimize work by only calculating integrals for points with
# theta between theta_0 and theta_1.
# For theta <= theta_0, the integral is 0
# For theta >= theta_1, the integral is constant.

# Precalculate this constant:
poledepth, _ = integrate.fixed_quad(D_integrand, theta0, theta1, n=64)
poledepth *= -R/parameters.g

angles[:] = np.arcsin(X[:, 2]/R)

for ii in range(len(X)):
if angles[ii] <= theta0:
val[ii] = 0.0
elif angles[ii] >= theta1:
val[ii] = poledepth
else:
# Fixed quadrature with 64 points gives absolute errors below 1e-13
# for a quantity of order 1e-3.
v, _ = integrate.fixed_quad(D_integrand, theta0, angles[ii], n=64)
val[ii] = -(R/parameters.g)*v

return val


# Get coordinates to pass to Dval function
W = VectorFunctionSpace(mesh, D0.ufl_element())
X = interpolate(mesh.coordinates, W)
D0.dat.data[:] = Dval(X.dat.data_ro)

# Adjust mean value of initial D
C = Function(D0.function_space()).assign(Constant(1.0))
area = assemble(C*dx)
Dmean = assemble(D0*dx)/area
D0 -= Dmean
D0 += Constant(parameters.H)

# optional perturbation
if perturb:
alpha = Constant(1/3.)
beta = Constant(1/15.)
Dhat = Constant(120.)
theta2 = Constant(pi/4.)
g = Constant(parameters.g)
D_pert = Function(D0.function_space()).interpolate(Dhat*cos(theta)*exp(-(lamda/alpha)**2)*exp(-((theta2 - theta)/beta)**2))
D0 += D_pert


def initialise_fn():
u0 = stepper.fields("u")
D0 = stepper.fields("D")

u0.project(uexpr, form_compiler_parameters={'quadrature_degree': 12})

X = interpolate(domain.mesh.coordinates, W)
D0.dat.data[:] = Dval(X.dat.data_ro)
area = assemble(C*dx)
Dmean = assemble(D0*dx)/area
D0 -= Dmean
D0 += parameters.H
if perturb:
theta, lamda = latlon_coords(domain.mesh)
D_pert.interpolate(Dhat*cos(theta)*exp(-(lamda/alpha)**2)*exp(-((theta2 - theta)/beta)**2))
D0 += D_pert
domain.k = interpolate(x/R, domain.mesh.coordinates.function_space())
domain.outward_normals.interpolate(CellNormal(domain.mesh))
eqns.prescribed_fields("coriolis").interpolate(fexpr)
pv()


pv.setup(domain, stepper.fields)
mesh_generator.get_first_mesh(initialise_fn)

domain.k = interpolate(x/R, domain.mesh.coordinates.function_space())
domain.outward_normals.interpolate(CellNormal(domain.mesh))
eqns.prescribed_fields("coriolis").interpolate(fexpr)
pv()

Dbar = Function(D0.function_space()).assign(H)
stepper.set_reference_profiles([('D', Dbar)])

stepper.run(t=0, tmax=tmax)
1 change: 1 addition & 0 deletions gusto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def perp(self, o, a):
from gusto.limiters import * # noqa
from gusto.linear_solvers import * # noqa
from gusto.meshes import * # noqa
from gusto.moving_mesh import * # noqa
from gusto.numerical_integrator import * # noqa
from gusto.physics import * # noqa
from gusto.preconditioners import * # noqa
Expand Down
2 changes: 1 addition & 1 deletion gusto/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -1452,7 +1452,7 @@ def setup(self, domain, state_fields, vorticity_type=None):
f = state_fields("coriolis")
L += gamma*f*dx

problem = LinearVariationalProblem(a, L, self.field)
problem = LinearVariationalProblem(a, L, self.field, constant_jacobian=False)
self.evaluator = LinearVariationalSolver(problem, solver_parameters={"ksp_type": "cg"})


Expand Down
6 changes: 5 additions & 1 deletion gusto/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ class Domain(object):
the same then this can be specified through the "degree" argument.
"""
def __init__(self, mesh, dt, family, degree=None,
horizontal_degree=None, vertical_degree=None):
horizontal_degree=None, vertical_degree=None,
move_mesh=False):
"""
Args:
mesh (:class:`Mesh`): the model's mesh.
Expand Down Expand Up @@ -59,6 +60,9 @@ def __init__(self, mesh, dt, family, degree=None,
else:
raise TypeError(f'dt must be a Constant, float or int, not {type(dt)}')

# store whether we are moving the mesh
self.move_mesh = move_mesh

# -------------------------------------------------------------------- #
# Build compatible function spaces
# -------------------------------------------------------------------- #
Expand Down
23 changes: 20 additions & 3 deletions gusto/equations.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
from gusto.fml import (Term, all_terms, keep, drop, Label, subject, name,
replace_subject, replace_trial_function)
from gusto.labels import (time_derivative, transport, prognostic, hydrostatic,
linearisation, pressure_gradient, coriolis)
linearisation, pressure_gradient, coriolis,
transporting_velocity)
from gusto.thermodynamics import exner_pressure
from gusto.common_forms import (advection_form, continuity_form,
vector_invariant_form, kinetic_energy_form,
Expand Down Expand Up @@ -236,6 +237,7 @@ def __init__(self, field_names, domain, space_names,

# Make the full mixed function space
W = MixedFunctionSpace(self.spaces)
self.W = W

# Can now call the underlying PrognosticEquation
full_field_name = "_".join(self.field_names)
Expand Down Expand Up @@ -668,14 +670,29 @@ def __init__(self, domain, parameters, fexpr=None, bexpr=None,
# -------------------------------------------------------------------- #
# Transport Terms
# -------------------------------------------------------------------- #

# Mesh movement requires the circulation form, and an
# additional modification
if domain.move_mesh:
assert u_transport_option == "vector_invariant_form"

# Velocity transport term -- depends on formulation
if u_transport_option == "vector_invariant_form":
u_adv = prognostic(vector_invariant_form(domain, w, u, u), 'u')
elif u_transport_option == "vector_advection_form":
u_adv = prognostic(advection_form(w, u, u), 'u')
if domain.move_mesh:
ke_form = prognostic(kinetic_energy_form(w, u, u), "u")
ke_form = transport.remove(ke_form)
ke_form = ke_form.label_map(
lambda t: t.has_label(transporting_velocity),
lambda t: Term(ufl.replace(
t.form, {t.get(transporting_velocity): u}), t.labels))
ke_form = transporting_velocity.remove(ke_form)
u_adv -= ke_form
elif u_transport_option == "circulation_form":
ke_form = prognostic(kinetic_energy_form(w, u, u), 'u')
u_adv = prognostic(advection_equation_circulation_form(domain, w, u, u), 'u') + ke_form
ke_form = prognostic(kinetic_energy_form(w, u, u), "u")
u_adv = prognostic(advection_equation_circulation_form(domain, w, u, u), "u") + ke_form
else:
raise ValueError("Invalid u_transport_option: %s" % u_transport_option)

Expand Down
6 changes: 4 additions & 2 deletions gusto/forcing.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,13 @@ def __init__(self, equation, alpha):

# now we can set up the explicit and implicit problems
explicit_forcing_problem = LinearVariationalProblem(
a.form, L_explicit.form, self.xF, bcs=bcs
a.form, L_explicit.form, self.xF, bcs=bcs,
constant_jacobian=False
)

implicit_forcing_problem = LinearVariationalProblem(
a.form, L_implicit.form, self.xF, bcs=bcs
a.form, L_implicit.form, self.xF, bcs=bcs,
constant_jacobian=False
)

self.solvers = {}
Expand Down
3 changes: 2 additions & 1 deletion gusto/linear_solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,8 @@ def __init__(self, equation, alpha):
bcs = [DirichletBC(W.sub(0), bc.function_arg, bc.sub_domain) for bc in equation.bcs['u']]
problem = LinearVariationalProblem(aeqn.form,
action(Leqn.form, self.xrhs),
self.dy, bcs=bcs)
self.dy, bcs=bcs,
constant_jacobian=False)

self.solver = LinearVariationalSolver(problem,
solver_parameters=self.solver_parameters,
Expand Down
3 changes: 3 additions & 0 deletions gusto/moving_mesh/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from gusto.moving_mesh.monge_ampere import * # noqa
from gusto.moving_mesh.monitor import * # noqa
from gusto.moving_mesh.utility_functions import * # noqa
Loading
Loading