-
Notifications
You must be signed in to change notification settings - Fork 13
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
Terminator toy #457
Changes from 25 commits
e6dabe9
fb42334
b3fa487
6688c1e
4157642
e422188
fc2aa7f
8bf7d04
315fffe
55a8259
1f1aa3c
36a3ffd
fbc3e0a
0d1e5be
fa6cc8f
532c617
c73f6b5
1322d1d
33e70ff
3608f70
c4345b0
b931032
1c89b49
c01aa56
4005ea4
03816ec
0cac4dc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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): | ||
|
@@ -1751,3 +1751,72 @@ 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) | ||
|
||
assert species1_name in equation.field_names, f"Field {species1_name} does not exist in the equation set" | ||
assert species2_name in equation.field_names, 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}') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add a |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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): | ||
|
@@ -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') | ||
|
@@ -378,6 +377,89 @@ 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
velocity field that is used for the transport of tracers. | ||
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, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very picky point here. It might be slightly better if we change this error (and the one below) to something like:
as this is the normal type of python error when the wrong argument is given to a routine
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, I've done that for the two name checks. Is the check for the function spaces ok as it is, as this is not an argument error, but a compatibility error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the check is OK as is, as you say it is not about having an incorrect argument but that the model set up isn't correct