Skip to content
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
eb360ac
make dt and t Functions on the Real space
jshipton Sep 12, 2024
93ad1cf
sort out value of t in timestepper
jshipton Sep 12, 2024
b54f3bd
more Functions in R instead of Constants
jshipton Sep 12, 2024
998deb6
adjoint diffusion example
jshipton Sep 12, 2024
209d056
try adjoint with shallow water
jshipton Sep 12, 2024
1682f88
Merge branch 'main' of https://github.com/firedrakeproject/gusto into…
jshipton Sep 12, 2024
e49b006
use SIQN for adjoint shallow water
jshipton Sep 12, 2024
2b38eea
some small changes to enable moist thermal shallow water adjoint
jshipton Sep 12, 2024
b0055c8
Merge branch 'main' into adjoint
jshipton Dec 16, 2024
a7e6de1
Add adjoint tests
Ig-dolci Dec 16, 2024
3bc9a15
flake8
Ig-dolci Dec 16, 2024
4cfd4bb
wip
Ig-dolci Dec 16, 2024
7b9d24d
flake8
Ig-dolci Dec 16, 2024
31d5c59
Testing
Ig-dolci Dec 17, 2024
96becfe
Minor changer
Ig-dolci Dec 17, 2024
9f9b8eb
Test all controls
Ig-dolci Dec 17, 2024
61ca2dc
Check the blocks are empty
Ig-dolci Dec 17, 2024
6da7518
Add a notebook
Ig-dolci Dec 17, 2024
14371fd
Small changes
Ig-dolci Dec 17, 2024
9b8b0ed
dd
Ig-dolci Dec 18, 2024
b088504
Remove adjoint examples; enhance the notebook text; fix the tests
Ig-dolci Dec 18, 2024
7680ebc
flake8
Ig-dolci Dec 18, 2024
af8908b
Merge branch 'main' into adjoint
Ig-dolci Dec 18, 2024
c6eb3b2
Match with the main branch
Ig-dolci Dec 18, 2024
719a194
wip
Ig-dolci Dec 19, 2024
542d33e
Add convert_parameters_to_real_space function
Ig-dolci Dec 19, 2024
efc7e99
wip
Ig-dolci Dec 19, 2024
f89173a
flake8
Ig-dolci Dec 19, 2024
84814ce
replace deprecated decorator
Ig-dolci Dec 19, 2024
06de329
fix error
Ig-dolci Jan 6, 2025
4331a01
wip
Ig-dolci Jan 6, 2025
49b5e76
remove output files
Ig-dolci Jan 6, 2025
ac6cd36
more fixes
Ig-dolci Jan 6, 2025
d2fe122
Enhance docs
Ig-dolci Jan 6, 2025
998deb1
start to use real space functions instead of constants in configuration
jshipton Feb 11, 2025
ae31058
more work towards using functions on R instead of Constants
jshipton Feb 11, 2025
39829b3
Merge branch 'main' of https://github.com/firedrakeproject/gusto into…
jshipton Feb 11, 2025
ed8b8b8
more changes to use real functions instead of constants vis the confi…
jshipton Feb 12, 2025
4971e1b
change some constants in SIQN to be real functions for adjoint - not …
jshipton Feb 14, 2025
5b16837
more test fixes
jshipton Feb 17, 2025
6585dfd
I think this fixes all the easy issues
jshipton Feb 17, 2025
ea044e6
also catch class attributes with default values
jshipton Feb 18, 2025
1e32799
minus sign reinstated
jshipton Feb 19, 2025
779756c
fix issues with multiple domains - picking up from checkpoint require…
jshipton Feb 19, 2025
4cbdadb
lint
jshipton Feb 19, 2025
f973f84
fix vector diffusion
jshipton Feb 20, 2025
d9065e3
fix lint
jshipton Feb 20, 2025
74762da
Merge branch 'main' into new_adjoint
jshipton Feb 21, 2025
837ec30
this fixes the sw sensitivity test
jshipton Feb 26, 2025
65e274b
Merge branch 'new_adjoint' of https://github.com/firedrakeproject/gus…
jshipton Feb 26, 2025
f5ed0dc
remove notebook to put in separate PR
jshipton Feb 26, 2025
c5060de
remove adjoint comment
jshipton Feb 28, 2025
93e06d6
revert change to abstractmethod
jshipton Feb 28, 2025
9c5a13f
changes to docs and comment
jshipton Feb 28, 2025
d6c1287
split up configuration file
jshipton Feb 28, 2025
e906cd6
forgot to add this file
jshipton Mar 3, 2025
1ec4322
Merge branch 'main' into new_adjoint
jshipton Mar 5, 2025
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
768 changes: 768 additions & 0 deletions docs/notebook/shallow_water_adjoint.ipynb

Large diffs are not rendered by default.

22 changes: 18 additions & 4 deletions gusto/core/configuration.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Some simple tools for configuring the model."""
from abc import ABCMeta, abstractproperty
from abc import ABCMeta, abstractmethod
from enum import Enum
from firedrake import sqrt, Constant
from firedrake import sqrt, Constant, Function, FunctionSpace


__all__ = [
Expand All @@ -12,7 +12,7 @@
"EmbeddedDGOptions", "ConservativeEmbeddedDGOptions", "RecoveryOptions",
"ConservativeRecoveryOptions", "SUPGOptions", "MixedFSOptions",
"SpongeLayerParameters", "DiffusionParameters", "BoundaryLayerParameters",
"SubcyclingOptions"
"SubcyclingOptions", "convert_parameters_to_real_space"
]


Expand Down Expand Up @@ -167,7 +167,7 @@ class ShallowWaterParameters(Configuration):
class WrapperOptions(Configuration, metaclass=ABCMeta):
"""Base class for specifying options for a transport scheme."""

@abstractproperty
@abstractmethod

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should this actually just be @property?

def name(self):
pass

Expand Down Expand Up @@ -308,3 +308,17 @@ def check_options(self):
raise ValueError(
"Cannot provide both fixed_subcycles and subcycle_by_courant"
+ "parameters.")


def convert_parameters_to_real_space(parameters, mesh):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Naive question, but can we do this conversion from Constant to real function when instantiating the Configuration object? Or is there a reason we need to do it later on?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

i.e. could we do that conversion here:

object.__setattr__(self, name, Constant(value))
, where we already converted floats to Constants? Can we just do straight to real functions?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't the problem that we need the mesh? Maybe we just pass that to the Configuration class?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah yes, the problem is that we need the mesh. I think it probably would be better to pass mesh to the Configuration class then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Obviously that unfortunately would make this a bigger change as all examples would need updating!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes, I think that might be why we didn't want to do it... it does feel like a better solution though

"""Convert parameters to functions in real space.

Args:
parameters (:class:`Configuration`): the configuration object
containing the parameters to convert
mesh (:class:`firedrake.Mesh`): the mesh object to use for the real space.
"""
R = FunctionSpace(mesh, 'R', 0)
for name, value in vars(parameters).items():
if isinstance(value, (float, Constant)):
setattr(parameters, name, Function(R, val=float(value)))
7 changes: 4 additions & 3 deletions gusto/core/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,16 @@ def __init__(self, mesh, dt, family, degree=None,
# -------------------------------------------------------------------- #

# Store central dt for use in the rest of the model
R = FunctionSpace(mesh, "R", 0)
if type(dt) is Constant:
self.dt = dt
self.dt = Function(R, val=float(dt))
elif type(dt) in (float, int):
self.dt = Constant(dt)
self.dt = Function(R, val=dt)
else:
raise TypeError(f'dt must be a Constant, float or int, not {type(dt)}')

# Make a placeholder for the time
self.t = Constant(0.0)
self.t = Function(R, val=0.0)

# -------------------------------------------------------------------- #
# Build compatible function spaces
Expand Down
12 changes: 10 additions & 2 deletions gusto/equations/boussinesq_equations.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
time_derivative, transport, prognostic, linearisation,
pressure_gradient, coriolis, divergence, gravity, incompressible
)
from gusto.core.configuration import convert_parameters_to_real_space
from gusto.equations.common_forms import (
advection_form, vector_invariant_form,
kinetic_energy_form, advection_equation_circulation_form,
Expand Down Expand Up @@ -105,6 +106,11 @@ def __init__(self, domain, parameters,
active_tracers=active_tracers)

self.parameters = parameters
# Convert the attributes of type ``float`` or ``firedrake.Constant``
# in the parameters to a function in real space. This conversion is a
# preventive to avoid issues with adjoint computations, particularly
# when the parameters are used as controls in sensitivity analyses.
convert_parameters_to_real_space(parameters, domain.mesh)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So if we did the conversion to reals initially, then we wouldn't need to do this in any of the equations?

self.compressible = compressible

w, phi, gamma = self.tests[0:3]
Expand Down Expand Up @@ -168,10 +174,12 @@ def __init__(self, domain, parameters,
# -------------------------------------------------------------------- #
if compressible:
cs = parameters.cs
# On assuming ``cs`` as a constant, it is right keep it out of the
# integration.
linear_div_form = divergence(subject(
prognostic(cs**2 * phi * div(u_trial) * dx, 'p'), self.X))
prognostic(cs**2 * (phi * div(u_trial) * dx), 'p'), self.X))
divergence_form = divergence(linearisation(
subject(prognostic(cs**2 * phi * div(u) * dx, 'p'), self.X),
subject(prognostic(cs**2 * (phi * div(u) * dx), 'p'), self.X),
linear_div_form))
else:
# This enforces that div(u) = 0
Expand Down
10 changes: 8 additions & 2 deletions gusto/equations/compressible_euler_equations.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
)
from gusto.equations.active_tracers import Phases, TracerVariableType
from gusto.equations.prognostic_equations import PrognosticEquationSet

from gusto.core.configuration import convert_parameters_to_real_space
__all__ = ["CompressibleEulerEquations", "HydrostaticCompressibleEulerEquations"]


Expand Down Expand Up @@ -45,7 +45,7 @@ def __init__(self, domain, parameters, sponge_options=None,
Args:
domain (:class:`Domain`): the model's domain object, containing the
mesh and the compatible function spaces.
parameters (:class:`Configuration`, optional): an object containing
x (:class:`Configuration`, optional): an object containing
the model's physical parameters.
sponge_options (:class:`SpongeLayerParameters`, optional): any
parameters for applying a sponge layer to the upper boundary.
Expand Down Expand Up @@ -101,6 +101,11 @@ def __init__(self, domain, parameters, sponge_options=None,
active_tracers=active_tracers)

self.parameters = parameters
# Convert the attributes of type ``float`` or ``firedrake.Constant``
# in the parameters to a function in real space. This conversion is a
# preventive to avoid issues with adjoint computations, particularly
# when the parameters are used as controls in sensitivity analyses.
convert_parameters_to_real_space(parameters, domain.mesh)
g = parameters.g
cp = parameters.cp

Expand All @@ -109,6 +114,7 @@ def __init__(self, domain, parameters, sponge_options=None,
u_trial = split(self.trials)[0]
_, rho_bar, theta_bar = split(self.X_ref)[0:3]
zero_expr = Constant(0.0)*theta
# Check this for adjoints

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this is a leftover comment?

exner = exner_pressure(parameters, rho, theta)
n = FacetNormal(domain.mesh)

Expand Down
15 changes: 14 additions & 1 deletion gusto/equations/shallow_water_equations.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
linear_continuity_form, linear_advection_form
)
from gusto.equations.prognostic_equations import PrognosticEquationSet
from gusto.core.configuration import convert_parameters_to_real_space


__all__ = ["ShallowWaterEquations", "LinearShallowWaterEquations",
Expand Down Expand Up @@ -87,6 +88,11 @@ def __init__(self, domain, parameters, fexpr=None, topog_expr=None,

self.parameters = parameters
self.domain = domain
# Convert the attributes of type ``float`` or ``firedrake.Constant``
# in the parameters to a function in real space. This conversion is a
# preventive to avoid issues with adjoint computations, particularly
# when the parameters are used as controls in sensitivity analyses.
convert_parameters_to_real_space(parameters, self.domain.mesh)
self.active_tracers = active_tracers

self._setup_residual(fexpr, topog_expr, u_transport_option)
Expand Down Expand Up @@ -163,8 +169,9 @@ def _setup_residual(self, fexpr, topog_expr, u_transport_option):
# -------------------------------------------------------------------- #
# Pressure Gradient Term
# -------------------------------------------------------------------- #
# On assuming ``g``, it is right to keep it out of the integral.
pressure_gradient_form = pressure_gradient(
subject(prognostic(-g*div(w)*D*dx, 'u'), self.X))
subject(prognostic(-g*(div(w)*D*dx), 'u'), self.X))

residual = (mass_form + adv_form + pressure_gradient_form)

Expand Down Expand Up @@ -418,6 +425,7 @@ def _setup_residual(self, fexpr, topog_expr, u_transport_option):
# provide linearisation
if self.equivalent_buoyancy:
beta2 = self.parameters.beta2

qsat_expr = self.compute_saturation(self.X)
qv = conditional(qt < qsat_expr, qt, qsat_expr)
qvbar = conditional(qtbar < qsat_expr, qtbar, qsat_expr)
Expand Down Expand Up @@ -647,6 +655,11 @@ def __init__(self, domain, parameters,
active_tracers=active_tracers)

self.parameters = parameters
# Convert the attributes of type ``float`` or ``firedrake.Constant``
# in the parameters to a function in real space. This conversion is a
# preventive to avoid issues with adjoint computations, particularly
# when the parameters are used as controls in sensitivity analyses.
convert_parameters_to_real_space(parameters, domain.mesh)
g = parameters.g
H = parameters.H

Expand Down
12 changes: 7 additions & 5 deletions gusto/physics/shallow_water_microphysics.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""

from firedrake import (
conditional, Function, dx, min_value, max_value, Constant, assemble
conditional, Function, dx, min_value, max_value, FunctionSpace, assemble
)
from firedrake.__future__ import interpolate
from firedrake.fml import subject
Expand Down Expand Up @@ -94,13 +94,14 @@ def __init__(self, equation, saturation_curve,
self.water_v = Function(Vv)
self.source = Function(Vv)

R = FunctionSpace(equation.domain.mesh, "R", 0)
# tau is the timescale for conversion (may or may not be the timestep)
if tau is not None:
self.set_tau_to_dt = False
self.tau = tau
self.tau = Function(R).assign(tau)
else:
self.set_tau_to_dt = True
self.tau = Constant(0)
self.tau = Function(R)
logger.info("Timescale for rain conversion has been set to dt. If this is not the intention then provide a tau parameter as an argument to InstantRain.")

if self.time_varying_saturation:
Expand Down Expand Up @@ -270,12 +271,13 @@ def __init__(self, equation, saturation_curve,
V_idxs.append(self.Vb_idx)

# tau is the timescale for condensation/evaporation (may or may not be the timestep)
R = FunctionSpace(equation.domain.mesh, "R", 0)
if tau is not None:
self.set_tau_to_dt = False
self.tau = tau
self.tau = Function(R).assign(tau)
else:
self.set_tau_to_dt = True
self.tau = Constant(0)
self.tau = Function(R)
logger.info("Timescale for moisture conversion between vapour and cloud has been set to dt. If this is not the intention then provide a tau parameter as an argument to SWSaturationAdjustment.")

if self.time_varying_saturation:
Expand Down
3 changes: 2 additions & 1 deletion gusto/spatial_methods/diffusion_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,5 @@ def __init__(self, equation, variable, diffusion_parameters):
kappa = diffusion_parameters.kappa
self.form = diffusion(kappa * self.test.dx(0) * self.field.dx(0) * dx)
else:
raise NotImplementedError("CG diffusion only implemented in 1D")
kappa = diffusion_parameters.kappa
self.form = diffusion(kappa * inner(grad(self.test), grad(self.field)) * dx)
10 changes: 5 additions & 5 deletions gusto/time_discretisation/time_discretisation.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import math

from firedrake import (Function, TestFunction, TestFunctions, DirichletBC,
Constant, NonlinearVariationalProblem,
NonlinearVariationalSolver)
NonlinearVariationalProblem, NonlinearVariationalSolver,
FunctionSpace)
from firedrake.fml import (replace_subject, replace_test_function, Term,
all_terms, drop)
from firedrake.formmanipulation import split_form
Expand Down Expand Up @@ -88,10 +88,10 @@ def __init__(self, domain, field_name=None, subcycling_options=None,
self.domain = domain
self.field_name = field_name
self.equation = None

self.dt = Constant(0.0)
R = FunctionSpace(domain.mesh, "R", 0)
self.dt = Function(R, val=0.0)
self.dt.assign(domain.dt)
self.original_dt = Constant(0.0)
self.original_dt = Function(R, val=0.0)
self.original_dt.assign(self.dt)
self.options = options
self.limiter = limiter
Expand Down
2 changes: 1 addition & 1 deletion gusto/timestepping/timestepper.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ def run(self, t, tmax, pick_up=False):

self.timestep()

self.t.assign(self.t + self.dt)
self.t.assign(float(self.t) + float(self.dt))
self.step += 1

with timed_stage("Dump output"):
Expand Down
78 changes: 78 additions & 0 deletions integration-tests/adjoints/test_diffusion_sensitivity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import pytest
import numpy as np

from firedrake import *
from firedrake.adjoint import *
from pyadjoint import get_working_tape
from gusto import *


@pytest.fixture(autouse=True)
def handle_taping():
yield
tape = get_working_tape()
tape.clear_tape()


@pytest.fixture(autouse=True, scope="module")
def handle_annotation():
from firedrake.adjoint import annotate_tape, continue_annotation
if not annotate_tape():
continue_annotation()
yield
# Ensure annotation is paused when we finish.
annotate = annotate_tape()
if annotate:
pause_annotation()


@pytest.mark.parametrize("nu_is_control", [True, False])
def test_diffusion_sensitivity(nu_is_control, tmpdir):
assert get_working_tape()._blocks == []
n = 30
mesh = PeriodicUnitSquareMesh(n, n)
output = OutputParameters(dirname=str(tmpdir))
dt = 0.01
domain = Domain(mesh, 10*dt, family="BDM", degree=1)
io = IO(domain, output)

V = VectorFunctionSpace(mesh, "CG", 2)
domain.spaces.add_space("vecCG", V)

R = FunctionSpace(mesh, "R", 0)
# We need to define nu as a function in order to have a control variable.
nu = Function(R, val=0.0001)
diffusion_params = DiffusionParameters(kappa=nu)
eqn = DiffusionEquation(domain, V, "f", diffusion_parameters=diffusion_params)

diffusion_scheme = BackwardEuler(domain)
diffusion_methods = [CGDiffusion(eqn, "f", diffusion_params)]
timestepper = Timestepper(eqn, diffusion_scheme, io, spatial_methods=diffusion_methods)

x = SpatialCoordinate(mesh)
fexpr = as_vector((sin(2*pi*x[0]), cos(2*pi*x[1])))
timestepper.fields("f").interpolate(fexpr)

end = 0.1
timestepper.run(0., end)

u = timestepper.fields("f")
J = assemble(inner(u, u)*dx)

if nu_is_control:
control = Control(nu)
h = Function(R, val=0.0001) # the direction of the perturbation
else:
control = Control(u)
# the direction of the perturbation
h = Function(V).interpolate(fexpr * np.random.rand())

# the functional as a pure function of nu
Jhat = ReducedFunctional(J, control)

if nu_is_control:
assert np.allclose(J, Jhat(nu))
assert taylor_test(Jhat, nu, h) > 1.95
else:
assert np.allclose(J, Jhat(u))
assert taylor_test(Jhat, u, h) > 1.95
Loading