Skip to content
Open
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
806be1e
Added a run_coupled! function to implement checkpointers in coupled s…
taimoorsohail Mar 11, 2025
46b7bb2
Added a run_coupled! function to implement checkpointers in coupled s…
taimoorsohail Mar 11, 2025
75b8182
Simplified the near_global_ocean example to see why the checkpointing…
taimoorsohail Mar 12, 2025
55106ba
Merge remote-tracking branch 'origin/main' into ts/checkpointer-for-c…
taimoorsohail Mar 12, 2025
61f91f5
merge main;
taimoorsohail Mar 13, 2025
fb5d35d
Testing the set! function
navidcy Mar 13, 2025
e39c87a
merge main
navidcy Mar 13, 2025
d15439f
managed to pick up!
navidcy Mar 13, 2025
a0dca49
Changed checkointer_mwe.jl
taimoorsohail Mar 13, 2025
8ff3748
Cleaning up checkpointer_mwe.jl file
taimoorsohail Mar 13, 2025
3fdf785
extends methods to work with OSIM and OSIMSIM
navidcy Mar 13, 2025
2328bb5
mwe
navidcy Mar 13, 2025
9ab0df1
simplify
navidcy Mar 13, 2025
5bd34f8
tidying up
navidcy Mar 13, 2025
9e2af01
bit cleaner
navidcy Mar 13, 2025
4f5ff2e
set!(sim::OSIMSIM{PrescribedAtmosphere})
navidcy Mar 13, 2025
ba2521a
cleaner mwe
navidcy Mar 13, 2025
b75d6e0
Changed the function to set!
taimoorsohail Mar 13, 2025
f1cae4f
Merge NCC changes
taimoorsohail Mar 13, 2025
dfb57fa
Merge NCC changes
taimoorsohail Mar 14, 2025
76dd720
reverting near_global_ocean.jl example
taimoorsohail Mar 14, 2025
3177cf2
Update near_global_ocean_simulation.jl
taimoorsohail Mar 14, 2025
868c870
Update Project.toml
taimoorsohail Mar 14, 2025
5f02123
Added checkpointing test; integrated checkpointer into one_degree exa…
taimoorsohail Mar 14, 2025
f9087aa
set clock method for each simulation type
navidcy Mar 14, 2025
4b0ebeb
Apply suggestions from code review
navidcy Mar 14, 2025
3f0b9d5
Apply suggestions from code review
navidcy Mar 14, 2025
f4aa21e
don't pickup by default; add explanation
navidcy Mar 14, 2025
0e0a168
merge
navidcy Mar 14, 2025
f56ca8e
move set_clock! for PrescribedAtmosphere to where it belongs
navidcy Mar 14, 2025
f8c1444
move set_clock! for PrescribedAtmosphere to where it belongs
navidcy Mar 14, 2025
cfba574
only pickup in the second time
navidcy Mar 14, 2025
9f811df
Merge branch 'main' into ts/checkpointer-for-coupled-model
navidcy Mar 14, 2025
4110aca
extend set!(model::OSIM,...) instead of set!(sim:OSIMSIM,...)
navidcy Mar 16, 2025
e56515f
Merge branch 'main' into ts/checkpointer-for-coupled-model
navidcy Mar 16, 2025
a9be97c
use default radiation
navidcy Mar 16, 2025
afd7a66
add set!(::PrescribedAtmospher, checkopoint_file_path)
navidcy Mar 16, 2025
1fddbf2
use set!(::PrescribedAtmosphere,...)
navidcy Mar 16, 2025
8699207
check all clocks are aligned
navidcy Mar 16, 2025
9f9ca89
drop unused aliases
navidcy Mar 16, 2025
ba80b56
Merge branch 'ts/checkpointer-for-coupled-model' of github.com:CliMA/…
taimoorsohail Mar 19, 2025
90abea4
Removed bottom line
taimoorsohail Mar 19, 2025
9efd6b8
try to generalize
navidcy Mar 19, 2025
006574f
Merge branch 'ts/checkpointer-for-coupled-model' of github.com:CliMA/…
navidcy Mar 19, 2025
e3029cb
Merge branch 'main' into ts/checkpointer-for-coupled-model
navidcy Mar 19, 2025
1e8d638
don't assume ocean component is special
navidcy Mar 20, 2025
5a69a52
bump Oceanigans compat
navidcy Mar 20, 2025
a74499f
remove commented code
navidcy Mar 20, 2025
0a46067
properties in write_output! is kwarg
navidcy Mar 20, 2025
98ec18b
undo changes
navidcy Mar 20, 2025
f9e9e60
Delete examples/generate_atmos_dataset.jl
navidcy Mar 20, 2025
39c347a
Delete src/CoupledSimulation.jl
navidcy Mar 20, 2025
5281eab
Delete test/test_ocean_sea_ice_model_parameter_space.jl
navidcy Mar 20, 2025
368358e
Delete test/test_simulations.jl
navidcy Mar 20, 2025
021c306
Delete src/DataWrangling/JRA55.jl
navidcy Mar 20, 2025
48dd1b1
Delete src/DistributedUtils.jl
navidcy Mar 20, 2025
fdf45d7
don't import things we don't need
navidcy Mar 20, 2025
875d045
cleanup
navidcy Mar 20, 2025
1e34bb5
validate_properties -> validate_checkpointed_properties
navidcy Mar 20, 2025
1f3a46b
no need to duplicate validation
navidcy Mar 20, 2025
4f9425a
fix initialize! and update_state! + add set_clock!
navidcy Mar 25, 2025
bf96083
reorganize imports
navidcy Mar 25, 2025
cd7f0c5
wip
navidcy Apr 24, 2025
47d1121
merge main and resolve conflicts
navidcy Apr 24, 2025
8bd0d36
Update ClimaOcean.jl
navidcy Apr 25, 2025
ed8c6df
Merge branch 'main' into ts/checkpointer-for-coupled-model
navidcy Apr 28, 2025
72efbc4
Merge branch 'main' into ts/checkpointer-for-coupled-model
taimoorsohail Apr 30, 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
18 changes: 18 additions & 0 deletions examples/one_degree_simulation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,14 @@ ocean.output_writers[:surface] = JLD2OutputWriter(ocean.model, outputs;
overwrite_existing = true,
array_type = Array{Float32})

# We also save a series of Checkpointers that will enable us to restart the simulation
# from a later point; ideal for longer runs.

simulation.output_writers[:checkpoint] = Checkpointer(ocean.model;
schedule = TimeInterval(5days),
prefix = "one_degree_checkpointer",
overwrite_existing = true)

# ### Ready to run

# We are ready to press the big red button and run the simulation.
Expand All @@ -184,6 +192,16 @@ simulation.stop_time = 360days

run!(simulation)

# After we had run long-enough to produce at least one checkpointer file, if we want to restart the
# simulation we call
#
# ```julia
# run!(simulation, pickup=true)
# ```
#
# Doing so, simulation will be picked up from the latest checkpointer.


# ### A pretty movie
#
# We load the saved output and make a pretty movie of the simulation. First we plot a snapshot:
Expand Down
4 changes: 2 additions & 2 deletions src/ClimaOcean.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export
Metadata,
all_dates,
JRA55FieldTimeSeries,
ECCO_field,
ECCO_field,
ECCORestoring,
LinearlyTaperedPolarMask,
ocean_simulation,
Expand All @@ -51,7 +51,7 @@ const SKOFTS = SomeKindOfFieldTimeSeries
@inline stateindex(a::SKOFTS, i, j, k, grid, time, args...) = @inbounds a[i, j, k, time]

@inline function stateindex(a::Function, i, j, k, grid, time, loc)
LX, LY, LZ = loc
LX, LY, LZ = loc
λ, φ, z = node(i, j, k, grid, LX(), LY(), LZ())
return a(λ, φ, z, time)
end
Expand Down
54 changes: 43 additions & 11 deletions src/OceanSeaIceModels/PrescribedAtmospheres.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ using Oceananigans.OutputReaders: FieldTimeSeries, update_field_time_series!, ex
using Oceananigans.TimeSteppers: Clock, tick!

using Adapt
using JLD2
using Thermodynamics.Parameters: AbstractThermodynamicsParameters

import Oceananigans.Fields: set!
import Oceananigans.OutputWriters: checkpointer_address
import Oceananigans.TimeSteppers: time_step!

import Thermodynamics.Parameters:
Expand All @@ -22,15 +25,15 @@ import Thermodynamics.Parameters:
T_0, # Enthalpy reference temperature
LH_v0, # Vaporization enthalpy at the reference temperature
LH_s0, # Sublimation enthalpy at the reference temperature
LH_f0, # Fusionn enthalpy at the reference temperature
LH_f0, # Fusion enthalpy at the reference temperature
cp_d, # Heat capacity of dry air at constant pressure
cp_v, # Isobaric specific heat capacity of gaseous water vapor
cp_l, # Isobaric specific heat capacity of liquid water
cp_i, # Isobaric specific heat capacity of water ice
cv_v, # Heat capacity of dry air at constant volume
cv_l, # Isobaric specific heat capacity of liquid water
cv_i, # Isobaric specific heat capacity of liquid water
e_int_v0, # what? someting about reference internal energy of water vapor
e_int_v0, # what? something about reference internal energy of water vapor
T_freeze, # Freezing temperature of _pure_ water
T_triple, # Triple point temperature of _pure_ water
press_triple, # Triple point pressure of pure water
Expand Down Expand Up @@ -75,7 +78,7 @@ Construct a set of parameters that define the density of moist air,
```

where ``p`` is pressure, ``T`` is temperature, ``q`` defines the partition
of total mass into vapor, liqiud, and ice mass fractions, and
of total mass into vapor, liquid, and ice mass fractions, and
``Rᵐ`` is the effective specific gas constant for the mixture,

```math
Expand Down Expand Up @@ -221,7 +224,7 @@ Base.summary(::PATP{FT}) where FT = "PrescribedAtmosphereThermodynamicsParameter
function Base.show(io::IO, p::PrescribedAtmosphereThermodynamicsParameters)
FT = eltype(p)

cp = p.constitutive
cp = p.constitutive
hc = p.heat_capacity
pt = p.phase_transitions

Expand All @@ -238,9 +241,9 @@ function Base.show(io::IO, p::PrescribedAtmosphereThermodynamicsParameters)
"└── PhaseTransitionParameters{$FT}", '\n',
" ├── reference_vaporization_enthalpy (ℒᵛ⁰): ", prettysummary(pt.reference_vaporization_enthalpy), '\n',
" ├── reference_sublimation_enthalpy (ℒˢ⁰): ", prettysummary(pt.reference_sublimation_enthalpy), '\n',
" ├── reference_temperature (T⁰): ", prettysummary(pt.reference_temperature), '\n',
" ├── reference_temperature (T⁰): ", prettysummary(pt.reference_temperature), '\n',
" ├── triple_point_temperature (Tᵗʳ): ", prettysummary(pt.triple_point_temperature), '\n',
" ├── triple_point_pressure (pᵗʳ): ", prettysummary(pt.triple_point_pressure), '\n',
" ├── triple_point_pressure (pᵗʳ): ", prettysummary(pt.triple_point_pressure), '\n',
" ├── water_freezing_temperature (Tᶠ): ", prettysummary(pt.water_freezing_temperature), '\n',
" └── total_ice_nucleation_temperature (Tⁱ): ", prettysummary(pt.total_ice_nucleation_temperature))
end
Expand Down Expand Up @@ -318,6 +321,36 @@ function Base.show(io::IO, pa::PrescribedAtmosphere)
print(io, "└── boundary_layer_height: ", prettysummary(pa.boundary_layer_height))
end

# set the clock to be the same as the ocean model
function set!(model::PrescribedAtmosphere, checkpoint_file_path)
addr = checkpointer_address(model)

jldopen(checkpoint_file_path, "r") do file
checkpointed_clock = file["$addr/clock"]

# Update model clock
set_clock!(model, checkpointed_clock)
end

return nothing
end

checkpointer_address(::PrescribedAtmosphere) = "HydrostaticFreeSurfaceModel"
Copy link
Member

Choose a reason for hiding this comment

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

Ideally we'd like to make the checkpointer also save the clock for the atmosphere and all other models and then read it everything off from the corresponding checkpointer address.

Copy link
Member

Choose a reason for hiding this comment

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

I think that's for a future PR..

Copy link
Member

Choose a reason for hiding this comment

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

I don't know, I'm apprehensive... I hate this

checkpointer_address(::PrescribedAtmosphere) = "HydrostaticFreeSurfaceModel"

because it's a hack. But on the other hand I like the symmetry here:

function set!(model::OSIM, checkpoint_file_path::AbstractString)
atmosphere = model.atmosphere
ocean = model.ocean.model
set!(ocean, checkpoint_file_path)
set!(atmosphere, checkpoint_file_path)
set_clock!(model, ocean.clock)
return nothing
end

Essentially the hack tells the checkpointer for the atmosphere that it should go read data from the ocean model checkpointer.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I guess this sets the basis for future code development so in that sense is OK?

Copy link
Member

Choose a reason for hiding this comment

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

why do you have to write

checkpointer_address(::PrescribedAtmosphere) = "HydrostaticFreeSurfaceModel"

Copy link
Member

Choose a reason for hiding this comment

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

I’m changing that
That’s part of the hack I was explaining yesterday I don’t like.


"""
set_clock!(sim, clock)

Set the clock of `sim`ulation to match the values of `clock`.
"""
function set_clock!(sim::PrescribedAtmosphere, clock)
sim.clock.time = clock.time
sim.clock.iteration = clock.iteration
sim.clock.last_Δt = clock.last_Δt
sim.clock.last_stage_Δt = clock.last_stage_Δt
sim.clock.stage = clock.stage
return nothing
end

function default_atmosphere_velocities(grid, times)
ua = FieldTimeSeries{Center, Center, Nothing}(grid, times)
va = FieldTimeSeries{Center, Center, Nothing}(grid, times)
Expand Down Expand Up @@ -357,15 +390,15 @@ end

for fts in ftses
update_field_time_series!(fts, time)
end
end

return nothing
end

@inline thermodynamics_parameters(atmos::Nothing) = nothing
@inline thermodynamics_parameters(atmos::PrescribedAtmosphere) = atmos.thermodynamics_parameters
@inline surface_layer_height(atmos::PrescribedAtmosphere) = atmos.surface_layer_height
@inline boundary_layer_height(atmos::PrescribedAtmosphere) = atmos.boundary_layer_height
@inline boundary_layer_height(atmos::PrescribedAtmosphere) = atmos.boundary_layer_height

"""
PrescribedAtmosphere(grid, times;
Expand Down Expand Up @@ -420,7 +453,7 @@ struct TwoBandDownwellingRadiation{SW, LW}
end

"""
TwoBandDownwellingRadiation(shortwave=nothing, longwave=nothing)
TwoBandDownwellingRadiation(; shortwave=nothing, longwave=nothing)

Return a two-band model for downwelling radiation (split in a shortwave band
and a longwave band) that passes through the atmosphere and arrives at the surface of ocean
Expand All @@ -434,4 +467,3 @@ Adapt.adapt_structure(to, tsdr::TwoBandDownwellingRadiation) =
adapt(to, tsdr.longwave))

end # module

51 changes: 37 additions & 14 deletions src/OceanSeaIceModels/ocean_sea_ice_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ using Oceananigans.TimeSteppers: Clock
using Oceananigans: SeawaterBuoyancy
using ClimaSeaIce.SeaIceThermodynamics: melting_temperature
using KernelAbstractions: @kernel, @index

using SeawaterPolynomials: TEOS10EquationOfState

import Thermodynamics as AtmosphericThermodynamics
Expand All @@ -12,12 +11,14 @@ import Thermodynamics as AtmosphericThermodynamics
import Oceananigans: fields, prognostic_fields
import Oceananigans.Architectures: architecture
import Oceananigans.Fields: set!
import Oceananigans.Models: timestepper, NaNChecker, default_nan_checker
import Oceananigans.OutputWriters: default_included_properties
import Oceananigans.Simulations: reset!, initialize!, iteration
import Oceananigans.Models: timestepper, NaNChecker, default_nan_checker, initialization_update_state!
import Oceananigans.OutputWriters: default_included_properties, checkpointer_address,
write_output!, initialize_jld2_file!
import Oceananigans.Simulations: reset!, initialize!, iteration, run!
import Oceananigans.TimeSteppers: time_step!, update_state!, time
import Oceananigans.Utils: prettytime
import Oceananigans.Models: timestepper, NaNChecker, default_nan_checker, initialization_update_state!

import .PrescribedAtmospheres: set_clock!

mutable struct OceanSeaIceModel{I, A, O, F, C, Arch} <: AbstractModel{Nothing, Arch}
architecture :: Arch
Expand Down Expand Up @@ -59,14 +60,13 @@ prettytime(model::OSIM) = prettytime(model.clock.time)
iteration(model::OSIM) = model.clock.iteration
timestepper(::OSIM) = nothing
default_included_properties(::OSIM) = tuple()
prognostic_fields(cm::OSIM) = nothing
prognostic_fields(::OSIM) = nothing
fields(::OSIM) = NamedTuple()
default_clock(TT) = Oceananigans.TimeSteppers.Clock{TT}(0, 0, 1)
time(model::OSIM) = model.clock.time
checkpointer_address(::OSIM) = "HydrostaticFreeSurfaceModel"

function reset!(model::OSIM)
reset!(model.ocean)
return nothing
end
reset!(model::OSIM) = reset!(model.ocean)

# Make sure to initialize the exchanger here
function initialization_update_state!(model::OSIM)
Expand All @@ -81,6 +81,32 @@ function initialize!(model::OSIM)
return nothing
end

initialize_jld2_file!(filepath, init, jld2_kw, including, outputs, model::OSIM) =
initialize_jld2_file!(filepath, init, jld2_kw, including, outputs, model.ocean.model)

write_output!(c::Checkpointer, model::OSIM) = write_output!(c, model.ocean.model)

function set_clock!(model::OSIM, clock)
model.clock.time = clock.time
model.clock.iteration = clock.iteration
model.clock.last_Δt = clock.last_Δt
model.clock.last_stage_Δt = clock.last_stage_Δt
model.clock.stage = clock.stage
return nothing
end

function set!(model::OSIM, checkpoint_file_path::AbstractString)
atmosphere = model.atmosphere
ocean = model.ocean.model

set!(ocean, checkpoint_file_path)
set!(atmosphere, checkpoint_file_path)

set_clock!(model, ocean.clock)

return nothing
end

reference_density(unsupported) =
throw(ArgumentError("Cannot extract reference density from $(typeof(unsupported))"))

Expand Down Expand Up @@ -159,10 +185,8 @@ function OceanSeaIceModel(ocean, sea_ice=FreezingLimitedOceanTemperature(eltype(
return ocean_sea_ice_model
end

time(coupled_model::OceanSeaIceModel) = coupled_model.clock.time

# Check for NaNs in the first prognostic field (generalizes to prescribed velocities).
function default_nan_checker(model::OceanSeaIceModel)
function default_nan_checker(model::OSIM)
u_ocean = model.ocean.model.velocities.u
nan_checker = NaNChecker((; u_ocean))
return nan_checker
Expand Down Expand Up @@ -199,4 +223,3 @@ function above_freezing_ocean_temperature!(ocean, sea_ice::SeaIceSimulation)

return nothing
end

67 changes: 60 additions & 7 deletions test/test_ocean_sea_ice_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,15 @@ using ClimaSeaIce.Rheologies
halo = (7, 7, 7),
z = (-6000, 0))

bottom_height = regrid_bathymetry(grid;
bottom_height = regrid_bathymetry(grid;
minimum_depth = 10,
interpolation_passes = 20,
major_basins = 1)

grid = ImmersedBoundaryGrid(grid, GridFittedBottom(bottom_height); active_cells_map=true)

free_surface = SplitExplicitFreeSurface(grid; substeps=20)
ocean = ocean_simulation(grid; free_surface)
ocean = ocean_simulation(grid; free_surface)

backend = JRA55NetCDFBackend(4)
atmosphere = JRA55PrescribedAtmosphere(arch; backend)
Expand All @@ -69,18 +69,18 @@ using ClimaSeaIce.Rheologies
#####

# Adding a sea ice model to the coupled model
τua = Field{Face, Center, Nothing}(grid)
τua = Field{Face, Center, Nothing}(grid)
τva = Field{Center, Face, Nothing}(grid)

dynamics = SeaIceMomentumEquation(grid;
dynamics = SeaIceMomentumEquation(grid;
coriolis = ocean.model.coriolis,
top_momentum_stress = (u=τua, v=τva),
rheology = ElastoViscoPlasticRheology(),
solver = SplitExplicitSolver(120))

sea_ice = sea_ice_simulation(grid; dynamics, advection=WENO(order=7))
sea_ice = sea_ice_simulation(grid; dynamics, advection=WENO(order=7))
liquidus = sea_ice.model.ice_thermodynamics.phase_transitions.liquidus

# Set the ocean temperature and salinity
set!(ocean.model, T=temperature_metadata[1], S=salinity_metadata[1])
above_freezing_ocean_temperature!(ocean, sea_ice)
Expand All @@ -102,3 +102,56 @@ using ClimaSeaIce.Rheologies
end
end

"""
testbed_coupled_simulation(grid; stop_iteration=8)

Return a test-bed coupled simulation with a Checkpointer.
"""
function testbed_coupled_simulation(grid; stop_iteration=8)
ocean = ocean_simulation(grid)

atmosphere = JRA55PrescribedAtmosphere(arch; backend=JRA55NetCDFBackend(4))

coupled_model = OceanSeaIceModel(ocean; atmosphere)

simulation = Simulation(coupled_model; Δt=10, stop_iteration)

simulation.output_writers[:checkpoint] = Checkpointer(ocean.model;
schedule = IterationInterval(3),
prefix = "checkpointer",
dir = ".",
verbose = true,
overwrite_existing = true)

return simulation
end

@testset "Checkpointer test" begin
for arch in test_architectures

Nx, Ny, Nz = 40, 25, 10

grid = LatitudeLongitudeGrid(arch;
size = (Nx, Ny, Nz),
halo = (7, 7, 7),
z = (-6000, 0),
latitude = (-75, 75),
longitude = (0, 360))

simulation = testbed_coupled_simulation(grid; stop_iteration=8)

run!(simulation)

# create a new simulation and pick up from the last checkpointer
new_simulation = testbed_coupled_simulation(grid; stop_iteration=13)

run!(new_simulation, pickup=true)

# ensure the ocean, atmosphere, and coupled model are all at same time and iteration
clock = new_simulation.model.ocean.model.clock
@test new_simulation.model.atmosphere.clock.iteration ≈ clock.iteration
@test new_simulation.model.atmosphere.clock.time ≈ clock.time
@test new_simulation.model.clock.iteration ≈ clock.iteration
@test new_simulation.model.clock.time ≈ clock.time
end
end