Skip to content

New interface for ECCORestoring #185

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 44 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
2a9b254
start changing ecco restoring
simone-silvestri Sep 19, 2024
3c7c0ce
this should work?
simone-silvestri Sep 20, 2024
82fd2d5
new interface
simone-silvestri Sep 20, 2024
fc33335
fix docstring
simone-silvestri Sep 20, 2024
d2f51d1
simplify
simone-silvestri Sep 20, 2024
bc8c226
adding a maxiter
simone-silvestri Sep 20, 2024
c9036ec
make it work
simone-silvestri Sep 20, 2024
8941604
remove manifest
simone-silvestri Sep 20, 2024
9f16b60
adding maxiter
simone-silvestri Sep 20, 2024
a94f083
some changes
simone-silvestri Sep 20, 2024
e53a807
add range checks
simone-silvestri Sep 20, 2024
7597b1b
fix docstring
simone-silvestri Sep 20, 2024
b5d1661
inpainting_iterations?
simone-silvestri Sep 20, 2024
f6f40ad
better docstring
simone-silvestri Sep 20, 2024
58517cb
better defaults
simone-silvestri Sep 20, 2024
beb0836
correct docstring
simone-silvestri Sep 20, 2024
764ad91
adding a show method
simone-silvestri Sep 20, 2024
ee03ff3
fixed tests
simone-silvestri Sep 23, 2024
f82292e
more fixes
simone-silvestri Sep 24, 2024
a474956
fix ranges check
simone-silvestri Sep 24, 2024
78012d0
fix ecco tests
simone-silvestri Sep 24, 2024
32fd953
fix ecco tests
simone-silvestri Oct 7, 2024
bda2828
correct the precompile runtime
simone-silvestri Oct 9, 2024
fc82bf8
remove concurrency
simone-silvestri Oct 21, 2024
e1c05a2
Merge branch 'main' into ss/ecco-restoring-interface
simone-silvestri Oct 21, 2024
cf5a861
bugfix
simone-silvestri Oct 24, 2024
27bf157
Merge branch 'main' into ss/ecco-restoring-interface
simone-silvestri Oct 24, 2024
1bbfd7b
flux form
simone-silvestri Oct 24, 2024
a3e4807
Merge branch 'main' into ss/ecco-restoring-interface
simone-silvestri Oct 28, 2024
df7bed2
NearestNeighborInpainting
simone-silvestri Oct 28, 2024
701c3d1
some more comments plus fix tests
simone-silvestri Oct 28, 2024
644370b
check inpainting
simone-silvestri Oct 28, 2024
9346a64
remove space
simone-silvestri Oct 28, 2024
defa077
bugfix
simone-silvestri Oct 28, 2024
819142c
bugfix
simone-silvestri Oct 28, 2024
2b5af9a
fix a couple of bugs
simone-silvestri Oct 29, 2024
0c407dc
fix tests
simone-silvestri Oct 29, 2024
3bb195c
corrected tests
simone-silvestri Oct 29, 2024
862d77a
setting
simone-silvestri Oct 29, 2024
7bd6559
bugfix
simone-silvestri Oct 30, 2024
5ba0558
Merge branch 'main' into ss/ecco-restoring-interface
simone-silvestri Nov 1, 2024
f24e672
Merge branch 'main' into ss/ecco-restoring-interface
simone-silvestri Nov 4, 2024
1dc6f88
try setting the runtime version
simone-silvestri Nov 5, 2024
72cfb20
whoops
simone-silvestri Nov 5, 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/Manifest.toml

# Files generated by invoking Julia with --code-coverage
*.jl.cov
*.jl.*.cov
Expand Down
6 changes: 4 additions & 2 deletions src/ClimaOcean.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ export
SimilarityTheoryTurbulentFluxes,
JRA55_prescribed_atmosphere,
JRA55NetCDFBackend,
ECCOMetadata,
regrid_bathymetry,
retrieve_bathymetry,
stretched_vertical_faces,
exponential_z_faces,
PowerLawStretching, LinearStretching,
exponential_z_faces,
JRA55_field_time_series,
ECCO_field, ECCOMetadata,
ECCO_field,
ECCOMetadata,
ECCORestoring,
LatitudinallyTaperedPolarMask,
ocean_simulation,
initialize!

Expand Down
2 changes: 1 addition & 1 deletion src/DataWrangling/ECCO/ECCO.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module ECCO

export ECCOMetadata, ECCO_field, ECCO_mask, adjusted_ECCO_tracers, initialize!
export ECCO2Monthly, ECCO4Monthly, ECCO2Daily
export ECCO_restoring_forcing
export ECCORestoring, LatitudinallyTaperedPolarMask

using ClimaOcean.DataWrangling: inpaint_mask!
using ClimaOcean.InitialConditions: three_dimensional_regrid!, interpolate!
Expand Down
46 changes: 46 additions & 0 deletions src/DataWrangling/ECCO/ECCO_mask.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Oceananigans.Architectures: AbstractArchitecture
import ClimaOcean: stateindex

"""
ECCO_mask(architecture = CPU(); minimum_value = Float32(-1e5))
Expand Down Expand Up @@ -40,3 +41,48 @@ end
@inbounds mask[i, j, k] = (Tᵢ[i, j, k] == 0)
end

struct LatitudinallyTaperedPolarMask{N, S, Z}
northern_edges :: N
southern_edges :: S
z_edges :: Z
end

"""
LatitudinallyTaperedPolarMask(; northern_edges = (70, 75),
southern_edges = (-75, -70),
z_edges = (-20, 0))

Build a mask that is linearly tapered in latitude inbetween the northern and southern edges.
The mask is constant in depth between the z_edges and is equal to zero everywhere else.
The mask has the following functional form:

```julia
n = 1 / (northern_edges[2] - northern_edges[1]) * (φ - northern_edges[1])
s = 1 / (southern_edges[1] - southern_edges[2]) * (φ - southern_edges[2])

within_depth = (z_edges[1] < z < z_edges[2])

mask = within_depth ? max(n, s, 0) : 0
```
"""
function LatitudinallyTaperedPolarMask(; northern_edges = (70, 75),
southern_edges = (-75, -70),
z_edges = (-20, 0))

return LatitudinallyTaperedPolarMask(northern_edges, southern_edges, z_edges)
end

@inline function (mask::LatitudinallyTaperedPolarMask)(φ, z)
n = 1 / (mask.northern_edges[2] - mask.northern_edges[1]) * (φ - mask.northern_edges[1])
s = 1 / (mask.southern_edges[1] - mask.southern_edges[2]) * (φ - mask.southern_edges[2])

within_depth = (mask.z_edges[1] < z < mask.z_edges[2])

return ifelse(within_depth, max(n, s, zero(n)), zero(n))
end

@inline function stateindex(mask::LatitudinallyTaperedPolarMask, i, j, k, grid, time, loc)
LX, LY, LZ = loc
λ, φ, z = node(i, j, k, grid, LX(), LY(), LZ())
return mask(φ, z)
end
106 changes: 61 additions & 45 deletions src/DataWrangling/ECCO/ECCO_restoring.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ using Dates: Second
using ClimaOcean: stateindex

import Oceananigans.Fields: set!
import Oceananigans.Forcings: regularize_forcing
import Oceananigans.OutputReaders: new_backend, update_field_time_series!

@inline instantiate(T::DataType) = T()
Expand All @@ -20,43 +21,41 @@ import Oceananigans.OutputReaders: new_backend, update_field_time_series!
struct ECCONetCDFBackend{N} <: AbstractInMemoryBackend{Int}
start :: Int
length :: Int
maxiter :: Int
Copy link
Member

Choose a reason for hiding this comment

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

should we make it more clear this refers to inpainting?

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 have changed it to inpainting_maxiter. I am not super happy about it though. Do you have a suggestion for a name for this variable?

Copy link
Member

Choose a reason for hiding this comment

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

How about making a new struct, Inpainting, which has a property maxiter (and which may be generalized to include more settings in the future)


ECCONetCDFBackend{N}(start::Int, length::Int) where N = new{N}(start, length)
ECCONetCDFBackend{N}(start::Int, length::Int, maxiter::Int) where N = new{N}(start, length, maxiter)
end

"""
ECCONetCDFBackend(length)
ECCONetCDFBackend(length; on_native_grid = false, maxiter = Inf)

Represents an ECCO FieldTimeSeries backed by ECCO native .nc files.
Each time instance is stored in an individual file.
the maxiter keyword argument is the maximum number of iterations for the inpainting algorithm.
"""
ECCONetCDFBackend(length; on_native_grid = false) = ECCONetCDFBackend{on_native_grid}(1, length)
ECCONetCDFBackend(length; on_native_grid = false, maxiter = Inf) = ECCONetCDFBackend{on_native_grid}(1, length, maxiter)

Base.length(backend::ECCONetCDFBackend) = backend.length
Base.summary(backend::ECCONetCDFBackend) = string("ECCONetCDFBackend(", backend.start, ", ", backend.length, ")")

const ECCONetCDFFTS{N} = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:ECCONetCDFBackend{N}} where N

new_backend(::ECCONetCDFBackend{N}, start, length) where N = ECCONetCDFBackend{N}(start, length)
new_backend(b::ECCONetCDFBackend{N}, start, length) where N = ECCONetCDFBackend{N}(start, length, b.maxiter)
on_native_grid(::ECCONetCDFBackend{N}) where N = N

function set!(fts::ECCONetCDFFTS, path::ECCOMetadata=fts.path, name::String=fts.name)

backend = fts.backend
start = backend.start
start = backend.start
maxiter = backend.maxiter

for t in start:start+length(backend)-1

# find the file associated with the time index
metadata = @inbounds path[t]

arch = architecture(fts)
f = inpainted_ECCO_field(metadata; architecture = arch)
if on_native_grid(backend)
set!(fts[t], f)
else
interpolate!(fts[t], f)
end
# Set the field with the correct metadata
set!(fts[t], metadata; maxiter)
end

fill_halo_regions!(fts)
Expand Down Expand Up @@ -94,6 +93,7 @@ end
architecture = CPU(),
time_indices_in_memory = 2,
time_indexing = Cyclical(),
maxiter = Inf,
grid = nothing)

Create a field time series object for ECCO data.
Expand All @@ -105,17 +105,20 @@ Create a field time series object for ECCO data.
- architecture: The architecture to use for computations (default: CPU()).
- time_indices_in_memory: The number of time indices to keep in memory (default: 2).
- time_indexing: The time indexing scheme to use (default: Cyclical()).
- maxiter: The maximum number of iterations for the inpainting algorithm (default: Inf).
- grid: if not a `nothing`, the ECCO data is directly interpolated on the `grid`,
"""
function ECCO_field_time_series(metadata::ECCOMetadata;
architecture = CPU(),
time_indices_in_memory = 2,
time_indexing = Cyclical(),
maxiter = Inf,
grid = nothing)

# ECCO data is too chunky to allow other backends
backend = ECCONetCDFBackend(time_indices_in_memory;
on_native_grid = isnothing(grid))
on_native_grid = isnothing(grid),
maxiter)

# Making sure all the required individual files are downloaded
download_dataset!(metadata)
Expand Down Expand Up @@ -173,22 +176,22 @@ A struct representing ECCO restoring.
- `ECCO_grid`: The native ECCO grid to interpolate from.
- `mask`: A mask (could be a number, an array, a function or a field).
- `variable_name`: The variable name of the variable that needs restoring.
- `λ⁻¹`: The reciprocal of the restoring timescale.
- `rate`: The reciprocal of the restoring timescale.
"""
struct ECCORestoring{FTS, G, M, V, N} <: Function
struct ECCORestoring{FTS, G, M, V, N}
ECCO_fts :: FTS
ECCO_grid :: G
mask :: M
variable_name :: V
λ⁻¹ :: N
rate :: N
end

Adapt.adapt_structure(to, p::ECCORestoring) =
ECCORestoring(Adapt.adapt(to, p.ECCO_fts),
Adapt.adapt(to, p.ECCO_grid),
Adapt.adapt(to, p.mask),
Adapt.adapt(to, p.variable_name),
Adapt.adapt(to, p.λ⁻¹))
Adapt.adapt(to, p.rate))

@inline function (p::ECCORestoring)(i, j, k, grid, clock, fields)

Expand All @@ -206,8 +209,8 @@ Adapt.adapt_structure(to, p::ECCORestoring) =

# Extracting the mask value at the current node
mask = stateindex(p.mask, i, j, k, grid, clock.time, loc)

return p.λ⁻¹ * mask * (ECCO_var - var)
return p.rate * mask * (ECCO_var - var)
end

# Differentiating between restoring done with an ECCO FTS
Expand All @@ -231,52 +234,65 @@ end
@inline get_ECCO_variable(::Val{false}, ECCO_fts, i, j, k, ECCO_grid, grid, time) = @inbounds ECCO_fts[i, j, k, time]

"""
ECCO_restoring_forcing(metadata::ECCOMetadata;
architecture = CPU(),
backend = ECCONetCDFBackend(2),
time_indexing = Cyclical(),
mask = 1,
timescale = 5days)
ECCORestoring(variable_name::Symbol, [architecture = CPU()];
version = ECCO4Monthly(),
dates = all_ECCO_dates(version),
time_indices_in_memory = 2, # Not more than this if we want to use GPU!
time_indexing = Cyclical(),
mask = 1,
rate = 1 / 20days,
grid = nothing,
maxiter = Inf)

Create a restoring forcing term that restores to values stored in an ECCO field time series.

# Arguments:
=============
- `metadata`: The metadata for the ECCO field time series.
- `variable_name`: The name of the variable to restore. The choice is between
:temperature, :salinity, :u_velocity, :v_velocity, :sea_ice_thickness, :sea_ice_area_fraction.
- `architecture`: The architecture. Typically `CPU` or `GPU`. Default is `CPU`.

# Keyword Arguments:
====================
- `architecture`: The architecture. Typically `CPU` or `GPU`
- `version`: The version of the ECCO dataset. Default is `ECCO4Monthly()`.
- `dates`: The dates to use for the ECCO dataset. Default is `all_ECCO_dates(version)`.
- `time_indices_in_memory`: The number of time indices to keep in memory. trade-off between performance
and memory footprint.
- `time_indexing`: The time indexing scheme for the field time series, see [`FieldTimeSeries`](@ref)
- `time_indexing`: The time indexing scheme for the field time series
- `mask`: The mask value. Can be a function of `(x, y, z, time)`, an array or a number
- `timescale`: The restoring timescale.
- `rate`: The restoring rate in s⁻¹.
- `time_indices_in_memory = 2, # Not more than this if we want to use GPU!
- `maxiter`: maximum number of iterations for the inpainting algorithm. (defaults to `Inf`)

It is possible to also pass an `ECCOMetadata` type as the first argument without the need for the
`variable_name` argument and the `version` and `dates` keyword arguments.
"""
function ECCO_restoring_forcing(variable_name::Symbol, version=ECCO4Monthly(); kw...)
metadata = ECCOMetadata(variable_name, all_ECCO_dates(version), version)
return ECCO_restoring_forcing(metadata; kw...)
function ECCORestoring(variable_name::Symbol, architecture = CPU();
version = ECCO4Monthly(),
dates = all_ECCO_dates(version),
kw...)

metadata = ECCOMetadata(variable_name, dates, version)
return ECCORestoring(metadata; architecture, kw...)
end

function ECCO_restoring_forcing(metadata::ECCOMetadata;
architecture = CPU(),
time_indices_in_memory = 2, # Not more than this if we want to use GPU!
time_indexing = Cyclical(),
mask = 1,
timescale = 20days,
grid = nothing)
function ECCORestoring(metadata::ECCOMetadata;
architecture = CPU(),
time_indices_in_memory = 2, # Not more than this if we want to use GPU!
Copy link
Member

Choose a reason for hiding this comment

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

should this be backend?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

At the moment, the only supported backend is the ECCOBackendNetCDF. If we want to be able to preprocess data we can change this to accept a backend and support different backends.

time_indexing = Cyclical(),
mask = 1,
rate = 1 / 20days,
grid = nothing.
maxiter = Inf)

ECCO_fts = ECCO_field_time_series(metadata; grid, architecture, time_indices_in_memory, time_indexing)
ECCO_fts = ECCO_field_time_series(metadata; grid, architecture, time_indices_in_memory, time_indexing, maxiter)
ECCO_grid = ECCO_fts.grid

# Grab the correct Oceananigans field to restore
variable_name = metadata.name
field_name = oceananigans_fieldname[variable_name]
ECCO_restoring = ECCORestoring(ECCO_fts, ECCO_grid, mask, field_name, 1 / timescale)

# Defining the forcing that depends on the restoring field.
restoring_forcing = Forcing(ECCO_restoring; discrete_form = true)

return restoring_forcing
return ECCORestoring(ECCO_fts, ECCO_grid, mask, field_name, rate)
end

regularize_forcing(forcing::ECCORestoring, field, field_name, model_field_names) = forcing
Loading
Loading