Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
54548ab
Add SpeedyWeather extension
glwagner Apr 9, 2025
36a6bdf
add a few things
glwagner Apr 10, 2025
6b19679
start populating the model
simone-silvestri Apr 10, 2025
e3fc3e8
change project
simone-silvestri Apr 10, 2025
8fc92aa
add speedy
glwagner Apr 10, 2025
22cfaba
Merge branch 'speedy-ext' of https://github.com/CliMA/ClimaOcean.jl i…
glwagner Apr 10, 2025
b702959
building
glwagner Apr 10, 2025
007aed0
grid test
glwagner Apr 10, 2025
a3aa03f
better comment
simone-silvestri Apr 14, 2025
9e5e4af
this should start working?
simone-silvestri Apr 14, 2025
93d054d
comments
simone-silvestri Apr 14, 2025
60c6476
import stuff
simone-silvestri Apr 14, 2025
3d313d7
some improvements
simone-silvestri Apr 14, 2025
c663312
add the regridding
simone-silvestri Apr 15, 2025
e33b6af
this should wortk
simone-silvestri Apr 15, 2025
c83fa03
do not use regrid!
simone-silvestri Apr 15, 2025
292c3c8
remove the regrid!
simone-silvestri Apr 15, 2025
a81ab5f
this works?
simone-silvestri Apr 15, 2025
24c88fa
going
simone-silvestri Apr 15, 2025
2152712
this seems to work
simone-silvestri Apr 15, 2025
269ca76
still some NaNs to fix
simone-silvestri Apr 15, 2025
876b3a7
suggested changes
simone-silvestri Apr 15, 2025
8526e35
Merge branch 'main' into speedy-ext
simone-silvestri Apr 15, 2025
148d9c3
Merge branch 'main' into speedy-ext
simone-silvestri Aug 21, 2025
2dc2b86
starting stuff
simone-silvestri Aug 21, 2025
4148fd7
simulation is running!
simone-silvestri Aug 21, 2025
122e27b
now everything works
simone-silvestri Aug 22, 2025
c7b2949
use functions
simone-silvestri Aug 22, 2025
8c23280
speed up stuff
simone-silvestri Aug 22, 2025
90ea8bc
coupled simulation that runs
simone-silvestri Aug 22, 2025
5872737
using trunc=63
simone-silvestri Aug 22, 2025
e433d9e
small fix
simone-silvestri Aug 25, 2025
3ad33d7
add the sst
simone-silvestri Aug 25, 2025
0fb3f26
no arch
simone-silvestri Aug 25, 2025
5832320
more changes
simone-silvestri Aug 25, 2025
34d54f3
make sure it is kelvin
simone-silvestri Aug 25, 2025
5e05425
coupled
simone-silvestri Aug 26, 2025
acb4be4
Merge branch 'main' into speedy-ext
simone-silvestri Aug 27, 2025
7524c1d
add this to tests
simone-silvestri Aug 27, 2025
f1ebfcc
Merge branch 'speedy-ext' of github.com:CliMA/ClimaOcean.jl into spee…
simone-silvestri Aug 27, 2025
3b1a977
test this out
simone-silvestri Aug 27, 2025
e68d16b
add this
simone-silvestri Aug 27, 2025
99cefaa
try high res
simone-silvestri Sep 1, 2025
dacc8fc
high res
simone-silvestri Sep 1, 2025
4281c44
add more packages
simone-silvestri Sep 1, 2025
2c38620
thos now should work
simone-silvestri Sep 1, 2025
f83c78c
Merge branch 'speedy-ext' of github.com:CliMA/ClimaOcean.jl into spee…
simone-silvestri Sep 1, 2025
0eaac46
we do not need PyCall
simone-silvestri Sep 1, 2025
e819c1b
make it work with PythonCall
simone-silvestri Sep 1, 2025
c04115f
probably better like this?
simone-silvestri Sep 1, 2025
dd41c84
this should work now
simone-silvestri Sep 1, 2025
c52000d
molmass ratio
simone-silvestri Sep 1, 2025
bbc7bcb
add
simone-silvestri Sep 3, 2025
0db655f
add a coupled simulation
simone-silvestri Sep 3, 2025
6391632
go ahead
simone-silvestri Sep 3, 2025
aebdd57
add this
simone-silvestri Sep 4, 2025
fd652e1
Merge branch 'main' into speedy-ext
simone-silvestri Sep 5, 2025
fb09b37
add it as an example
simone-silvestri Sep 8, 2025
7876e35
Merge branch 'speedy-ext' of github.com:CliMA/ClimaOcean.jl into spee…
simone-silvestri Sep 8, 2025
a469d8c
Merge branch 'main' into speedy-ext
simone-silvestri Sep 8, 2025
5452faf
bugfix
simone-silvestri Sep 8, 2025
7223d4d
Merge branch 'speedy-ext' of github.com:CliMA/ClimaOcean.jl into spee…
simone-silvestri Sep 8, 2025
a153dd9
Update examples/coupled_simulation.jl
simone-silvestri Sep 9, 2025
3ba6b53
Update examples/coupled_simulation.jl
simone-silvestri Sep 9, 2025
ba68985
Update docs/make.jl
simone-silvestri Sep 9, 2025
255f44c
corrections
simone-silvestri Sep 9, 2025
6aef9ba
new versions
simone-silvestri Sep 29, 2025
7f17322
Merge branch 'speedy-ext' of github.com:CliMA/ClimaOcean.jl into spee…
simone-silvestri Sep 29, 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
9 changes: 8 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
CFTime = "179af706-886a-5703-950a-314cd64e0468"
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
ClimaSeaIce = "6ba0ff68-24e6-4315-936c-2e99227c95a4"
ConservativeRegridding = "8e50ac2c-eb48-49bc-a402-07c87b949343"
CubicSplines = "9c784101-8907-5a6d-9be6-98f00873c89b"
DataDeps = "124859b0-ceae-595e-8997-d05f6a7a8dfe"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
GeometryOps = "3251bfac-6a57-4b6d-aa61-ac1fef2975ab"
GeometryOpsCore = "05efe853-fabf-41c8-927e-7063c8b9f013"
ImageMorphology = "787d08f9-d448-5407-9aad-5290dd7ab264"
JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c"
Expand All @@ -24,6 +28,7 @@ PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
Scratch = "6c6a2e73-6563-6170-7368-637461726353"
SeawaterPolynomials = "d496a93d-167e-4197-9f49-d3af4ff8fe40"
SpeedyWeather = "9e226e20-d153-4fed-8a5b-493def4f21a9"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
SurfaceFluxes = "49b00bb7-8bd4-4f2b-b78c-51cd0450215f"
Expand All @@ -34,6 +39,7 @@ Reactant = "3c362404-f566-11ee-1572-e11a4b42c853"

[extensions]
ClimaOceanReactantExt = "Reactant"
ClimaOceanSpeedyWeatherExt = "SpeedyWeather"

[compat]
Adapt = "4"
Expand All @@ -54,6 +60,7 @@ PrecompileTools = "1"
Reactant = "0.2.45"
Scratch = "1"
SeawaterPolynomials = "0.3.5"
SpeedyWeather = "0.14.0"
StaticArrays = "1"
Statistics = "1.9"
SurfaceFluxes = "0.11, 0.12"
Expand All @@ -67,4 +74,4 @@ MPIPreferences = "3da0fdf6-3ccc-4f1b-acd9-58baa6c99267"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Coverage", "Test", "MPIPreferences", "CUDA_Runtime_jll", "Reactant"]
test = ["Coverage", "Test", "MPIPreferences", "CUDA_Runtime_jll"]
25 changes: 25 additions & 0 deletions ext/ClimaOceanSpeedyWeatherExt/ClimaOceanSpeedyWeatherExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module ClimaOceanSpeedyWeatherExt

using OffsetArrays
using KernelAbstractions
using Statistics

import SpeedyWeather
import ClimaOcean
import Oceananigans

function clenshaw_latitude_longitude_grid(arch, spectral_grid)
grid = LatitudeLongitudeGrid(arch;
size = (360, 179, 1),
latitude = (-89.5, 89.5),
longitude = (0, 360),
z = (0, 1))
return grid
end

include("conservative_regridding.jl")
include("speedy_atmosphere_simulations.jl")
include("speedy_weather_exchanger.jl")
# include("speedy_weather_fluxes.jl")

end # module ClimaOceanSpeedyWeatherExt
81 changes: 81 additions & 0 deletions ext/ClimaOceanSpeedyWeatherExt/conservative_regridding.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using ConservativeRegridding

using Oceananigans
using Oceananigans.Utils
using Oceananigans.Grids: λnode, φnode, on_architecture, AbstractGrid
using GeometryOps: Point2

using KernelAbstractions: @kernel, @index

using SpeedyWeather.RingGrids

"""
get_faces(grid)

Returns a list representing all horizontal grid cells in a curvilinear `grid`.
The output is an Array of 5 * M `Point2` elements where `M = Nx * Ny`. Each column lists the vertices
associated with a horizontal cell in clockwise order starting from the southwest (bottom left) corner.
"""
function get_faces(grid::AbstractGrid)
Nx, Ny, _ = size(grid)
FT = eltype(grid)

cpu_grid = on_architecture(Oceananigans.CPU(), grid)

sw = fill(Point2{FT}(0, 0), 1, Nx*Ny)
nw = fill(Point2{FT}(0, 0), 1, Nx*Ny)
ne = fill(Point2{FT}(0, 0), 1, Nx*Ny)
se = fill(Point2{FT}(0, 0), 1, Nx*Ny)

launch!(Oceananigans.CPU(), cpu_grid, :xy, _get_vertices!, sw, nw, ne, se, grid)

vertices = vcat(sw, nw, ne, se, sw)

return vertices
end

# Move to SpeedyWeather?
function get_faces(grid::SpeedyWeather.SpectralGrid)

Grid = grid.Grid
nlat_half = grid.nlat_half
npoints = RingGrids.get_npoints2D(Grid, nlat_half)

# vertex east, south, west, north (i.e. clockwise for every grid point)
E, S, W, N = RingGrids.get_vertices(Grid, nlat_half)

# allocate faces as Point2{Float64} so that no data copy has to be made in Makie
faces = Matrix{NTuple{2, Float64}}(undef, 5, npoints)

@inbounds for ij in 1:npoints
faces[1, ij] = (E[1, ij], E[2, ij]) # clockwise
faces[2, ij] = (S[1, ij], S[2, ij])
faces[3, ij] = (W[1, ij], W[2, ij])
faces[4, ij] = (N[1, ij], N[2, ij])
faces[5, ij] = (E[1, ij], E[2, ij]) # back to east to close the polygon
end

return faces
end

@kernel function _get_vertices!(sw, nw, ne, se, grid)
i, j = @index(Global, NTuple)

FT = eltype(grid)
Nx = size(grid, 1)
λ⁻⁻ = λnode(i, j, 1, grid, Face(), Face(), nothing)
λ⁺⁻ = λnode(i, j+1, 1, grid, Face(), Face(), nothing)
λ⁻⁺ = λnode(i+1, j, 1, grid, Face(), Face(), nothing)
λ⁺⁺ = λnode(i+1, j+1, 1, grid, Face(), Face(), nothing)

φ⁻⁻ = φnode(i, j, 1, grid, Face(), Face(), nothing)
φ⁺⁻ = φnode(i, j+1, 1, grid, Face(), Face(), nothing)
φ⁻⁺ = φnode(i+1, j, 1, grid, Face(), Face(), nothing)
φ⁺⁺ = φnode(i+1, j+1, 1, grid, Face(), Face(), nothing)

sw[i+(j-1)*Nx] = Point2{FT}(λ⁻⁻, φ⁻⁻)
nw[i+(j-1)*Nx] = Point2{FT}(λ⁻⁺, φ⁻⁺)
ne[i+(j-1)*Nx] = Point2{FT}(λ⁺⁺, φ⁺⁺)
se[i+(j-1)*Nx] = Point2{FT}(λ⁺⁻, φ⁺⁻)
end

52 changes: 52 additions & 0 deletions ext/ClimaOceanSpeedyWeatherExt/speedy_atmosphere_simulations.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Make sure the atmospheric parameters from SpeedyWeather can be used in the compute fluxes function
import ClimaOcean.OceanSeaIceModels.PrescribedAtmospheres:
thermodynamics_parameters,
boundary_layer_height,
surface_layer_height

import ClimaOcean: atmosphere_simulation

const SpeedySimulation = SpeedyWeather.Simulation
const SpeedyCoupledModel = ClimaOcean.OceanSeaIceModel{<:Any, <:SpeedySimulation}
Base.summary(::SpeedySimulation) = "SpeedyWeather.Simulation"

# This can be left blank:
Oceananigans.Models.update_model_field_time_series!(::SpeedySimulation, time) = nothing

# Take one time-step
Oceananigans.TimeSteppers.time_step!(atmos::SpeedySimulation, Δt) = SpeedyWeather.timestep!(atmos)

function atmosphere_simulation(grid::SpeedyWeather.SpectralGrid)
# orography = zeros(grid.Grid, grid.nlat_half))

surface_heat_flux = SpeedyWeather.PrescribedOceanHeatFlux()
surface_evaporation = SpeedyWeather.PrescribedOceanEvaporation()
atmos_model = SpeedyWeather.PrimitiveWetModel(grid;
# orography,
surface_heat_flux,
surface_evaporation)

atmos_sim = SpeedyWeather.initialize!(atmos_model)
return atmos_sim
end

# The height of near-surface variables used in the turbulent flux solver
function surface_layer_height(s::SpeedySimulation)
T = s.model.atmosphere.temp_ref
g = s.model.planet.gravity
Φ = s.model.geopotential.Δp_geopot_full
return Φ[end] * T / g
end

# This is a parameter that is used in the computation of the fluxes,
# It probably should not be here but in the similarity theory type.
boundary_layer_height(atmos::SpeedySimulation) = 600

# Base.eltype(::EarthAtmosphere{FT}) where FT = FT

# This is a _hack_!! The parameters should be consistent with what is specified in SpeedyWeather
thermodynamics_parameters(atmos::SpeedyWeather.Simulation) =
ClimaOcean.OceanSeaIceModels.PrescribedAtmosphereThermodynamicsParameters(Float32)



124 changes: 124 additions & 0 deletions ext/ClimaOceanSpeedyWeatherExt/speedy_weather_exchanger.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using ConservativeRegridding
using GeometryOps: CutAtAntimeridianAndPoles, ClosedRing
using GeoInterface: Polygon, LinearRing
using Oceananigans
using Oceananigans.Grids: architecture

import ClimaOcean.OceanSeaIceModels:
compute_net_atmosphere_fluxes!

import ClimaOcean.OceanSeaIceModels.InterfaceComputations:
atmosphere_exchanger,
initialize!,
StateExchanger,
interpolate_atmosphere_state!

const OCRExt = Base.get_extension(Oceananigans, :OceananigansConservativeRegriddingExt)
const SWGExt = Base.get_extension(SpeedyWeather, :SpeedyWeatherGeoMakieExt)

get_cell_matrix(grid::SpeedyWeather.SpectralGrid) = SWGExt.get_faces(grid; add_nans=false)
get_cell_matrix(grid::Oceananigans.AbstractGrid) = OCRExt.compute_cell_matrix(grid, Center(), Center(), nothing)

# For the moment the workflow is:
# 1. Perform the regridding on the CPU
# 2. Eventually copy the regridded fields to the GPU
# If this work we can
# 1. Copy speedyweather gridarrays to the GPU
# 2. Perform the regridding on the GPU
function atmosphere_exchanger(atmosphere::SpeedySimulation, exchange_grid, exchange_atmosphere_state)

# Figure this out:
spectral_grid = atmosphere.model.spectral_grid
atmosphere_cell_matrix = get_cell_matrix(spectral_grid)
exchange_cell_matrix = get_cell_matrix(exchange_grid)
arch = architecture(exchange_grid)

if arch isa Oceananigans.CPU # In case of a CPU grid, we reuse the already allocated fields
cpu_surface_state = (
u = vec(interior(exchange_atmosphere_state.u)),
v = vec(interior(exchange_atmosphere_state.v)),
T = vec(interior(exchange_atmosphere_state.T)),
q = vec(interior(exchange_atmosphere_state.q)),
p = vec(interior(exchange_atmosphere_state.p)),
Qs = vec(interior(exchange_atmosphere_state.Qs)),
Qℓ = vec(interior(exchange_atmosphere_state.Qℓ))
)
else # Otherwise we allocate new CPU fields
cpu_surface_state = (
u = zeros(Float32, length(exchange_cell_matrix)),
v = zeros(Float32, length(exchange_cell_matrix)),
T = zeros(Float32, length(exchange_cell_matrix)),
q = zeros(Float32, length(exchange_cell_matrix)),
p = zeros(Float32, length(exchange_cell_matrix)),
Qs = zeros(Float32, length(exchange_cell_matrix)),
Qℓ = zeros(Float32, length(exchange_cell_matrix)),
)
end

regridder = ConservativeRegridding.Regridder(exchange_cell_matrix, atmosphere_cell_matrix)

exchanger = (; cpu_surface_state, regridder)

return exchanger
end

fill_exchange_fields!(::Oceananigans.CPU, args...) = nothing

# TODO: add GPU support
function fill_exchange_fields!(::Oceananigans.GPU, exchange_state, cpu_surface_state)
# Can I just copyto! here?
end

# Regrid the atmospheric state on the exchange grid
function interpolate_atmosphere_state!(interfaces, atmos::SpeedySimulation, coupled_model)
exchanger = interfaces.exchanger
regridder = exchanger.regridder
exchange_grid = interfaces.exchanger.exchange_grid
exchange_state = exchanger.exchange_atmosphere_state

ua = atmos.diagnostic_variables.grid.u_grid[:, end]
va = atmos.diagnostic_variables.grid.v_grid[:, end]
Ta = atmos.diagnostic_variables.grid.temp_grid[:, end]
qa = atmos.diagnostic_variables.grid.humid_grid[:, end]
pa = exp.(atmos.diagnostic_variables.grid.pres_grid[:, end])
Qsa = atmos.diagnostic_variables.physics.surface_shortwave_down
Qla = atmos.diagnostic_variables.physics.surface_longwave_down

ue = exchanger.cpu_surface_state.u
ve = exchanger.cpu_surface_state.v
Te = exchanger.cpu_surface_state.T
qe = exchanger.cpu_surface_state.q
pe = exchanger.cpu_surface_state.p
Qse = exchanger.cpu_surface_state.Qs
Qle = exchanger.cpu_surface_state.Qℓ

ConservativeRegridding.regrid!(ue, regridder, ua)
ConservativeRegridding.regrid!(ve, regridder, va)
ConservativeRegridding.regrid!(Te, regridder, Ta)
ConservativeRegridding.regrid!(qe, regridder, qa)
ConservativeRegridding.regrid!(pe, regridder, pa)
ConservativeRegridding.regrid!(Qse, regridder, Qsa)
ConservativeRegridding.regrid!(Qle, regridder, Qla)

arch = architecture(exchange_grid)
fill_exchange_fields!(arch, exchange_state, exchanger.cpu_surface_state)

return nothing
end

function compute_net_atmosphere_fluxes!(coupled_model::SpeedyCoupledModel)
atmos = coupled_model.atmosphere

# All the location of these fluxes will change
Qs = similarity_theory_fields.sensible_heat
Mv = similarity_theory_fields.water_vapor
Qu = total_fluxes.ocean.heat.upwelling_radiation

regridder = coupled_model.interfaces.exchanger.regridder

ConservativeRegridding.regrid!(atmos.diagnostic_variables.physics.sensible_heat_flux, transpose(regridder), vec(interior(Qs)))
ConservativeRegridding.regrid!(atmos.diagnostic_variables.physics.evaporative_flux, transpose(regridder), vec(interior(Mv)))
ConservativeRegridding.regrid!(atmos.diagnostic_variables.physics.surface_longwave_up, transpose(regridder), vec(interior(Qu)))
Copy link
Member Author

Choose a reason for hiding this comment

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

Should this really be regridded?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I am not still not sure what we want to do with the upwelling longwave. We can either compute it in ClimaOcean or pass the temperature to the atmosphere which computes its upwelling radiation.
If we separate radiation it might be easier, but for the moment we can compute it in ClimaOcean and pass it to the atmosphere

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess with radiation computed in EarthSystemModel, we will indeed have to regrid the radiative fluxes back to the atmosphere?

Copy link
Collaborator

Choose a reason for hiding this comment

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

yeah, let's start with regridding also the longwave up which might be propedeutic for a radiation computed in the EarthSystemModel


return nothing
end
Loading
Loading