Skip to content

Commit a193aee

Browse files
glwagnernavidcy
andauthored
(0.3.0) Cleaner syntax for ECCOFieldTimeSeries and ECCORestoring (#284)
* Cleaner syntax for ECCOFieldTimeSeries * Fix indenting * Make grid positional argument * Eliminate architecture kwarg * Clean up ECCORestoring * More simplification * fix typo * fix typo * Bugfix * Fix test syntax * Bump project * Dont fail if file doesnt exist? * Fix ecco tests --------- Co-authored-by: Navid C. Constantinou <[email protected]>
1 parent 0c91ba6 commit a193aee

File tree

6 files changed

+101
-119
lines changed

6 files changed

+101
-119
lines changed

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name = "ClimaOcean"
22
uuid = "0376089a-ecfe-4b0e-a64f-9c555d74d754"
33
license = "MIT"
44
authors = ["Climate Modeling Alliance and contributors"]
5-
version = "0.2.5"
5+
version = "0.3.0"
66

77
[deps]
88
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"

src/DataWrangling/ECCO/ECCO_restoring.jl

+87-104
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using Oceananigans: location
2-
using Oceananigans.Grids: node, on_architecture
2+
using Oceananigans.Grids: AbstractGrid, node, on_architecture
33
using Oceananigans.Fields: interpolate!, interpolate, location, instantiated_location
44
using Oceananigans.OutputReaders: Cyclical, TotallyInMemory, AbstractInMemoryBackend, FlavorOfFTS, time_indices
55
using Oceananigans.Utils: Time
@@ -35,7 +35,7 @@ end
3535
Adapt.adapt_structure(to, b::ECCONetCDFBackend{N, C}) where {N, C} = ECCONetCDFBackend{N, C}(b.start, b.length, nothing, nothing)
3636

3737
"""
38-
ECCONetCDFBackend(length; on_native_grid = false, inpainting = NearestNeighborInpainting(Inf))
38+
ECCONetCDFBackend(length; on_native_grid=false, inpainting=NearestNeighborInpainting(Inf))
3939
4040
Represent an ECCO FieldTimeSeries backed by ECCO native netCDF files.
4141
Each time instance is stored in an individual file.
@@ -51,15 +51,15 @@ end
5151
Base.length(backend::ECCONetCDFBackend) = backend.length
5252
Base.summary(backend::ECCONetCDFBackend) = string("ECCONetCDFBackend(", backend.start, ", ", backend.length, ")")
5353

54-
const ECCONetCDFFTS{N} = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:ECCONetCDFBackend{N}} where N
54+
const ECCOFieldTimeSeries{N} = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:ECCONetCDFBackend{N}} where N
5555

5656
new_backend(b::ECCONetCDFBackend{native, cache_data}, start, length) where {native, cache_data} =
5757
ECCONetCDFBackend{native, cache_data}(start, length, b.inpainting, b.metadata)
5858

5959
on_native_grid(::ECCONetCDFBackend{native}) where native = native
6060
cache_inpainted_data(::ECCONetCDFBackend{native, cache_data}) where {native, cache_data} = cache_data
6161

62-
function set!(fts::ECCONetCDFFTS)
62+
function set!(fts::ECCOFieldTimeSeries)
6363
backend = fts.backend
6464
inpainting = backend.inpainting
6565
cache_data = cache_inpainted_data(backend)
@@ -103,12 +103,10 @@ function ECCO_times(metadata; start_time = first(metadata).dates)
103103
end
104104

105105
"""
106-
ECCO_field_time_series(metadata::ECCOMetadata;
107-
grid = nothing,
108-
architecture = isnothing(grid) ? CPU() : architecture(grid),
109-
time_indices_in_memory = 2,
110-
time_indexing = Cyclical(),
111-
inpainting_iterations = prod(size(metadata)),
106+
ECCOFieldTimeSeries(metadata::ECCOMetadata [, arch_or_grid=CPU() ];
107+
time_indices_in_memory = 2,
108+
time_indexing = Cyclical(),
109+
inpainting = nothing)
112110
113111
Create a field time series object for ECCO data.
114112
@@ -117,13 +115,12 @@ Arguments
117115
118116
- `metadata`: `ECCOMetadata` containing information about the ECCO dataset.
119117
118+
- `arch_or_grid`: Either a grid to interpolate ECCO data to, or an `arch`itecture
119+
to use for the native ECCO grid. Default: CPU().
120+
120121
Keyword Arguments
121122
=================
122123
123-
- `grid`: where ECCO data is interpolated. If `nothing`, the native `ECCO` grid is used.
124-
125-
- `architecture`: where data is stored. Should only be set if `isnothing(grid)`.
126-
127124
- `time_indices_in_memory`: The number of time indices to keep in memory. Default: 2.
128125
129126
- `time_indexing`: The time indexing scheme to use. Default: `Cyclical()`.
@@ -136,49 +133,47 @@ Keyword Arguments
136133
Default: `true`.
137134
138135
"""
139-
function ECCO_field_time_series(metadata::ECCOMetadata;
140-
architecture = CPU(),
141-
time_indices_in_memory = 2,
142-
time_indexing = Cyclical(),
143-
inpainting = NearestNeighborInpainting(prod(size(metadata))),
144-
cache_inpainted_data = true,
145-
grid = nothing)
136+
function ECCOFieldTimeSeries(metadata::ECCOMetadata, architecture::AbstractArchitecture=CPU(); kw...)
137+
download_dataset(metadata)
138+
ftmp = empty_ECCO_field(first(metadata); architecture)
139+
grid = ftmp.grid
140+
return ECCOFieldTimeSeries(metadata, grid; kw...)
141+
end
142+
143+
function ECCOFieldTimeSeries(metadata::ECCOMetadata, grid::AbstractGrid;
144+
time_indices_in_memory = 2,
145+
time_indexing = Cyclical(),
146+
inpainting = nothing,
147+
cache_inpainted_data = true)
146148

147149
# Make sure all the required individual files are downloaded
148150
download_dataset(metadata)
149151

150152
inpainting isa Int && (inpainting = NearestNeighborInpainting(inpainting))
151-
152-
ftmp = empty_ECCO_field(first(metadata); architecture)
153-
on_native_grid = isnothing(grid)
154-
on_native_grid && (grid = ftmp.grid)
153+
backend = ECCONetCDFBackend(time_indices_in_memory, metadata; on_native_grid, inpainting, cache_inpainted_data)
155154

156155
times = ECCO_times(metadata)
157156
loc = LX, LY, LZ = location(metadata)
158157
boundary_conditions = FieldBoundaryConditions(grid, loc)
159-
160-
backend = ECCONetCDFBackend(time_indices_in_memory, metadata; on_native_grid, inpainting, cache_inpainted_data)
161-
162158
fts = FieldTimeSeries{LX, LY, LZ}(grid, times; backend, time_indexing, boundary_conditions)
163159
set!(fts)
164160

165161
return fts
166162
end
167163

168-
ECCO_field_time_series(variable_name::Symbol, version=ECCO4Monthly(); kw...) =
169-
ECCO_field_time_series(ECCOMetadata(variable_name, all_ECCO_dates(version), version); kw...)
164+
ECCOFieldTimeSeries(variable_name::Symbol, version=ECCO4Monthly(); kw...) =
165+
ECCOFieldTimeSeries(ECCOMetadata(variable_name, all_ECCO_dates(version), version); kw...)
170166

171167
# Variable names for restoreable data
172168
struct Temperature end
173169
struct Salinity end
174170
struct UVelocity end
175171
struct VVelocity end
176172

177-
oceananigans_fieldname = Dict(
178-
:temperature => Temperature(),
179-
:salinity => Salinity(),
180-
:u_velocity => UVelocity(),
181-
:v_velocity => VVelocity())
173+
const oceananigans_fieldnames = Dict(:temperature => Temperature(),
174+
:salinity => Salinity(),
175+
:u_velocity => UVelocity(),
176+
:v_velocity => VVelocity())
182177

183178
@inline Base.getindex(fields, i, j, k, ::Temperature) = @inbounds fields.T[i, j, k]
184179
@inline Base.getindex(fields, i, j, k, ::Salinity) = @inbounds fields.S[i, j, k]
@@ -192,14 +187,14 @@ Base.summary(::VVelocity) = "v_velocity"
192187

193188
struct ECCORestoring{FTS, G, M, V, N}
194189
field_time_series :: FTS
195-
grid :: G
190+
native_grid :: G
196191
mask :: M
197192
variable_name :: V
198193
rate :: N
199194
end
200195

201196
Adapt.adapt_structure(to, p::ECCORestoring) = ECCORestoring(Adapt.adapt(to, p.field_time_series),
202-
Adapt.adapt(to, p.grid),
197+
Adapt.adapt(to, p.native_grid),
203198
Adapt.adapt(to, p.mask),
204199
Adapt.adapt(to, p.variable_name),
205200
Adapt.adapt(to, p.rate))
@@ -212,20 +207,20 @@ Adapt.adapt_structure(to, p::ECCORestoring) = ECCORestoring(Adapt.adapt(to, p.fi
212207

213208
# Possibly interpolate ECCO data from the ECCO grid to simulation grid.
214209
# Otherwise, simply extract the pre-interpolated data from p.field_time_series.
215-
backend = p.field_time_series.backend
216-
interpolating = on_native_grid(backend)
217-
ψ_ecco = maybe_interpolate(Val(interpolating), p.field_time_series, i, j, k, p.grid, grid, time)
210+
if p.native_grid isa Nothing
211+
ψ_ecco = @inbounds p.field_time_series[i, j, k, time]
212+
else
213+
ψ_ecco = interpolate_to_grid(p.field_time_series, i, j, k, p.native_grid, grid, time)
214+
end
218215

219216
ψ = @inbounds fields[i, j, k, p.variable_name]
220217
μ = stateindex(p.mask, i, j, k, grid, clock.time, loc)
221-
ω = p.rate
218+
r = p.rate
222219

223-
return ω * μ * (ψ_ecco - ψ)
220+
return r * μ * (ψ_ecco - ψ)
224221
end
225222

226-
@inline maybe_interpolate(::Val{false}, fts, i, j, k, native_grid, grid, time) = @inbounds fts[i, j, k, time]
227-
228-
@inline function maybe_interpolate(::Val{true}, fts, i, j, k, native_grid, grid, time)
223+
@inline function interpolate_to_grid(fts, i, j, k, native_grid, grid, time)
229224
times = fts.times
230225
data = fts.data
231226
time_indexing = fts.time_indexing
@@ -238,40 +233,47 @@ end
238233
end
239234

240235
"""
241-
ECCORestoring([arch=CPU(),]
242-
variable_name::Symbol;
243-
version=ECCO4Monthly(),
244-
dates=all_ECCO_dates(version),
245-
dates = all_ECCO_dates(version),
236+
ECCORestoring(variable_name::Symbol, [ arch_or_grid = CPU(), ];
237+
version = ECCO4Monthly(),
238+
dates = all_ECCO_dates(version),
246239
time_indices_in_memory = 2,
247240
time_indexing = Cyclical(),
248241
mask = 1,
249242
rate = 1,
250-
grid = nothing,
251-
inpainting = NearestNeighborInpainting(prod(size(metadata))),
243+
inpainting = NearestNeighborInpainting(Inf),
252244
cache_inpainted_data = true)
253245
254-
Create a forcing term that restores to values stored in an ECCO field time series.
246+
Build a forcing term that restores to values stored in an ECCO field time series.
255247
The restoring is applied as a forcing on the right hand side of the evolution equations calculated as
256248
257-
```julia
258-
F = mask ⋅ rate ⋅ (ECCO_variable - simulation_variable[i, j, k])
249+
```math
250+
= r μ (ψ_{ECCO} - ψ)
259251
```
260-
where `ECCO_variable` is linearly interpolated in space and time from the ECCO dataset of choice to the
261-
simulation grid and time.
252+
253+
where ``μ`` is the mask, ``r`` is the restoring rate, ``ψ`` is the simulation variable,
254+
and the ECCO variable ``ψ_ECCO`` is linearly interpolated in space and time from the
255+
ECCO dataset of choice to the simulation grid and time.
262256
263257
Arguments
264258
=========
265259
266-
- `arch`: The architecture. Typically `CPU()` or `GPU()`. Default: `CPU()`.
267-
268260
- `variable_name`: The name of the variable to restore. Choices include:
269-
* `:temperature`,
270-
* `:salinity`,
271-
* `:u_velocity`,
272-
* `:v_velocity`,
273-
* `:sea_ice_thickness`,
274-
* `:sea_ice_area_fraction`.
261+
* `:temperature`,
262+
* `:salinity`,
263+
* `:u_velocity`,
264+
* `:v_velocity`,
265+
* `:sea_ice_thickness`,
266+
* `:sea_ice_area_fraction`.
267+
268+
Note that `ECCOMetadata` may be provided as the first argument instead
269+
of `variable_name`. In this case the `version` and `dates` kwargs (described below)
270+
cannot be provided.
271+
272+
- `arch_or_grid`: Either the architecture of the simulation, or a grid on which the ECCO data
273+
is pre-interpolated when loaded. If an `arch`itecture is provided, such as
274+
`arch_or_grid = CPU()` or `arch_or_grid = GPU()`, ECCO data
275+
will be interpolated on-the-fly when the forcing tendency is computed.
276+
Default: CPU().
275277
276278
Keyword Arguments
277279
=================
@@ -280,77 +282,58 @@ Keyword Arguments
280282
281283
- `dates`: The dates to use for the ECCO dataset. Default: `all_ECCO_dates(version)`.
282284
283-
- `time_indices_in_memory`: The number of time indices to keep in memory; trade-off between performance
284-
and memory footprint.
285+
- `time_indices_in_memory`: The number of time indices to keep in memory. The number is chosen based on
286+
a trade-off between increased performance (more indices in memory) and reduced
287+
memory footprint (fewer indices in memory). Default: 2.
285288
286-
- `time_indexing`: The time indexing scheme for the field time series
289+
- `time_indexing`: The time indexing scheme for the field time series.
287290
288291
- `mask`: The mask value. Can be a function of `(x, y, z, time)`, an array, or a number.
289292
290293
- `rate`: The restoring rate, i.e., the inverse of the restoring timescale (in s⁻¹).
291294
292-
- `time_indices_in_memory:` how many time instances are loaded in memory; the remaining are loaded lazily.
293-
294295
- `inpainting`: inpainting algorithm, see [`inpaint_mask!`](@ref). Default: `NearestNeighborInpainting(Inf)`.
295296
296-
- `grid`: If `isnothing(grid)`, ECCO data is interpolated on-the-fly to the simulation grid.
297-
If `!isnothing(grid)`, ECCO data is pre-interpolated to `grid`.
298-
Default: nothing.
299-
300297
- `cache_inpainted_data`: If `true`, the data is cached to disk after inpainting for later retrieving.
301298
Default: `true`.
302-
303-
It is possible to also pass an `ECCOMetadata` type as the first argument without the need for the
304-
`variable_name` argument and the `version` and `dates` keyword arguments.
305299
"""
306-
function ECCORestoring(arch::AbstractArchitecture,
307-
variable_name::Symbol;
308-
version=ECCO4Monthly(),
309-
dates=all_ECCO_dates(version),
300+
function ECCORestoring(variable_name::Symbol,
301+
arch_or_grid = CPU();
302+
version = ECCO4Monthly(),
303+
dates = all_ECCO_dates(version),
310304
kw...)
311305

312306
metadata = ECCOMetadata(variable_name, dates, version)
313-
return ECCORestoring(arch, metadata; kw...)
307+
return ECCORestoring(metadata, arch_or_grid; kw...)
314308
end
315309

316-
function ECCORestoring(arch::AbstractArchitecture,
317-
metadata::ECCOMetadata;
310+
function ECCORestoring(metadata::ECCOMetadata,
311+
arch_or_grid = CPU();
318312
rate,
319313
mask = 1,
320-
grid = nothing,
321314
time_indices_in_memory = 2, # Not more than this if we want to use GPU!
322315
time_indexing = Cyclical(),
323316
inpainting = NearestNeighborInpainting(Inf),
324317
cache_inpainted_data = true)
325318

326-
# Validate architecture
327-
if !isnothing(grid) && architecture(grid) != arch
328-
throw(ArgumentError("The architecture of ECCORestoring must match the architecture of the grid."))
329-
end
330-
331-
fts = ECCO_field_time_series(metadata;
332-
grid,
333-
architecture = arch,
334-
time_indices_in_memory,
335-
time_indexing,
336-
inpainting,
337-
cache_inpainted_data)
319+
fts = ECCOFieldTimeSeries(metadata, arch_or_grid;
320+
time_indices_in_memory,
321+
time_indexing,
322+
inpainting,
323+
cache_inpainted_data)
338324

339325
# Grab the correct Oceananigans field to restore
340326
variable_name = metadata.name
341-
field_name = oceananigans_fieldname[variable_name]
327+
field_name = oceananigans_fieldnames[variable_name]
342328

343329
# If we pass the grid we do not need to interpolate
344330
# so we can save parameter space by setting the native grid to nothing
345-
native_grid = isnothing(grid) ? fts.grid : nothing
331+
on_native_grid = arch_or_grid isa AbstractArchitecture
332+
maybe_native_grid = on_native_grid ? fts.grid : nothing
346333

347-
return ECCORestoring(fts, native_grid, mask, field_name, rate)
334+
return ECCORestoring(fts, maybe_native_grid, mask, field_name, rate)
348335
end
349336

350-
# Make sure we can call ECCORestoring with architecture as the first positional argument
351-
ECCORestoring(variable_name::Symbol; kw...) = ECCORestoring(CPU(), variable_name; kw...)
352-
ECCORestoring(metadata::ECCOMetadata; kw...) = ECCORestoring(CPU(), metadata; kw...)
353-
354337
function Base.show(io::IO, p::ECCORestoring)
355338
print(io, "ECCORestoring:", '\n',
356339
"├── restored variable: ", summary(p.variable_name), '\n',

test/runtests_setup.jl

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ salinity_metadata = ECCOMetadata(:salinity, dates)
3030

3131
# Fictitious grid that triggers bathymetry download
3232
function download_bathymetry(; dir = download_bathymetry_cache,
33-
filename = "ETOPO_2022_v1_60s_N90W180_surface.nc")
33+
filename = "ETOPO_2022_v1_60s_N90W180_surface.nc")
3434

3535
grid = LatitudeLongitudeGrid(size = (10, 10, 1),
3636
longitude = (0, 100),
@@ -41,3 +41,4 @@ function download_bathymetry(; dir = download_bathymetry_cache,
4141

4242
return nothing
4343
end
44+

test/test_downloading.jl

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ using ClimaOcean.ECCO: metadata_path
55
@testset "Availability of JRA55 data" begin
66
@info "Testing that we can download all the JRA55 data..."
77
for name in ClimaOcean.DataWrangling.JRA55.JRA55_variable_names
8-
98
fts = ClimaOcean.JRA55.JRA55_field_time_series(name; time_indices=2:3)
109
end
1110
end
@@ -22,9 +21,10 @@ end
2221

2322
@testset "Availability of the Bathymetry" begin
2423
@info "Testing that we can download the bathymetry..."
25-
dir="./"
26-
filename="ETOPO_2022_v1_60s_N90W180_surface.nc"
27-
filepath=joinpath(dir, filename)
24+
dir = "./"
25+
filename = "ETOPO_2022_v1_60s_N90W180_surface.nc"
26+
filepath = joinpath(dir, filename)
2827
isfile(filepath) && rm(filepath; force=true)
2928
download_bathymetry(; dir, filename)
3029
end
30+

0 commit comments

Comments
 (0)