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

Terminator toy #457

Merged
merged 27 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e6dabe9
started TerminatorToy physics class
ta440 Aug 16, 2023
fb42334
added the evaluate property to the TerminatorToy scheme
ta440 Sep 1, 2023
b3fa487
Merge branch 'main' of github.com:firedrakeproject/gusto into termina…
ta440 Sep 1, 2023
6688c1e
Merge branch 'tracer_density' into terminator_toy
ta440 Sep 22, 2023
4157642
made changes to terminator coupled equations
ta440 Sep 22, 2023
e422188
cleaned up equation formulation for Terminator Toy
ta440 Sep 25, 2023
fc2aa7f
changes
ta440 Sep 25, 2023
8bf7d04
Merge branch 'main' into terminator_toy
ta440 Sep 25, 2023
315fffe
io logging of tracer amounts for debugging
ta440 Oct 23, 2023
55a8259
Merge remote-tracking branch 'origin/main' into terminator_toy
ta440 Oct 23, 2023
1f1aa3c
updated to work with gusto changes
ta440 Oct 24, 2023
36a3ffd
added the SplitPrescribedTransport timestepper
ta440 Oct 25, 2023
fbc3e0a
merged main into terminator
ta440 Oct 26, 2023
0d1e5be
beginning the analytical formulation
ta440 Oct 27, 2023
fa6cc8f
add implicit_formulation of Terminator Toy
ta440 Oct 27, 2023
532c617
more implicit working
ta440 Oct 27, 2023
c73f6b5
making the physics scheme compatible with backwards euler
ta440 Oct 28, 2023
1322d1d
more changes
ta440 Nov 2, 2023
33e70ff
tidy up terminator toy
ta440 Nov 2, 2023
3608f70
linting
ta440 Nov 2, 2023
c4345b0
more linting
ta440 Nov 2, 2023
b931032
Merge branch 'main' into terminator_toy
ta440 Nov 2, 2023
1c89b49
more linting
ta440 Nov 2, 2023
c01aa56
compatible with fml purge
ta440 Nov 2, 2023
4005ea4
make the code more efficient
ta440 Nov 3, 2023
03816ec
better docstring and use ValueError
ta440 Nov 8, 2023
0cac4dc
Merge branch 'main' into terminator_toy
tommbendall Nov 9, 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
75 changes: 74 additions & 1 deletion gusto/physics.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
__all__ = ["SaturationAdjustment", "Fallout", "Coalescence", "EvaporationOfRain",
"AdvectedMoments", "InstantRain", "SWSaturationAdjustment",
"SourceSink", "SurfaceFluxes", "WindDrag", "StaticAdjustment",
"SuppressVerticalWind", "BoundaryLayerMixing"]
"SuppressVerticalWind", "BoundaryLayerMixing", "TerminatorToy"]


class PhysicsParametrisation(object, metaclass=ABCMeta):
Expand Down Expand Up @@ -1751,3 +1751,76 @@ def evaluate(self, x_in, dt):

self.X.assign(x_in)
self.rho_recoverer.project()


class TerminatorToy(PhysicsParametrisation):
"""
Setup the Terminator Toy chemistry interaction
as specified in 'The terminator toy chemistry test ...'
Lauritzen et. al. (2014).

The coupled equations for the two species are given by:

D/Dt (X) = 2Kx
D/Dt (X2) = -Kx

where Kx = k1*X2 - k2*(X**2)
"""

def __init__(self, equation, k1=1, k2=1,
species1_name='X', species2_name='X2'):
"""
Args:
equation (:class: 'PrognosticEquationSet'): the model's equation
k1(float, optional): Reaction rate for species 1 (X). Defaults to a constant
over the domain.
k2(float, optional): Reaction rate for species 2 (X2). Defaults to a constant
over the domain.
species1_name(str, optional): Name of the first interacting species. Defaults
to 'X'.
species2_name(str, optional): Name of the second interacting species. Defaults
to 'X2'.
"""

label_name = 'terminator_toy'
super().__init__(equation, label_name, parameters=None)

if species1_name not in equation.field_names:
raise ValueError(f"Field {species1_name} does not exist in the equation set")
if species2_name not in equation.field_names:
raise ValueError(f"Field {species2_name} does not exist in the equation set")

self.species1_idx = equation.field_names.index(species1_name)
self.species2_idx = equation.field_names.index(species2_name)

assert equation.function_space.sub(self.species1_idx) == equation.function_space.sub(self.species2_idx), "The function spaces for the two species need to be the same"

self.Xq = Function(equation.X.function_space())
Xq = self.Xq

species1 = split(Xq)[self.species1_idx]
species2 = split(Xq)[self.species2_idx]

test_1 = equation.tests[self.species1_idx]
test_2 = equation.tests[self.species2_idx]

Kx = k1*species2 - k2*(species1**2)

source1_expr = test_1 * 2*Kx * dx
source2_expr = test_2 * -Kx * dx

equation.residual -= self.label(subject(prognostic(source1_expr, 'X'), Xq), self.evaluate)
equation.residual -= self.label(subject(prognostic(source2_expr, 'X2'), Xq), self.evaluate)

def evaluate(self, x_in, dt):
"""
Evaluates the source/sink for the coalescence process.

Args:
x_in (:class:`Function`): the (mixed) field to be evolved.
dt (:class:`Constant`): the time interval for the scheme.
"""

logger.info(f'Evaluating physics parametrisation {self.label.label}')
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe add a pass underneath this to make it obvious that this evaluate method does nothing


pass
90 changes: 87 additions & 3 deletions gusto/timeloop.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
from gusto.transport_methods import TransportMethod
import ufl

__all__ = ["Timestepper", "SplitPhysicsTimestepper", "SemiImplicitQuasiNewton",
"PrescribedTransport"]
__all__ = ["Timestepper", "SplitPhysicsTimestepper", "SplitPrescribedTransport",
"SemiImplicitQuasiNewton", "PrescribedTransport"]


class BaseTimestepper(object, metaclass=ABCMeta):
Expand Down Expand Up @@ -124,7 +124,6 @@ def setup_transporting_velocity(self, scheme):
transport term should be replaced with the transport term of
this discretisation.
"""

if self.transporting_velocity == "prognostic" and "u" in self.fields._field_names:
# Use the prognostic wind variable as the transporting velocity
u_idx = self.equation.field_names.index('u')
Expand Down Expand Up @@ -378,6 +377,91 @@ def timestep(self):
scheme.apply(self.x.np1(scheme.field_name), self.x.np1(scheme.field_name))


class SplitPrescribedTransport(Timestepper):
"""
Implements a timeloop where the physics terms are solved separately from
the dynamics, like with SplitPhysicsTimestepper, but here we define
a prescribed transporting velocity, as opposed to having the
velocity as a prognostic variable.
"""

def __init__(self, equation, scheme, io, spatial_methods=None,
physics_schemes=None,
prescribed_transporting_velocity=None):
"""
Args:
equation (:class:`PrognosticEquation`): the prognostic equation
scheme (:class:`TimeDiscretisation`): the scheme to use to timestep
the prognostic equation
io (:class:`IO`): the model's object for controlling input/output.
spatial_methods (iter,optional): a list of objects describing the
methods to use for discretising transport or diffusion terms
for each transported/diffused variable. Defaults to None,
in which case the terms follow the original discretisation in
the equation.
physics_schemes: (list, optional): a list of tuples of the form
(:class:`PhysicsParametrisation`, :class:`TimeDiscretisation`),
pairing physics parametrisations and timestepping schemes to use
for each. Timestepping schemes for physics can be explicit
or implicit. Defaults to None.
prescribed_transporting_velocity: (field, optional): A known
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add a bit more description to this to explain that it can be a python function, taking time as an argument?

I see that the docstrings are wrong for the PrescribedTransport stepper, sorry about that!

velocity field that is used for the transport of tracers.
This can be made time-varying by defining a python function
that uses time as an argument.
Defaults to None.
"""

# As we handle physics differently to the Timestepper, these are not
# passed to the super __init__
super().__init__(equation, scheme, io, spatial_methods=spatial_methods)

if physics_schemes is not None:
self.physics_schemes = physics_schemes
else:
self.physics_schemes = []

for parametrisation, phys_scheme in self.physics_schemes:
# check that the supplied schemes for physics are valid
if hasattr(parametrisation, "explicit_only") and parametrisation.explicit_only:
assert isinstance(phys_scheme, ExplicitTimeDiscretisation), \
("Only explicit time discretisations can be used with "
+ f"physics scheme {parametrisation.label.label}")
apply_bcs = False
phys_scheme.setup(equation, apply_bcs, parametrisation.label)

if prescribed_transporting_velocity is not None:
self.velocity_projection = Projector(
prescribed_transporting_velocity(self.t),
self.fields('u'))
else:
self.velocity_projection = None

@property
def transporting_velocity(self):
return self.fields('u')

def setup_scheme(self):
self.setup_equation(self.equation)
# Go through and label all non-physics terms with a "dynamics" label
dynamics = Label('dynamics')
self.equation.label_terms(lambda t: not any(t.has_label(time_derivative, physics_label)), dynamics)
apply_bcs = True
self.scheme.setup(self.equation, apply_bcs, dynamics)
self.setup_transporting_velocity(self.scheme)
self.scheme.courant_max = self.io.courant_max

def timestep(self):

if self.velocity_projection is not None:
self.velocity_projection.project()

super().timestep()

with timed_stage("Physics"):
for _, scheme in self.physics_schemes:
scheme.apply(self.x.np1(scheme.field_name), self.x.np1(scheme.field_name))


class SemiImplicitQuasiNewton(BaseTimestepper):
"""
Implements a semi-implicit quasi-Newton discretisation,
Expand Down
Loading