-
Notifications
You must be signed in to change notification settings - Fork 5
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
Add remapping to coupler puts & gets #58
Changes from all commits
0e4b5dd
570b843
c6f1bfd
708f3b7
4a54a9c
cacae12
e9a0958
49f85f0
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 |
---|---|---|
|
@@ -4,4 +4,5 @@ authors = ["CliMA Contributors <[email protected]>"] | |
version = "0.1.0" | ||
|
||
[deps] | ||
ClimaCore = "d414da3d-4745-48bb-8d80-42e94e092884" | ||
PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -86,13 +86,17 @@ lnd_Y_default, lnd_domain = lnd_init(xmin = 0, xmax = 500, helem = 10, npoly = 0 | |
# Build remapping operators | ||
atm_boundary = Spaces.level(atm_domain.hv_face_space, PlusHalf(0)) | ||
|
||
atm_to_ocn = Operators.LinearRemap(ocn_domain, atm_boundary) | ||
atm_to_lnd = Operators.LinearRemap(lnd_domain, atm_boundary) | ||
ocn_to_atm = Operators.LinearRemap(atm_boundary, ocn_domain) | ||
lnd_to_atm = Operators.LinearRemap(atm_boundary, lnd_domain) | ||
maps = ( | ||
atmos_to_ocean = Operators.LinearRemap(ocn_domain, atm_boundary), | ||
atmos_to_land = Operators.LinearRemap(lnd_domain, atm_boundary), | ||
ocean_to_atmos = Operators.LinearRemap(atm_boundary, ocn_domain), | ||
land_to_atmos = Operators.LinearRemap(atm_boundary, lnd_domain), | ||
) | ||
|
||
# initialize coupling fields | ||
atm_T_sfc = Operators.remap(ocn_to_atm, ocn_Y_default.T_sfc) .+ Operators.remap(lnd_to_atm, lnd_Y_default.T_sfc) # masked arrays; regrid to atm grid | ||
atm_T_sfc = | ||
Operators.remap(maps.ocean_to_atmos, ocn_Y_default.T_sfc) .+ | ||
Operators.remap(maps.land_to_atmos, lnd_Y_default.T_sfc) # masked arrays; regrid to atm grid | ||
atm_F_sfc = Fields.zeros(atm_boundary) | ||
ocn_F_sfc = Fields.zeros(ocn_domain) | ||
lnd_F_sfc = Fields.zeros(lnd_domain) | ||
|
@@ -111,18 +115,35 @@ lnd_p = (cpl_parameters, F_sfc = lnd_F_sfc) | |
land = LandSimulation(lnd_Y, t_start, cpl_Δt / lnd_nsteps, t_end, SSPRK33(), lnd_p, saveat) | ||
|
||
# coupled simulation | ||
struct AOLCoupledSimulation{FT, A <: AtmosSimulation, O <: OceanSimulation, L <: LandSimulation} <: | ||
ClimaCoupler.AbstractCoupledSimulation | ||
struct AOLCoupledSimulation{ | ||
FT, | ||
A <: AtmosSimulation, | ||
O <: OceanSimulation, | ||
L <: LandSimulation, | ||
C <: ClimaCoupler.CouplerState, | ||
} <: ClimaCoupler.AbstractCoupledSimulation | ||
# Atmosphere Simulation | ||
atmos::A | ||
# Ocean Simulation | ||
ocean::O | ||
# Land Simulation | ||
land::L | ||
# Coupler storage | ||
coupler::C | ||
# The coupled time step size | ||
Δt::FT | ||
end | ||
sim = AOLCoupledSimulation(atmos, ocean, land, cpl_Δt) | ||
|
||
# init coupler fields and maps | ||
coupler = CouplerState() | ||
coupler_add_field!(coupler, :T_sfc_ocean, ocean.integrator.u.T_sfc; write_sim = ocean) | ||
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. Another way of asserting that the correct models write into the right vars could be via multiple dispatch, which would allow specifying the same name to one variable in different domains (e.g. 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. I'm worried that that would lead to a large number of methods for these shared variables. I think it would be better to have fewer methods that can give us flexibility. Let's discuss further. |
||
coupler_add_field!(coupler, :T_sfc_land, land.integrator.u.T_sfc; write_sim = land) | ||
coupler_add_field!(coupler, :F_sfc, atmos.integrator.u.F_sfc; write_sim = atmos) | ||
for (name, map) in pairs(maps) | ||
coupler_add_map!(coupler, name, map) | ||
end | ||
|
||
sim = AOLCoupledSimulation(atmos, ocean, land, coupler, cpl_Δt) | ||
|
||
# step for sims built on OrdinaryDiffEq | ||
function step!(sim::ClimaCoupler.AbstractSimulation, t_stop) | ||
|
@@ -140,30 +161,35 @@ function cpl_run(simulation::AOLCoupledSimulation) | |
## Atmos | ||
# pre: reset flux accumulator | ||
atmos.integrator.u.F_sfc .= 0.0 # reset surface flux to be accumulated | ||
# don't want to alloc here.. | ||
T_sfc_ocean = coupler_get(coupler, :T_sfc_ocean, atmos) | ||
T_sfc_land = coupler_get(coupler, :T_sfc_land, atmos) | ||
atmos.integrator.p.T_sfc .= T_sfc_land .+ T_sfc_ocean | ||
|
||
# run | ||
# run | ||
# NOTE: use (t - integ_atm.t) here instead of Δt_cpl to avoid accumulating roundoff error in our timestepping. | ||
step!(atmos, t) | ||
coupler_put!(coupler, :F_sfc, atmos.integrator.u.F_sfc, atmos) | ||
|
||
## Ocean | ||
# pre: get accumulated flux from atmos | ||
ocn_F_sfc = ocean.integrator.p.F_sfc | ||
Operators.remap!(ocn_F_sfc, atm_to_ocn, atmos.integrator.u.F_sfc ./ cpl_Δt) | ||
ocn_F_sfc .= coupler_get(coupler, :F_sfc, ocean) ./ cpl_Δt | ||
|
||
# run | ||
step!(ocean, t) | ||
# post: send ocean surface temp to atmos | ||
Operators.remap!(atmos.integrator.p.T_sfc, ocn_to_atm, ocean.integrator.u.T_sfc) | ||
coupler_put!(coupler, :T_sfc_ocean, ocean.integrator.u.T_sfc, ocean) | ||
|
||
## Land | ||
# pre: get accumulated flux from atmos | ||
lnd_F_sfc = land.integrator.p.F_sfc | ||
Operators.remap!(lnd_F_sfc, atm_to_lnd, atmos.integrator.u.F_sfc ./ cpl_Δt) | ||
lnd_F_sfc .= coupler_get(coupler, :F_sfc, land) ./ cpl_Δt | ||
|
||
# run | ||
step!(land, t) | ||
# post: send ocean surface temp to atmos | ||
atmos.integrator.p.T_sfc .+= Operators.remap(lnd_to_atm, land.integrator.u.T_sfc) | ||
# post: send land surface temp to atmos | ||
coupler_put!(coupler, :T_sfc_land, land.integrator.u.T_sfc, land) | ||
end | ||
@info "Simulation Complete" | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,77 @@ | ||
using Test | ||
using Random | ||
using ClimaCoupler, Dates, Unitful | ||
using IntervalSets | ||
using ClimaCore: Domains, Meshes, Geometry, Topologies, Spaces, Fields, Operators | ||
|
||
struct SimulationA{T} <: ClimaCoupler.AbstractSimulation | ||
data::T | ||
end | ||
ClimaCoupler.name(::SimulationA) = :simA | ||
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. I wonder if we could get away with avoiding the (simA, simB, ...) assignment, e.g. using 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. Can you elaborate? 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. I guess it would be good for this to work for an arbitrary number of sims, each of which has it's own user-defined tag. Maybe the solution will become clearer next week, when we focus on the interface! :) |
||
|
||
struct SimulationB{T} <: ClimaCoupler.AbstractSimulation | ||
data::T | ||
end | ||
ClimaCoupler.name(::SimulationB) = :simB | ||
|
||
function spectral_space_2D(; n1 = 1, n2 = 1, Nij = 4) | ||
domain = Domains.RectangleDomain( | ||
Geometry.XPoint(-1.0) .. Geometry.XPoint(1.0), | ||
Geometry.YPoint(-1.0) .. Geometry.YPoint(1.0), | ||
x1periodic = false, | ||
x2periodic = false, | ||
x1boundary = (:east, :west), | ||
x2boundary = (:south, :north), | ||
) | ||
mesh = Meshes.RectilinearMesh(domain, n1, n2) | ||
grid_topology = Topologies.Topology2D(mesh) | ||
|
||
quad = Spaces.Quadratures.GLL{Nij}() | ||
space = Spaces.SpectralElementSpace2D(grid_topology, quad) | ||
return space | ||
end | ||
|
||
@testset "Coupler Interface" begin | ||
Random.seed!(26) | ||
|
||
spaceA = spectral_space_2D() | ||
spaceB = spectral_space_2D(n1 = 2, n2 = 2) | ||
|
||
simA = SimulationA(ones(spaceA)) | ||
simB = SimulationB(zeros(spaceB)) | ||
coupler = CouplerState() | ||
|
||
data = rand(10, 10) | ||
coupler_add_field!(coupler, :test1, data) | ||
coupler_add_field!(coupler, :test2, data) | ||
coupler_add_field!(coupler, :test3, data) | ||
coupler_add_field!(coupler, :test1, simA.data; write_sim = simA) | ||
|
||
map = Operators.LinearRemap(spaceB, spaceA) | ||
coupler_add_map!(coupler, :simA_to_simB, map) | ||
|
||
@show coupler | ||
|
||
@testset "coupler_get" begin | ||
@test data === coupler_get(coupler, :test1) | ||
@test simA.data === coupler_get(coupler, :test1) | ||
|
||
# test remapping | ||
@test map === ClimaCoupler.get_remap_operator(coupler, ClimaCoupler.name(simB), ClimaCoupler.name(simA)) | ||
@test ones(spaceB) ≈ coupler_get(coupler, :test1, simB) | ||
|
||
# key not in coupler dict | ||
@test_throws KeyError coupler_get(coupler, :idontexist) | ||
end | ||
|
||
@testset "coupler_put!" begin | ||
newdata = rand(10, 10) | ||
coupler_put!(coupler, :test1, newdata) | ||
|
||
newdata = zeros(spaceA) | ||
# simA can write to :test1 | ||
coupler_put!(coupler, :test1, newdata, simA) | ||
@test newdata == coupler_get(coupler, :test1) | ||
# coupler_put! is in-place; original data array has been modified | ||
@test data === coupler_get(coupler, :test1) | ||
@test simA.data === coupler_get(coupler, :test1) | ||
|
||
# simB cannot write to :test1 | ||
@test_throws AssertionError coupler_put!(coupler, :test1, newdata, simB) | ||
|
||
# coupler_put! must be to a previously add_fielded field | ||
@test_throws KeyError coupler_put!(coupler, :idontexist, newdata) | ||
# incoming data must match dimensions of add_fielded field | ||
@test_throws DimensionMismatch coupler_put!(coupler, :test1, rand(10, 5)) | ||
# coupler_put! must be to a previously added field | ||
@test_throws KeyError coupler_put!(coupler, :idontexist, newdata, simA) | ||
# incoming data must match dimensions/space of added field | ||
@test_throws ErrorException coupler_put!(coupler, :test1, ones(spaceB), simA) | ||
end | ||
end |
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.
minor, but we should be consistent between using "atm" and "atmos", "land" and "lnd, etc.