Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ ClimaCoupler.jl Release Notes

### ClimaCoupler features

#### Shared component `dt` can be overwritten for individual components
Previously, we required that the user either specify a shared `dt` to be
used by all component models, or specify values for all component models
(`dt_atmos`, `dt_ocean`, `dt_seaice`, `dt_land`). If fewer than 4
model-specific timesteps were provided, they would be discarded and
`dt` would be used uniformly instead. After this PR, if a user provides
fewer than 4 model-specific timesteps, they will be used for those models,
and the generic `dt` will be used for any models that don't have a more
specific timestep.
This makes choosing the timesteps simpler and allows us to easily set
specific `dt`s only for the models we're interested in.

This PR also changes the prescribed ocean and sea ice simulations
to update the stored SST/SIC based on a daily schedule. Now, the
input data will be interpolated from monthly to daily instead of
to every timestep.

#### Add default `get_field` methods for surface models PR[#1210](https://github.com/CliMA/ClimaCoupler.jl/pull/1210)
Add default methods for `get_field` methods that are commonly
not extended for surface models. These return reasonable default
Expand Down
2 changes: 1 addition & 1 deletion config/ci_configs/amip_component_dts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ co2: "maunaloa"
dt_atmos: "150secs"
dt_cpl: "150secs"
dt_land: "50secs"
dt_ocean: "30secs"
dt_ocean: "75secs"
dt_rad: "1hours"
dt_save_to_sol: "1days"
dt_seaice: "37.5secs"
Expand Down
4 changes: 2 additions & 2 deletions experiments/ClimaEarth/cli_options.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ function argparse_settings()
arg_type = String
default = "20000101"
"--dt_cpl"
help = "Coupling time step in seconds [400 (default); allowed formats: \"Nsecs\", \"Nmins\", \"Nhours\", \"Ndays\", \"Inf\"]"
help = "Coupling time step in seconds [400secs (default); allowed formats: \"Nsecs\", \"Nmins\", \"Nhours\", \"Ndays\", \"Inf\"]"
arg_type = String
default = "400secs"
"--dt"
help = "Component model time step [allowed formats: \"Nsecs\", \"Nmins\", \"Nhours\", \"Ndays\", \"Inf\"]"
help = "Component model time step [400secs (default); allowed formats: \"Nsecs\", \"Nmins\", \"Nhours\", \"Ndays\", \"Inf\"]"
arg_type = String
default = "400secs"
"--dt_atmos"
Expand Down
6 changes: 4 additions & 2 deletions experiments/ClimaEarth/components/atmosphere/climaatmos.jl
Original file line number Diff line number Diff line change
Expand Up @@ -453,8 +453,10 @@ function get_atmos_config_dict(coupler_dict::Dict, job_id::String, atmos_output_

# The Atmos `get_simulation` function expects the atmos config to contains its timestep size
# in the `dt` field. If there is a `dt_atmos` field in coupler_dict, we add it to the atmos config as `dt`
dt_atmos = haskey(coupler_dict, "dt_atmos") ? coupler_dict["dt_atmos"] : coupler_dict["dt"]
atmos_config["dt"] = dt_atmos
component_dt_dict = coupler_dict["component_dt_dict"]
dt_atmos = haskey(component_dt_dict, "dt_atmos") ? component_dt_dict["dt_atmos"] : component_dt_dict["dt"]
# convert from number of seconds to a string that atmos can parse
atmos_config["dt"] = string(dt_atmos) * "secs"

# set restart file to the initial file saved in this location if it is not nothing
# TODO this is hardcoded and should be fixed once we have a better restart system
Expand Down
9 changes: 7 additions & 2 deletions experiments/ClimaEarth/components/ocean/prescr_ocean.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ClimaUtilities.ClimaArtifacts: @clima_artifact
import Interpolations # triggers InterpolationsExt in ClimaUtilities
import Thermodynamics as TD
import ClimaCoupler: Checkpointer, FieldExchanger, Interfacer
import ClimaDiagnostics as CD

"""
PrescribedOceanSimulation{C}
Expand Down Expand Up @@ -84,6 +85,8 @@ function PrescribedOceanSimulation(
SST_init = zeros(space)
evaluate!(SST_init, SST_timevaryinginput, t_start)

SST_schedule = CD.Schedules.EveryCalendarDtSchedule(TimeManager.time_to_period("1days"); start_date = date0)

# Create the cache
cache = (;
T_sfc = SST_init,
Expand All @@ -97,6 +100,7 @@ function PrescribedOceanSimulation(
phase = TD.Liquid(),
thermo_params = thermo_params,
SST_timevaryinginput = SST_timevaryinginput,
SST_schedule = SST_schedule,
)
return PrescribedOceanSimulation(cache)
end
Expand All @@ -107,10 +111,11 @@ end
Interfacer.step!(sim::PrescribedOceanSimulation, t)

Update the cached surface temperature field using the prescribed data
at each timestep.
at each timestep. This doesn't happen at every timestep,
but only when the SST data is scheduled to be updated.
"""
function Interfacer.step!(sim::PrescribedOceanSimulation, t)
evaluate!(sim.cache.T_sfc, sim.cache.SST_timevaryinginput, t)
sim.cache.SST_schedule(t) && evaluate!(sim.cache.T_sfc, sim.cache.SST_timevaryinginput, t)
end

function Checkpointer.get_model_cache(sim::PrescribedOceanSimulation)
Expand Down
28 changes: 24 additions & 4 deletions experiments/ClimaEarth/components/ocean/prescr_seaice.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import SciMLBase
import ClimaCore as CC
import ClimaDiagnostics as CD
import ClimaTimeSteppers as CTS
import ClimaUtilities.TimeVaryingInputs: TimeVaryingInput, evaluate!
import ClimaUtilities.ClimaArtifacts: @clima_artifact
Expand Down Expand Up @@ -143,8 +144,20 @@ function PrescribedIceSimulation(
tspan = Float64.(tspan)
saveat = Float64.(saveat)
end

# Set up a callback to read in SST data daily
SIC_schedule = CD.Schedules.EveryCalendarDtSchedule(TimeManager.time_to_period("1days"); start_date = date0)
SIC_update_cb = TimeManager.Callback(SIC_schedule, read_sic_data!)

problem = SciMLBase.ODEProblem(ode_function, Y, tspan, (; cache..., params = params))
integrator = SciMLBase.init(problem, ode_algo, dt = dt, saveat = saveat, adaptive = false)
integrator = SciMLBase.init(
problem,
ode_algo,
dt = dt,
saveat = saveat,
adaptive = false,
callback = SciMLBase.CallbackSet(SIC_update_cb),
)

sim = PrescribedIceSimulation(params, space, integrator)

Expand All @@ -153,6 +166,16 @@ function PrescribedIceSimulation(
return sim
end

"""
read_sic_data!(integrator)

Read in the sea ice concentration data at the current time step.
This function is intended to be used within a callback
"""
function read_sic_data!(integrator)
evaluate!(integrator.p.area_fraction, integrator.p.SIC_timevaryinginput, integrator.t)
end

# extensions required by Interfacer
Interfacer.get_field(sim::PrescribedIceSimulation, ::Val{:area_fraction}) = sim.integrator.p.area_fraction
Interfacer.get_field(sim::PrescribedIceSimulation, ::Val{:roughness_buoyancy}) = sim.integrator.p.params.z0b
Expand Down Expand Up @@ -237,9 +260,6 @@ function ice_rhs!(dY, Y, p, t)
FT = eltype(Y)
params = p.params

# Update the cached area fraction with the current SIC
evaluate!(p.area_fraction, p.SIC_timevaryinginput, t)

# Overwrite ice fraction with the static land area fraction anywhere we have nonzero land area
# max needed to avoid Float32 errors (see issue #271; Heisenbug on HPC)
@. p.area_fraction = max(min(p.area_fraction, FT(1) - p.land_fraction), FT(0))
Expand Down
2 changes: 1 addition & 1 deletion experiments/ClimaEarth/setup_run.jl
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ function setup_and_run(config_dict::AbstractDict)
NB: Eventually, we will call all of radiation from the coupler, in addition to the albedo calculation.
=#
schedule_checkpoint = EveryCalendarDtSchedule(TimeManager.time_to_period(checkpoint_dt); start_date = date0)
checkpoint_cb = TimeManager.TimeManager.Callback(schedule_checkpoint, Checkpointer.checkpoint_sims)
checkpoint_cb = TimeManager.Callback(schedule_checkpoint, Checkpointer.checkpoint_sims)

if sim_mode <: AMIPMode
schedule_albedo = EveryCalendarDtSchedule(TimeManager.time_to_period(dt_rad); start_date = date0)
Expand Down
29 changes: 11 additions & 18 deletions experiments/ClimaEarth/user_io/arg_parsing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -232,27 +232,20 @@ function parse_component_dts!(config_dict)
# Specify component model names
component_dt_names = ["dt_atmos", "dt_land", "dt_ocean", "dt_seaice"]
component_dt_dict = Dict{String, typeof(Δt_cpl)}()
# check if all component dt's are specified
if all(key -> !isnothing(config_dict[key]), component_dt_names)
# when all component dt's are specified, ignore the dt field
if haskey(config_dict, "dt")
@warn "Removing dt in favor of individual component dt's"
delete!(config_dict, "dt")
end
for key in component_dt_names

@assert all(key -> !isnothing(config_dict[key]), component_dt_names) || haskey(config_dict, "dt") "all model-specific timesteps (dt_atmos, dt_land, dt_ocean, and dt_seaice) or a generic timestep (dt) must be specified"

for key in component_dt_names
if !isnothing(config_dict[key])
# Check if the component timestep is specified
component_dt = Float64(Utilities.time_to_seconds(config_dict[key]))
@assert isapprox(Δt_cpl % component_dt, 0.0) "Coupler dt must be divisible by all component dt's\n dt_cpl = $Δt_cpl\n $key = $component_dt"
component_dt_dict[key] = component_dt
end
else
# when not all component dt's are specified, use the dt field
@assert haskey(config_dict, "dt") "dt or (dt_atmos, dt_land, dt_ocean, and dt_seaice) must be specified"
for key in component_dt_names
if !isnothing(config_dict[key])
@warn "Removing $key from config in favor of dt because not all component dt's are specified"
end
delete!(config_dict, key)
component_dt_dict[key] = Float64(Utilities.time_to_seconds(config_dict["dt"]))
else
# If the component timestep is not specified, use the generic timestep
dt = Float64(Utilities.time_to_seconds(config_dict["dt"]))
@assert isapprox(Δt_cpl % dt, 0.0) "Coupler dt must be divisible by all component dt's\n dt_cpl = $Δt_cpl\n dt = $dt"
component_dt_dict[key] = dt
end
end
config_dict["component_dt_dict"] = component_dt_dict
Expand Down
Loading