Skip to content

Caching inpainting data + some updates to the one degree simulation #230

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

Merged
merged 38 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
6adbbbf
Update compat
glwagner Nov 10, 2024
746b599
don't relax compass
navidcy Nov 10, 2024
4ac5f21
Update Project.toml
glwagner Nov 10, 2024
2736766
Update Project.toml
glwagner Nov 10, 2024
8545662
Update Project.toml
glwagner Nov 10, 2024
e148194
Generalize default free surface for IBG
glwagner Nov 10, 2024
9c3aa7e
Update Project.toml
navidcy Nov 10, 2024
ce7c116
use interior for max of field
navidcy Nov 10, 2024
f21a42b
Merge branch 'glw/more-one-degree' of github.com:CliMA/ClimaOcean.jl …
navidcy Nov 10, 2024
7509819
smaller Δt
navidcy Nov 10, 2024
b3913e9
Merge branch 'main' into glw/more-one-degree
navidcy Nov 10, 2024
d58fd5c
Merge branch 'main' into glw/more-one-degree
navidcy Nov 11, 2024
870e57a
Implement inpainting saving
glwagner Nov 11, 2024
adeddb5
Merge branch 'glw/more-one-degree' of https://github.com/CliMA/ClimaO…
glwagner Nov 11, 2024
854f5a8
Clean up
glwagner Nov 11, 2024
f6d4b48
Test inpainting caching
glwagner Nov 11, 2024
6d547ce
Update ECCO_field docstring
glwagner Nov 11, 2024
6b3ad97
Merge branch 'main' into glw/more-one-degree
glwagner Nov 12, 2024
b5c586f
Merge branch 'main' into glw/more-one-degree
navidcy Nov 12, 2024
7008439
Modify one degree simulation
Nov 13, 2024
13091c9
some more updates to the one degree simulation
simone-silvestri Nov 13, 2024
60b8a25
bugfix
simone-silvestri Nov 13, 2024
680f4b5
Merge remote-tracking branch 'origin/main' into glw/more-one-degree
simone-silvestri Nov 15, 2024
eef5a1b
cache inpainted data?
simone-silvestri Nov 15, 2024
c653299
ebugfix
simone-silvestri Nov 15, 2024
d1dd2f3
advancing
simone-silvestri Nov 15, 2024
b1ae2ae
small bug
simone-silvestri Nov 15, 2024
ccbe516
some bugfixes
simone-silvestri Nov 15, 2024
bccd3a8
add a `@root` to caching
simone-silvestri Nov 15, 2024
f1d3ced
open with read permission
simone-silvestri Nov 15, 2024
517782c
interpolate on grid beforehand
simone-silvestri Nov 15, 2024
3539660
fix minimum-seaice in the meantime
simone-silvestri Nov 15, 2024
235a67c
comment
simone-silvestri Nov 15, 2024
5c23b6e
trhow an error
simone-silvestri Nov 15, 2024
bcfc307
correct arch
simone-silvestri Nov 15, 2024
4c26acd
Merge remote-tracking branch 'origin/main' into glw/more-one-degree
glwagner Nov 18, 2024
88cc7e0
rm fake sea ice again
glwagner Nov 18, 2024
5715bf4
Make default true for caching
glwagner Nov 18, 2024
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
129 changes: 83 additions & 46 deletions experiments/one_degree_simulation/one_degree_simulation.jl
Original file line number Diff line number Diff line change
@@ -1,84 +1,121 @@
using ClimaOcean
using ClimaOcean.ECCO: ECCO4Monthly
using ClimaOcean.ECCO: ECCO4Monthly, NearestNeighborInpainting
using OrthogonalSphericalShellGrids
using Oceananigans
using Oceananigans.Units
using CFTime
using Dates
using Printf
using CUDA: @allowscalar, device!

arch = CPU()
z = exponential_z_faces(Nz=30, depth=6000)
Nx = 120
Ny = 60
Nz = length(z) - 1
using Oceananigans.Grids: znode

grid = TripolarGrid(arch; z, size = (Nx, Ny, Nz), north_poles_latitude=55, first_pole_longitude=70)
device!(3)
arch = GPU()

@show grid
#####
##### Grid and Bathymetry
#####

bottom_height = regrid_bathymetry(grid;
Nx = 360
Ny = 180
Nz = 100

z_faces = exponential_z_faces(; Nz, depth=5000, h=34)

underlying_grid = TripolarGrid(arch;
size = (Nx, Ny, Nz),
z = z_faces,
first_pole_longitude = 70,
north_poles_latitude = 55)

bottom_height = regrid_bathymetry(underlying_grid;
minimum_depth = 10,
interpolation_passes = 5,
major_basins = 3)
interpolation_passes = 75,
major_basins = 2)

# Open Gibraltar strait
# TODO: find a better way to do this
tampered_bottom_height = deepcopy(bottom_height)
view(tampered_bottom_height, 102:103, 124, 1) .= -400

grid = ImmersedBoundaryGrid(underlying_grid, GridFittedBottom(tampered_bottom_height))

#####
##### Closures
#####

# Closure
gm = Oceananigans.TurbulenceClosures.IsopycnalSkewSymmetricDiffusivity(κ_skew=1000, κ_symmetric=1000)
catke = Oceananigans.TurbulenceClosures.CATKEVerticalDiffusivity()
closure = (gm, catke)

# Polar restoring
@inline function restoring_mask(λ, φ, z, t=0)
ϵN = (φ - 75) / 5
ϵN = clamp(ϵN, zero(ϵN), one(ϵN))
ϵS = - (φ + 75) / 5
ϵS = clamp(ϵS, zero(ϵS), one(ϵS))
return ϵN + ϵS
end
catke = ClimaOcean.OceanSimulations.default_ocean_closure()
viscous_closure = Oceananigans.TurbulenceClosures.HorizontalScalarDiffusivity(ν=10000)

closure = (gm, catke, viscous_closure)

restoring_rate = 1 / 1days
#####
##### Restoring
#####

restoring_mask_field = CenterField(grid)
set!(restoring_mask_field, restoring_mask)
restoring_rate = 1 / 2days
z_below_surface = @allowscalar znode(1, 1, grid.Nz, grid, Center(), Center(), Face())

@inline sponge_layer(λ, φ, z, t, c, ω) = - restoring_mask(λ, φ, z, t) * ω * c
Fu = Forcing(sponge_layer, field_dependencies=:u, parameters=restoring_rate)
Fv = Forcing(sponge_layer, field_dependencies=:v, parameters=restoring_rate)
mask = LinearlyTaperedPolarMask(southern=(-80, -70), northern=(70, 90), z=(z_below_surface, 0))

dates = DateTimeProlepticGregorian(1993, 1, 1) : Month(1) : DateTimeProlepticGregorian(1994, 1, 1)
temperature = ECCOMetadata(:temperature, dates, ECCO4Monthly())
salinity = ECCOMetadata(:salinity, dates, ECCO4Monthly())
dates = DateTimeProlepticGregorian(1993, 1, 1) : Month(1) : DateTimeProlepticGregorian(1993, 11, 1)
temperature = ECCOMetadata(:temperature; dates, version=ECCO4Monthly(), dir="./")
salinity = ECCOMetadata(:salinity; dates, version=ECCO4Monthly(), dir="./")

FT = ECCORestoring(arch, temperature; grid, mask=restoring_mask_field, rate=restoring_rate)
FS = ECCORestoring(arch, salinity; grid, mask=restoring_mask_field, rate=restoring_rate)
forcing = (T=FT, S=FS, u=Fu, v=Fv)
# inpainting = NearestNeighborInpainting(30) should be enough to fill the gaps near bathymetry
FT = ECCORestoring(arch, temperature; grid, mask, rate=restoring_rate, inpainting=NearestNeighborInpainting(30))
FS = ECCORestoring(arch, salinity; grid, mask, rate=restoring_rate, inpainting=NearestNeighborInpainting(30))
forcing = (T=FT, S=FS)

#####
##### Ocean simulation
#####

momentum_advection = VectorInvariant()
tracer_advection = Centered(order=2)
tracer_advection = Centered(order=2)

# Should we add a side drag since this is at a coarser resolution?
ocean = ocean_simulation(grid; momentum_advection, tracer_advection,
closure, forcing,
tracers = (:T, :S, :e))

set!(ocean.model,
T = ECCOMetadata(:temperature; dates=first(dates)),
S = ECCOMetadata(:salinity; dates=first(dates)))
set!(ocean.model, T=ECCOMetadata(:temperature; dates=first(dates)),
S=ECCOMetadata(:salinity; dates=first(dates)))

#####
##### Atmospheric forcing
#####

radiation = Radiation(arch)
atmosphere = JRA55_prescribed_atmosphere(arch; backend=JRA55NetCDFBackend(20))

#####
##### Coupled simulation
#####

radiation = Radiation(arch)
atmosphere = JRA55_prescribed_atmosphere(arch; backend=JRA55NetCDFBackend(41))
sea_ice = ClimaOcean.OceanSeaIceModels.MinimumTemperatureSeaIce()
coupled_model = OceanSeaIceModel(ocean, sea_ice; atmosphere, radiation)

simulation = Simulation(coupled_model; Δt=1, stop_iteration=10)
simulation = Simulation(coupled_model; Δt=15minutes, stop_time=2*365days)

#####
##### Run it!
#####

wall_time = Ref(time_ns())

function progress(sim)
ocean = sim.model.ocean
u, v, w = ocean.model.velocities
T = ocean.model.tracers.T
Tmax = maximum(T)
Tmin = minimum(T)
umax = maximum(abs, u), maximum(abs, v), maximum(abs, w)
Tmax = maximum(interior(T))
Tmin = minimum(interior(T))
umax = (maximum(abs, interior(u)),
maximum(abs, interior(v)),
maximum(abs, interior(w)))

step_time = 1e-9 * (time_ns() - wall_time[])

@info @sprintf("Time: %s, n: %d, Δt: %s, max|u|: (%.2e, %.2e, %.2e) m s⁻¹, extrema(T): (%.2f, %.2f) ᵒC, wall time: %s \n",
Expand All @@ -90,6 +127,6 @@ function progress(sim)
return nothing
end

add_callback!(simulation, progress, IterationInterval(1))
add_callback!(simulation, progress, IterationInterval(10))

run!(simulation)
136 changes: 75 additions & 61 deletions src/DataWrangling/ECCO/ECCO.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ using Oceananigans.Utils

using KernelAbstractions: @kernel, @index
using NCDatasets
using JLD2
using Downloads: download
using Dates
using Adapt
using Scratch

download_ECCO_cache::String = ""

function __init__()
global download_ECCO_cache = @get_scratch!("ECCO")
end
Expand Down Expand Up @@ -123,43 +125,68 @@ end
"""
ECCO_field(metadata::ECCOMetadata;
architecture = CPU(),
horizontal_halo = (3, 3))

Retrieve the ecco field corresponding to `metadata`.
The data is loaded from `filename` on `architecture` with `horizontal_halo`
in the x and y direction. The halo in the z-direction is one.
inpainting = nothing,
mask = nothing,
horizontal_halo = (7, 7),
cache_inpainted_data = false)

Return a `Field` on `architecture` described by `ECCOMetadata` with
`horizontal_halo` size.
If not `nothing`, the `inpainting` method is used to fill the cells
within the specified `mask`. `mask` is set to `ECCO_mask` for non-nothing
`inpainting`.
"""
function ECCO_field(metadata::ECCOMetadata;
architecture = CPU(),
horizontal_halo = (7, 7))
inpainting = nothing,
mask = nothing,
horizontal_halo = (7, 7),
cache_inpainted_data = false)

# Respect user-supplied mask, but otherwise build default ECCO mask.
if !isnothing(inpainting) && isnothing(mask)
mask = ECCO_mask(metadata, architecture)
end

field = empty_ECCO_field(metadata; architecture, horizontal_halo)
inpainted_path = inpainted_metadata_path(metadata)

if !isnothing(inpainting) && isfile(inpainted_path)
file = jldopen(inpainted_path, "r")
maxiter = file["inpainting_maxiter"]

# read data if generated with the same inpainting
if maxiter == inpainting.maxiter
data = file["data"]
close(file)
copyto!(parent(field), data)
return field
end
end

download_dataset(metadata)
path = metadata_path(metadata)
ds = Dataset(path)

shortname = short_name(metadata)

if variable_is_three_dimensional(metadata)
data = ds[shortname][:, :, :, 1]

# The surface layer in three-dimensional ECCO fields is at `k = 1`
data = reverse(data, dims=3)
else
data = ds[shortname][:, :, 1]
end

close(ds)

field = empty_ECCO_field(metadata; architecture, horizontal_halo)

# Convert data from Union(FT, missing} to FT
FT = eltype(field)
data[ismissing.(data)] .= 1e10 # Artificially large number!
data = if location(field)[2] == Face
data = if location(field)[2] == Face # ?
new_data = zeros(FT, size(field))
new_data[:, 1:end-1, :] .= data
new_data
else
convert.(FT, data)
data = Array{FT}(data)
end

# ECCO4 data is on a -180, 180 longitude grid as opposed to ECCO2 data that
Expand All @@ -173,71 +200,58 @@ function ECCO_field(metadata::ECCOMetadata;
set!(field, data)
fill_halo_regions!(field)

if !isnothing(inpainting)
# Make sure all values are extended properly
name = string(metadata.name)
date = string(metadata.dates)
version = summary(metadata.version)
@info string("Inpainting ", version, " ", name, " data from ", date, "...")
start_time = time_ns()

inpaint_mask!(field, mask; inpainting)
fill_halo_regions!(field)

elapsed = 1e-9 * (time_ns() - start_time)
@info string(" ... (", prettytime(elapsed), ")")

# We cache the inpainted data to avoid recomputing it
@root if cache_inpainted_data
file = jldopen(inpainted_path, "w+")
file["data"] = on_architecture(CPU(), parent(field))
file["inpainting_maxiter"] = inpainting.maxiter
close(file)
end
end

return field
end

# Fallback
ECCO_field(var_name::Symbol; kw...) = ECCO_field(ECCOMetadata(var_name); kw...)

"""
inpainted_ECCO_field(metadata::ECCOMetadata;
architecture = CPU(),
mask = ECCO_mask(metadata, architecture),
inpainting = NearestNeighborInpainting(Inf),
kw...)

Retrieve the ECCO field corresponding to `metadata` inpainted to fill all the missing
values in the original dataset.

Arguments
=========

- `metadata`: the metadata corresponding to the dataset.

Keyword Arguments
=================

- `architecture`: either `CPU()` or `GPU()`.
- `mask`: the mask used to inpaint the field, see [`inpaint_mask!`](@ref).
- `inpainting`: the inpainting algorithm, see [`inpaint_mask!`](@ref). Default: `NearestNeighborInpainting(Inf)`.
"""
function inpainted_ECCO_field(metadata::ECCOMetadata;
architecture = CPU(),
mask = ECCO_mask(metadata, architecture),
inpainting = NearestNeighborInpainting(Inf),
kw...)

# Make sure all values are extended properly
name = string(metadata.name)
date = string(metadata.dates)
version = summary(metadata.version)
@info string("Inpainting ", version, " ", name, " data from ", date, "...")
start_time = time_ns()

f = ECCO_field(metadata; architecture, kw...)
inpaint_mask!(f, mask; inpainting)
fill_halo_regions!(f)
elapsed = 1e-9 * (time_ns() - start_time)
@info string(" ... (", prettytime(elapsed), ")")
return f
function inpainted_metadata_filename(metadata::ECCOMetadata)
original_filename = metadata_filename(metadata)
without_extension = original_filename[1:end-3]
return without_extension * "_inpainted.jld2"
end

inpainted_ECCO_field(variable_name::Symbol; kw...) = inpainted_ECCO_field(ECCOMetadata(variable_name); kw...)
function set!(field::Field, ecco_metadata::ECCOMetadata; kw...)
inpainted_metadata_path(metadata::ECCOMetadata) = joinpath(metadata.dir, inpainted_metadata_filename(metadata))

function set!(field::Field, ECCO_metadata::ECCOMetadata; kw...)

# Fields initialized from ECCO
grid = field.grid
arch = child_architecture(grid)
mask = ECCO_mask(ecco_metadata, arch)
mask = ECCO_mask(ECCO_metadata, arch)

f = inpainted_ECCO_field(ecco_metadata; mask,
architecture = arch,
kw...)
f = ECCO_field(ECCO_metadata; mask,
architecture = arch,
kw...)

interpolate!(field, f)

return field
end

end # Module

2 changes: 1 addition & 1 deletion src/DataWrangling/ECCO/ECCO_metadata.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Metadata information for an ECCO dataset:
- `dir`: The directory where the dataset is stored.
"""
struct ECCOMetadata{D, V}
name :: Symbol
name :: Symbol
dates :: D
version :: V
dir :: String
Expand Down
Loading
Loading