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

Coupled transport #415

Merged
merged 33 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
d3dca64
started butcher tableau implementation
ta440 Jul 24, 2023
01883ef
more changes
ta440 Jul 24, 2023
cf99916
bug fixes for coding
alexbrown1995 Jul 24, 2023
82a313f
bug fixes - numpy not used
alexbrown1995 Jul 24, 2023
6e789f0
minor changes..
alexbrown1995 Jul 24, 2023
e967b7a
Removing class ExplicitTimeDiscretisation and using ExplicitMultistage
alexbrown1995 Jul 25, 2023
cf67da5
All explicit (non-multistep) time discs are now multistage
alexbrown1995 Jul 25, 2023
a459916
Add back in ExplicitTimeDiscretisation....
alexbrown1995 Jul 25, 2023
95bbc2e
merge to main
alexbrown1995 Jul 25, 2023
5efcaf4
Merge branch 'main' into butcher_tableau
alexbrown1995 Jul 25, 2023
8566d10
small bug fixes
alexbrown1995 Jul 25, 2023
f316ef9
debugging....
alexbrown1995 Jul 26, 2023
7636609
tidied up timesteppers and added in workaround for convergence bug
alexbrown1995 Jul 26, 2023
74f1c91
Updated solver settings to fix convergence issues
alexbrown1995 Jul 27, 2023
89b1191
added header comments explaining the butcher matrix approach for expl…
ta440 Jul 27, 2023
6c5f493
added CoupledTransportEquation class that has multiple active tracers
ta440 Jul 28, 2023
1f72c1f
more changes
ta440 Jul 28, 2023
f750f57
more changes
ta440 Jul 31, 2023
3260258
more changes
ta440 Aug 1, 2023
0bc261f
tests made for CoupledTransportEquation()
ta440 Aug 2, 2023
6be702c
removed uneccessary interacting_species class
ta440 Aug 2, 2023
e99818b
added more comments to test_coupled_transport
ta440 Aug 2, 2023
783213f
revert accidental changes to time_discretisation file
ta440 Aug 2, 2023
90642ef
unreverted time_discretisation
ta440 Aug 2, 2023
c96900c
Merge branch 'main' of github.com:firedrakeproject/gusto into coupled…
ta440 Aug 2, 2023
1d53ca5
trailing whitespaces removed
ta440 Aug 2, 2023
ff3af57
made tracer velocity a throwaway variable
ta440 Aug 2, 2023
b151e54
lint changes in test_coupled_transport
ta440 Aug 2, 2023
dc70f40
more whitespaces exterminated
ta440 Aug 2, 2023
8b503e3
more whitespaces annihilated
ta440 Aug 2, 2023
451000d
made Tom changes
ta440 Aug 3, 2023
a3c5e7b
changes from main
ta440 Aug 8, 2023
f70f4d4
made precipation tests compatible with coupled transport
ta440 Aug 8, 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
2 changes: 0 additions & 2 deletions gusto/active_tracers.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,6 @@ def __init__(self, name, space, variable_type,
self.variable_type = variable_type
self.phase = phase
self.chemical = chemical
if self.variable_type != TracerVariableType.mixing_ratio:
raise NotImplementedError('Only mixing ratio tracers are currently implemented')

if (variable_type == TracerVariableType.density and transport_eqn == TransportEquationType.advective):
logger.warning('Active tracer initialised which describes a '
Expand Down
41 changes: 20 additions & 21 deletions gusto/equations.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ def __init__(self, domain, function_space, field_name, Vu=None):
Vu (:class:`FunctionSpace`, optional): the function space for the
velocity field. If this is not specified, uses the HDiv spaces
set up by the domain. Defaults to None.
**kwargs: any keyword arguments to be passed to the advection form.
"""
super().__init__(domain, function_space, field_name)

Expand Down Expand Up @@ -508,36 +507,38 @@ def get_active_tracer(self, field_name):
return active_tracer_to_return


class ForcedAdvectionEquation(PrognosticEquationSet):
class CoupledTransportEquation(PrognosticEquationSet):
u"""
Discretises the advection equation with a source/sink term, \n
Discretises the transport equation, \n
∂q/∂t + (u.∇)q = F,
which can also be augmented with active tracers.
with the application of active tracers.
As there are multiple tracers or species that are
interacting, q and F are vectors.
This equation can be enhanced through the addition of
sources or sinks (F) by applying it with physics schemes.
"""
def __init__(self, domain, function_space, field_name, Vu=None,
active_tracers=None, **kwargs):
def __init__(self, domain, active_tracers, Vu=None):
"""
Args:
domain (:class:`Domain`): the model's domain object, containing the
mesh and the compatible function spaces.
function_space (:class:`FunctionSpace`): the function space that the
equation's prognostic is defined on.
field_name (str): name of the prognostic field.
active_tracers (list): a list of `ActiveTracer` objects
that encode the metadata for any active tracers to be included
in the equations. This is required for using this class; if there
is only a field to be advected, use the AdvectionEquation
instead.
Vu (:class:`FunctionSpace`, optional): the function space for the
velocity field. If this is not specified, uses the HDiv spaces
set up by the domain. Defaults to None.
active_tracers (list, optional): a list of `ActiveTracer` objects
that encode the metadata for any active tracers to be included
in the equations. Defaults to None.
"""

self.field_names = [field_name]
self.space_names = {field_name: function_space.name}
self.active_tracers = active_tracers
self.terms_to_linearise = {}
self.field_names = []
self.space_names = {}

# Build finite element spaces
self.spaces = [domain.spaces("tracer", V=function_space)]
self.spaces = []

# Add active tracers to the list of prognostics
if active_tracers is None:
Expand All @@ -547,27 +548,25 @@ def __init__(self, domain, function_space, field_name, Vu=None,
# Make the full mixed function space
W = MixedFunctionSpace(self.spaces)

# Can now call the underlying PrognosticEquation
full_field_name = "_".join(self.field_names)
PrognosticEquation.__init__(self, domain, W, full_field_name)

if Vu is not None:
V = domain.spaces("HDiv", V=Vu, overwrite_space=True)
else:
V = domain.spaces("HDiv")
u = self.prescribed_fields("u", V)
_ = self.prescribed_fields("u", V)

self.tests = TestFunctions(W)
self.X = Function(W)

mass_form = self.generate_mass_terms()
transport_form = prognostic(advection_form(self.tests[0], split(self.X)[0], u), field_name)

self.residual = subject(mass_form + transport_form, self.X)
self.residual = subject(mass_form, self.X)

# Add transport of tracers
if len(active_tracers) > 0:
self.residual += self.generate_tracer_transport_terms(active_tracers)
self.residual += self.generate_tracer_transport_terms(active_tracers)


# ============================================================================ #
# Specified Equation Sets
Expand Down
65 changes: 65 additions & 0 deletions integration-tests/equations/test_coupled_transport.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""
Tests the CoupledTransportEquation class.
Two tracers are transported using combinations of advective and
conservative forms. The tracers are set to be a mixing ratio when
using the advective form and a density for the conservative form.

"""

from firedrake import norm
from gusto import *
import pytest


def run(timestepper, tmax, f_end):
timestepper.run(0, tmax)
norm1 = norm(timestepper.fields("f1") - f_end) / norm(f_end)
norm2 = norm(timestepper.fields("f2") - f_end) / norm(f_end)
return norm1, norm2


@pytest.mark.parametrize("geometry", ["slice", "sphere"])
@pytest.mark.parametrize("equation_form1", ["advective", "continuity"])
@pytest.mark.parametrize("equation_form2", ["advective", "continuity"])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, I think it's the right idea to test these combinations

def test_coupled_transport_scalar(tmpdir, geometry, equation_form1, equation_form2, tracer_setup):
setup = tracer_setup(tmpdir, geometry)
domain = setup.domain

if equation_form1 == "advective":
tracer1 = ActiveTracer(name='f1', space='DG',
variable_type=TracerVariableType.mixing_ratio,
transport_eqn=TransportEquationType.advective)
else:
tracer1 = ActiveTracer(name='f1', space='DG',
variable_type=TracerVariableType.density,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest that you keep mixing_ratio always with transport_eqn=TransportEquationType.advective and density with transport_eqn=TransportEquationType.conservative. Still test a mixture of advective and conservative variables, but since mixing ratios obey the advective form it would be better to keep that in this test

transport_eqn=TransportEquationType.conservative)

if equation_form2 == "advective":
tracer2 = ActiveTracer(name='f2', space='DG',
variable_type=TracerVariableType.mixing_ratio,
transport_eqn=TransportEquationType.advective)
else:
tracer2 = ActiveTracer(name='f2', space='DG',
variable_type=TracerVariableType.density,
transport_eqn=TransportEquationType.conservative)

tracers = [tracer1, tracer2]

V = domain.spaces("HDiv")
eqn = CoupledTransportEquation(domain, active_tracers=tracers, Vu=V)

transport_scheme = SSPRK3(domain)
transport_method = [DGUpwind(eqn, 'f1'), DGUpwind(eqn, 'f2')]

timestepper = PrescribedTransport(eqn, transport_scheme, setup.io, transport_method)

# Initial conditions
timestepper.fields("f1").interpolate(setup.f_init)
timestepper.fields("f2").interpolate(setup.f_init)
timestepper.fields("u").project(setup.uexpr)

error1, error2 = run(timestepper, setup.tmax, setup.f_end)
assert error1 < setup.tol, \
'The transport error for tracer 1 is greater than the permitted tolerance'
assert error2 < setup.tol, \
'The transport error for tracer 2 is greater than the permitted tolerance'
15 changes: 12 additions & 3 deletions integration-tests/equations/test_forced_advection.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,15 @@ def run_forced_advection(tmpdir):
msat = Function(VD)
msat.interpolate(msat_expr)

rain = Rain(space='tracer',
# Rain is a first tracer
rain = Rain(space='DG',
transport_eqn=TransportEquationType.no_transport)
meqn = ForcedAdvectionEquation(domain, VD, field_name="water_vapour", Vu=Vu,
active_tracers=[rain])

# Also, have water_vapour as a tracer:
water_vapour = WaterVapour(space='DG')

meqn = CoupledTransportEquation(domain, active_tracers=[rain, water_vapour], Vu=Vu)

transport_method = DGUpwind(meqn, "water_vapour")
physics_parametrisations = [InstantRain(meqn, msat, rain_name="rain",
parameters=None)]
Expand All @@ -72,6 +77,10 @@ def run_forced_advection(tmpdir):
stepper.fields("u").project(as_vector([u_max]))
stepper.fields("water_vapour").project(mexpr)

# Start with no rain:
no_rain = 0*x
stepper.fields("rain").interpolate(no_rain)

# exact rainfall profile (analytically)
r_exact = stepper.fields("r_exact", space=VD)
lim1 = Lx/(2*pi) * acos((C0 + K0 - Csat)/Ksat)
Expand Down
12 changes: 8 additions & 4 deletions integration-tests/physics/test_precipitation.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@ def setup_fallout(dirname):
domain = Domain(mesh, dt, "CG", 1)
x = SpatialCoordinate(mesh)

# Define the tracers
rho = ActiveTracer(name='rho', space='DG1_equispaced', variable_type=TracerVariableType.density)
rain = Rain(space='DG1_equispaced')

Vu = domain.spaces("HDiv")

# Equation
Vrho = domain.spaces("DG1_equispaced")
active_tracers = [Rain(space='DG1_equispaced')]
eqn = ForcedAdvectionEquation(domain, Vrho, "rho", active_tracers=active_tracers)
transport_method = DGUpwind(eqn, "rho")
eqn = CoupledTransportEquation(domain, active_tracers=[rho, rain], Vu=Vu)
transport_method = [DGUpwind(eqn, "rho"), DGUpwind(eqn, "rain")]

# I/O
output = OutputParameters(dirname=dirname+"/fallout", dumpfreq=10, dumplist=['rain'])
Expand Down
Loading