From ae5f0be40c70563c2a82eddb29d6c9702028013c Mon Sep 17 00:00:00 2001 From: Simone Silvestri <33547697+simone-silvestri@users.noreply.github.com> Date: Thu, 25 Jul 2024 17:28:39 -0400 Subject: [PATCH 001/104] start modifying the data structure --- src/DataWrangling/DataWrangling.jl | 5 +- src/DataWrangling/JRA55.jl | 102 +------ src/DataWrangling/ecco_metadata.jl | 72 ++--- src/DataWrangling/ecco_restoring.jl | 32 +-- src/DataWrangling/jra55_field_time_series.jl | 266 +++++++++++++++++++ src/DataWrangling/jra55_metadata.jl | 156 +++++++++++ src/DataWrangling/metadata.jl | 58 ++++ 7 files changed, 509 insertions(+), 182 deletions(-) create mode 100644 src/DataWrangling/jra55_field_time_series.jl create mode 100644 src/DataWrangling/jra55_metadata.jl create mode 100644 src/DataWrangling/metadata.jl diff --git a/src/DataWrangling/DataWrangling.jl b/src/DataWrangling/DataWrangling.jl index af9ce21c..728585a3 100644 --- a/src/DataWrangling/DataWrangling.jl +++ b/src/DataWrangling/DataWrangling.jl @@ -69,11 +69,12 @@ function save_field_time_series!(fts; path, name, overwrite_existing=false) return nothing end +include("metadata.jl") include("inpaint_mask.jl") -include("JRA55.jl") include("ECCO.jl") +include("JRA55.jl") -using .JRA55 using .ECCO +using .JRA55 end # module diff --git a/src/DataWrangling/JRA55.jl b/src/DataWrangling/JRA55.jl index a7427ec7..83d13264 100644 --- a/src/DataWrangling/JRA55.jl +++ b/src/DataWrangling/JRA55.jl @@ -26,111 +26,13 @@ import Oceananigans.Fields: set! import Oceananigans.OutputReaders: new_backend, update_field_time_series! using Downloads: download +include("jra55_metadata.jl") + download_jra55_cache::String = "" function __init__() global download_jra55_cache = @get_scratch!("JRA55") end -# A list of all variables provided in the JRA55 dataset: -JRA55_variable_names = (:river_freshwater_flux, - :rain_freshwater_flux, - :snow_freshwater_flux, - :iceberg_freshwater_flux, - :specific_humidity, - :sea_level_pressure, - :relative_humidity, - :downwelling_longwave_radiation, - :downwelling_shortwave_radiation, - :temperature, - :eastward_velocity, - :northward_velocity) - -filenames = Dict( - :river_freshwater_flux => "RYF.friver.1990_1991.nc", # Freshwater fluxes from rivers - :rain_freshwater_flux => "RYF.prra.1990_1991.nc", # Freshwater flux from rainfall - :snow_freshwater_flux => "RYF.prsn.1990_1991.nc", # Freshwater flux from snowfall - :iceberg_freshwater_flux => "RYF.licalvf.1990_1991.nc", # Freshwater flux from calving icebergs - :specific_humidity => "RYF.huss.1990_1991.nc", # Surface specific humidity - :sea_level_pressure => "RYF.psl.1990_1991.nc", # Sea level pressure - :relative_humidity => "RYF.rhuss.1990_1991.nc", # Surface relative humidity - :downwelling_longwave_radiation => "RYF.rlds.1990_1991.nc", # Downwelling longwave radiation - :downwelling_shortwave_radiation => "RYF.rsds.1990_1991.nc", # Downwelling shortwave radiation - :temperature => "RYF.tas.1990_1991.nc", # Near-surface air temperature - :eastward_velocity => "RYF.uas.1990_1991.nc", # Eastward near-surface wind - :northward_velocity => "RYF.vas.1990_1991.nc", # Northward near-surface wind -) - -jra55_short_names = Dict( - :river_freshwater_flux => "friver", # Freshwater fluxes from rivers - :rain_freshwater_flux => "prra", # Freshwater flux from rainfall - :snow_freshwater_flux => "prsn", # Freshwater flux from snowfall - :iceberg_freshwater_flux => "licalvf", # Freshwater flux from calving icebergs - :specific_humidity => "huss", # Surface specific humidity - :sea_level_pressure => "psl", # Sea level pressure - :relative_humidity => "rhuss", # Surface relative humidity - :downwelling_longwave_radiation => "rlds", # Downwelling longwave radiation - :downwelling_shortwave_radiation => "rsds", # Downwelling shortwave radiation - :temperature => "tas", # Near-surface air temperature - :eastward_velocity => "uas", # Eastward near-surface wind - :northward_velocity => "vas", # Northward near-surface wind -) - -field_time_series_short_names = Dict( - :river_freshwater_flux => "Fri", # Freshwater fluxes from rivers - :rain_freshwater_flux => "Fra", # Freshwater flux from rainfall - :snow_freshwater_flux => "Fsn", # Freshwater flux from snowfall - :iceberg_freshwater_flux => "Fic", # Freshwater flux from calving icebergs - :specific_humidity => "qa", # Surface specific humidity - :sea_level_pressure => "pa", # Sea level pressure - :relative_humidity => "rh", # Surface relative humidity - :downwelling_longwave_radiation => "Ql", # Downwelling longwave radiation - :downwelling_shortwave_radiation => "Qs", # Downwelling shortwave radiation - :temperature => "Ta", # Near-surface air temperature - :eastward_velocity => "ua", # Eastward near-surface wind - :northward_velocity => "va", # Northward near-surface wind -) - -urls = Dict( - :shortwave_radiation => "https://www.dropbox.com/scl/fi/z6fkvmd9oe3ycmaxta131/" * - "RYF.rsds.1990_1991.nc?rlkey=r7q6zcbj6a4fxsq0f8th7c4tc&dl=0", - - :river_freshwater_flux => "https://www.dropbox.com/scl/fi/21ggl4p74k4zvbf04nb67/" * - "RYF.friver.1990_1991.nc?rlkey=ny2qcjkk1cfijmwyqxsfm68fz&dl=0", - - :rain_freshwater_flux => "https://www.dropbox.com/scl/fi/5icl1gbd7f5hvyn656kjq/" * - "RYF.prra.1990_1991.nc?rlkey=iifyjm4ppwyd8ztcek4dtx0k8&dl=0", - - :snow_freshwater_flux => "https://www.dropbox.com/scl/fi/1r4ajjzb3643z93ads4x4/" * - "RYF.prsn.1990_1991.nc?rlkey=auyqpwn060cvy4w01a2yskfah&dl=0", - - :iceberg_freshwater_flux => "https://www.dropbox.com/scl/fi/44nc5y27ohvif7lkvpyv0/" * - "RYF.licalvf.1990_1991.nc?rlkey=w7rqu48y2baw1efmgrnmym0jk&dl=0", - - :specific_humidity => "https://www.dropbox.com/scl/fi/66z6ymfr4ghkynizydc29/" * - "RYF.huss.1990_1991.nc?rlkey=107yq04aew8lrmfyorj68v4td&dl=0", - - :sea_level_pressure => "https://www.dropbox.com/scl/fi/0fk332027oru1iiseykgp/" * - "RYF.psl.1990_1991.nc?rlkey=4xpr9uah741483aukok6d7ctt&dl=0", - - :relative_humidity => "https://www.dropbox.com/scl/fi/1agwsp0lzvntuyf8bm9la/" * - "RYF.rhuss.1990_1991.nc?rlkey=8cd0vs7iy1rw58b9pc9t68gtz&dl=0", - - :downwelling_longwave_radiation => "https://www.dropbox.com/scl/fi/y6r62szkirrivua5nqq61/" * - "RYF.rlds.1990_1991.nc?rlkey=wt9yq3cyrvs2rbowoirf4nkum&dl=0", - - :downwelling_shortwave_radiation => "https://www.dropbox.com/scl/fi/z6fkvmd9oe3ycmaxta131/" * - "RYF.rsds.1990_1991.nc?rlkey=r7q6zcbj6a4fxsq0f8th7c4tc&dl=0", - - :temperature => "https://www.dropbox.com/scl/fi/fpl0npwi476w635g6lke9/" * - "RYF.tas.1990_1991.nc?rlkey=0skb9pe6lgbfbiaoybe7m945s&dl=0", - - :eastward_velocity => "https://www.dropbox.com/scl/fi/86wetpqla2x97isp8092g/" * - "RYF.uas.1990_1991.nc?rlkey=rcaf18sh1yz0v9g4hjm1249j0&dl=0", - - :northward_velocity => "https://www.dropbox.com/scl/fi/d38sflo9ddljstd5jwgml/" * - "RYF.vas.1990_1991.nc?rlkey=f9y3e57kx8xrb40gbstarf0x6&dl=0", -) - compute_bounding_nodes(::Nothing, ::Nothing, LH, hnodes) = nothing compute_bounding_nodes(bounds, ::Nothing, LH, hnodes) = bounds diff --git a/src/DataWrangling/ecco_metadata.jl b/src/DataWrangling/ecco_metadata.jl index 3855bcac..c2e61f5c 100644 --- a/src/DataWrangling/ecco_metadata.jl +++ b/src/DataWrangling/ecco_metadata.jl @@ -4,55 +4,21 @@ import Dates: year, month, day import Oceananigans.Fields: set! import Base +using ClimaOcean.DataWrangling: Metadata + struct ECCO2Monthly end struct ECCO2Daily end struct ECCO4Monthly end -# Metadata holding the ECCO dataset information: -# - `name`: The name of the dataset. -# - `dates`: The dates of the dataset, in a `AbstractCFDateTime` format. -# - `version`: The version of the dataset, could be ECCO2Monthly, ECCO2Daily, or ECCO4Monthly. -struct ECCOMetadata{D, V} - name :: Symbol - dates :: D - version :: V -end - -Base.show(io::IO, metadata::ECCOMetadata) = - print(io, "ECCOMetadata:", '\n', - "├── field: $(metadata.name)", '\n', - "├── dates: $(metadata.dates)", '\n', - "└── data version: $(metadata.version)") - -# The default is the ECCO2Daily dataset at 1993-01-01. -function ECCOMetadata(name::Symbol; - date = DateTimeProlepticGregorian(1993, 1, 1), - version = ECCO2Daily()) - - return ECCOMetadata(name, date, version) -end +const ECCOMetadata{T} = Union{Metadata{T, <:ECCO4Monthly}, Metadata{T, <:ECCO2Daily}, Metadata{T, <:ECCO2Monthly}} where T -# Treat ECCOMetadata as an array to allow iteration over the dates. -Base.getindex(metadata::ECCOMetadata, i::Int) = @inbounds ECCOMetadata(metadata.name, metadata.dates[i], metadata.version) -Base.length(metadata::ECCOMetadata) = length(metadata.dates) -Base.eltype(metadata::ECCOMetadata) = Base.eltype(metadata.dates) -Base.first(metadata::ECCOMetadata) = @inbounds ECCOMetadata(metadata.name, metadata.dates[1], metadata.version) -Base.last(metadata::ECCOMetadata) = @inbounds ECCOMetadata(metadata.name, metadata.dates[end], metadata.version) -Base.iterate(metadata::ECCOMetadata, i=1) = (@inline; (i % UInt) - 1 < length(metadata) ? (@inbounds ECCOMetadata(metadata.name, metadata.dates[i], metadata.version), i + 1) : nothing) +Base.size(data::Metadata{<:Any, <:ECCO2Daily}) = (1440, 720, 50, length(data.dates)) +Base.size(data::Metadata{<:Any, <:ECCO2Monthly}) = (1440, 720, 50, length(data.dates)) +Base.size(data::Metadata{<:Any, <:ECCO4Monthly}) = (720, 360, 50, length(data.dates)) -Base.axes(metadata::ECCOMetadata{<:AbstractCFDateTime}) = 1 -Base.first(metadata::ECCOMetadata{<:AbstractCFDateTime}) = metadata -Base.last(metadata::ECCOMetadata{<:AbstractCFDateTime}) = metadata -Base.iterate(metadata::ECCOMetadata{<:AbstractCFDateTime}) = (metadata, nothing) -Base.iterate(::ECCOMetadata{<:AbstractCFDateTime}, ::Any) = nothing - -Base.size(data::ECCOMetadata{<:Any, <:ECCO2Daily}) = (1440, 720, 50, length(data.dates)) -Base.size(data::ECCOMetadata{<:Any, <:ECCO2Monthly}) = (1440, 720, 50, length(data.dates)) -Base.size(data::ECCOMetadata{<:Any, <:ECCO4Monthly}) = (720, 360, 50, length(data.dates)) - -Base.size(::ECCOMetadata{<:AbstractCFDateTime, <:ECCO2Daily}) = (1440, 720, 50, 1) -Base.size(::ECCOMetadata{<:AbstractCFDateTime, <:ECCO2Monthly}) = (1440, 720, 50, 1) -Base.size(::ECCOMetadata{<:AbstractCFDateTime, <:ECCO4Monthly}) = (720, 360, 50, 1) +Base.size(::Metadata{<:AbstractCFDateTime, <:ECCO2Daily}) = (1440, 720, 50, 1) +Base.size(::Metadata{<:AbstractCFDateTime, <:ECCO2Monthly}) = (1440, 720, 50, 1) +Base.size(::Metadata{<:AbstractCFDateTime, <:ECCO4Monthly}) = (720, 360, 50, 1) # The whole range of dates in the different dataset versions all_ecco_dates(::ECCO4Monthly) = DateTimeProlepticGregorian(1992, 1, 1) : Month(1) : DateTimeProlepticGregorian(2023, 12, 1) @@ -60,8 +26,8 @@ all_ecco_dates(::ECCO2Monthly) = DateTimeProlepticGregorian(1992, 1, 1) : Month( all_ecco_dates(::ECCO2Daily) = DateTimeProlepticGregorian(1992, 1, 4) : Day(1) : DateTimeProlepticGregorian(2023, 12, 31) # File name generation specific to each Dataset version -function metadata_filename(metadata::ECCOMetadata{<:AbstractCFDateTime, <:ECCO4Monthly}) - shortname = short_name(metadata) +function metadata_filename(metadata::Metadata{<:AbstractCFDateTime, <:ECCO4Monthly}) + shortname = short_name(metadata) yearstr = string(Dates.year(metadata.dates)) monthstr = string(Dates.month(metadata.dates), pad=2) return shortname * "_" * yearstr * "_" * monthstr * ".nc" @@ -82,9 +48,9 @@ function metadata_filename(metadata::ECCOMetadata{<:AbstractCFDateTime}) end # Convenience functions -short_name(data::ECCOMetadata{<:Any, <:ECCO2Daily}) = ecco2_short_names[data.name] -short_name(data::ECCOMetadata{<:Any, <:ECCO2Monthly}) = ecco2_short_names[data.name] -short_name(data::ECCOMetadata{<:Any, <:ECCO4Monthly}) = ecco4_short_names[data.name] +short_name(data::Metadata{<:Any, <:ECCO2Daily}) = ecco2_short_names[data.name] +short_name(data::Metadata{<:Any, <:ECCO2Monthly}) = ecco2_short_names[data.name] +short_name(data::Metadata{<:Any, <:ECCO4Monthly}) = ecco4_short_names[data.name] field_location(data::ECCOMetadata) = ecco_location[data.name] @@ -122,12 +88,12 @@ ecco_location = Dict( ) # URLs for the ECCO datasets specific to each version -urls(::ECCOMetadata{<:Any, <:ECCO2Monthly}) = "https://ecco.jpl.nasa.gov/drive/files/ECCO2/cube92_latlon_quart_90S90N/monthly/" -urls(::ECCOMetadata{<:Any, <:ECCO2Daily}) = "https://ecco.jpl.nasa.gov/drive/files/ECCO2/cube92_latlon_quart_90S90N/daily/" -urls(::ECCOMetadata{<:Any, <:ECCO4Monthly}) = "https://ecco.jpl.nasa.gov/drive/files/Version4/Release4/interp_monthly/" +urls(::Metadata{<:Any, <:ECCO2Monthly}) = "https://ecco.jpl.nasa.gov/drive/files/ECCO2/cube92_latlon_quart_90S90N/monthly/" +urls(::Metadata{<:Any, <:ECCO2Daily}) = "https://ecco.jpl.nasa.gov/drive/files/ECCO2/cube92_latlon_quart_90S90N/daily/" +urls(::Metadata{<:Any, <:ECCO4Monthly}) = "https://ecco.jpl.nasa.gov/drive/files/Version4/Release4/interp_monthly/" """ - download_dataset!(metadata::ECCOMetadata) + download_dataset!(metadata::Metadata) Download the dataset specified by the given metadata. If the metadata contains a single date, the dataset is downloaded directly. If the metadata contains multiple dates, the dataset is @@ -139,7 +105,7 @@ or by launching julia with ECCO_USERNAME=myuser ECCO_PASSWORD=mypasswrd julia # Arguments -- `metadata::ECCOMetadata`: The metadata specifying the dataset to be downloaded. +- `metadata::Metadata`: The metadata specifying the dataset to be downloaded. """ function download_dataset!(metadata::ECCOMetadata; url = urls(metadata)) diff --git a/src/DataWrangling/ecco_restoring.jl b/src/DataWrangling/ecco_restoring.jl index 3f063fef..f6d1f608 100644 --- a/src/DataWrangling/ecco_restoring.jl +++ b/src/DataWrangling/ecco_restoring.jl @@ -12,6 +12,7 @@ using JLD2 using Dates using ClimaOcean: stateindex +using ClimaOcean.DataWrangling: native_times import Oceananigans.Fields: set! import Oceananigans.OutputReaders: new_backend, update_field_time_series! @@ -66,31 +67,6 @@ function set!(fts::ECCONetCDFFTS, path::ECCOMetadata=fts.path, name::String=fts. return nothing end -""" - ecco_times(metadata; start_time = metadata.dates[1]) - -Extracts the time values from the given metadata and calculates the time difference -from the start time. - -# Arguments -- `metadata`: The metadata containing the date information. -- `start_time`: The start time for calculating the time difference. Defaults to the first date in the metadata. - -# Returns -An array of time differences in seconds. -""" -function ecco_times(metadata; start_time = first(metadata).dates) - times = zeros(length(metadata)) - for (t, data) in enumerate(metadata) - date = data.dates - time = date - start_time - time = Second(time).value - times[t] = time - end - - return times -end - """ ECCO_field_time_series(metadata::ECCOMetadata; architecture = CPU(), @@ -100,10 +76,12 @@ end Create a field time series object for ECCO data. -# Arguments: +Arguments: +============== - metadata: An ECCOMetadata object containing information about the ECCO dataset. -# Keyword Arguments: +Keyword Arguments: +===================== - 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()). diff --git a/src/DataWrangling/jra55_field_time_series.jl b/src/DataWrangling/jra55_field_time_series.jl new file mode 100644 index 00000000..1d6eb6ee --- /dev/null +++ b/src/DataWrangling/jra55_field_time_series.jl @@ -0,0 +1,266 @@ +using Oceananigans.Units +using Oceananigans.Grids: node, on_architecture +using Oceananigans.Fields: interpolate!, interpolate, location, instantiated_location +using Oceananigans.OutputReaders: Cyclical, TotallyInMemory, AbstractInMemoryBackend, FlavorOfFTS, time_indices +using Oceananigans.Utils: Time + +using CUDA: @allowscalar +using Base + +using NCDatasets +using JLD2 +using Dates + +using ClimaOcean: stateindex +using ClimaOcean.DataWrangling: native_times + +import Oceananigans.Fields: set! +import Oceananigans.OutputReaders: new_backend, update_field_time_series! + +@inline instantiate(T::DataType) = T() +@inline instantiate(T) = T + +struct JRA55NetCDFBackend{N} <: AbstractInMemoryBackend{Int} + start :: Int + length :: Int + + JRA55NetCDFBackend{N}(start::Int, length::Int) where N = new{N}(start, length) +end + +""" + JRA55NetCDFBackend(length) + +Represents an JRA55 FieldTimeSeries backed by JRA55 native .nc files. +Each time instance is stored in an individual file. +""" +JRA55NetCDFBackend(length; on_native_grid = false) = JRA55NetCDFBackend{on_native_grid}(1, length) + +Base.length(backend::JRA55NetCDFBackend) = backend.length +Base.summary(backend::JRA55NetCDFBackend) = string("JRA55NetCDFBackend(", backend.start, ", ", backend.length, ")") + +const JRA55NetCDFFTS{N} = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <: JRA55NetCDFBackend{N}} where N + +new_backend(::JRA55NetCDFBackend{N}, start, length) where N = JRA55ONetCDFBackend{N}(start, length) +on_native_grid(::JRA55NetCDFBackend{N}) where N = N + +function set!(fts::JRA55NetCDFFTS, path::JRA55Metadata=fts.path, name::String=fts.name) + + backend = fts.backend + start = backend.start + + # Set the JRA55 dataset based on the backend!s + + 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 + end + + fill_halo_regions!(fts) + + return nothing +end + +""" + JRA55_field_time_series(metadata::ECCOMetadata; + architecture = CPU(), + time_indices_in_memory = 2, + time_indexing = Cyclical(), + grid = nothing) + +Create a field time series object for ECCO data. + +# Arguments: +- metadata: An ECCOMetadata object containing information about the ECCO dataset. + +# Keyword Arguments: +- 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()). +- grid: if not a `nothing`, the ECCO data is directly interpolated on the `grid`, +""" +function JRA55_field_time_series(metadata::JRA55Metadata; + architecture = CPU(), + backend = JRA55NetCDFBackend(20), + time_indexing = Cyclical(), + grid = nothing) + + # ECCO data is too chunky to allow other backends + backend = if backend isa JRA55NetCDFBackend + JRA55NetCDFBackend(backend.length; + on_native_grid = isnothing(grid)) + else + backend + end + + # Making sure all the required individual files are downloaded + download_dataset!(metadata) + + location = field_location(metadata) + shortname = short_name(metadata) + + JRA55_native_grid = jra55_native_grid() + boundary_conditions = FieldBoundaryConditions(JRA55_native_grid, location) + times = native_times(metadata) + + fts_grid = isnothing(grid) ? ECCO_native_grid : grid + + fts = FieldTimeSeries{location...}(fts_grid, times; + backend, + time_indexing, + boundary_conditions, + path = metadata, + name = shortname) + + # Let's set the data + set!(fts) + + return fts +end + +ECCO_field_time_series(variable_name::Symbol, version=ECCO4Monthly(); kw...) = + ECCO_field_time_series(ECCOMetadata(variable_name, all_ecco_dates(version), version); kw...) + +# Variable names for restoreable data +struct Temperature end +struct Salinity end +struct UVelocity end +struct VVelocity end + +oceananigans_fieldname = Dict( + :temperature => Temperature(), + :salinity => Salinity(), + :u_velocity => UVelocity(), + :v_velocity => VVelocity()) + +@inline Base.getindex(fields, i, j, k, ::Temperature) = @inbounds fields.T[i, j, k] +@inline Base.getindex(fields, i, j, k, ::Salinity) = @inbounds fields.S[i, j, k] +@inline Base.getindex(fields, i, j, k, ::UVelocity) = @inbounds fields.u[i, j, k] +@inline Base.getindex(fields, i, j, k, ::VVelocity) = @inbounds fields.v[i, j, k] + +""" + struct ECCORestoring{FTS, G, M, V, N} <: Function + +A struct representing ECCO restoring. + +# Fields +- `ecco_fts`: The ECCO FTS on the native ECCO grid. +- `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. +""" +struct ECCORestoring{FTS, G, M, V, N} <: Function + ecco_fts :: FTS + ecco_grid :: G + mask :: M + variable_name :: V + λ⁻¹ :: 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.λ⁻¹)) + +@inline function (p::ECCORestoring)(i, j, k, grid, clock, fields) + + # Figure out all the inputs: time, location, and node + time = Time(clock.time) + loc = location(p.ecco_fts) + + # Retrieve the variable to force + @inbounds var = fields[i, j, k, p.variable_name] + + ecco_backend = p.ecco_fts.backend + native_grid = on_native_grid(ecco_backend) + + ecco_var = get_ecco_variable(Val(native_grid), p.ecco_fts, i, j, k, p.ecco_grid, grid, time) + + # 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) +end + +# Differentiating between restoring done with an ECCO FTS +# that lives on the native ecco grid, that requires interpolation in space +# _inside_ the restoring function and restoring based on an ECCO +# FTS defined on the model grid that requires only time interpolation +@inline function get_ecco_variable(::Val{true}, ecco_fts, i, j, k, ecco_grid, grid, time) + # Extracting the ECCO field time series data and parameters + ecco_times = ecco_fts.times + ecco_data = ecco_fts.data + ecco_time_indexing = ecco_fts.time_indexing + ecco_backend = ecco_fts.backend + ecco_location = instantiated_location(ecco_fts) + + X = node(i, j, k, grid, ecco_location...) + + # Interpolating the ECCO field time series data onto the current node and time + return interpolate(X, time, ecco_data, ecco_location, ecco_grid, ecco_times, ecco_backend, ecco_time_indexing) +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) + +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. + +# Keyword Arguments: +==================== +- `architecture`: The architecture. Typically `CPU` or `GPU` +- `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) +- `mask`: The mask value. Can be a function of `(x, y, z, time)`, an array or a number +- `timescale`: The restoring timescale. +""" +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...) +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) + + ecco_fts = ECCO_field_time_series(metadata; grid, architecture, time_indices_in_memory, time_indexing) + 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 +end \ No newline at end of file diff --git a/src/DataWrangling/jra55_metadata.jl b/src/DataWrangling/jra55_metadata.jl new file mode 100644 index 00000000..e9b84fe9 --- /dev/null +++ b/src/DataWrangling/jra55_metadata.jl @@ -0,0 +1,156 @@ +using CFTime +using Dates +import Dates: year, month, day +import Oceananigans.Fields: set! +import Base + +using ClimaOcean.DataWrangling: Metadata + +struct JRA55MultipleYears end +struct JRA55RepeatYear end + +const JRA55Metadata{T} = Union{Metadata{T, <:JRA55MultipleYears}, Metadata{<:JRA55RepeatYear}} where T + +Base.size(data::JRA55Metadata) = (640, 320, length(data.dates)) +Base.size(::JRA55Metadata{<:AbstractCFDateTime}) = (640, 320, 1) + +# File name generation specific to each Dataset version +function metadata_filename(metadata::Metadata{<:AbstractCFDateTime, <:JRA55MultipleYears}) + # fix the filename + + return filename +end + +# File name generation specific to each Dataset version +function metadata_filename(metadata::Metadata{<:AbstractCFDateTime, <:JRA55RepeatYear}) + # fix the filename + + return filename +end + +# Convenience functions +short_name(data::JRA55Metadata) = jra55_short_names[data.name] +field_location(data::JRA55Metadata) = jra55_location[data.name] + +# A list of all variables provided in the JRA55 dataset: +JRA55_variable_names = (:river_freshwater_flux, + :rain_freshwater_flux, + :snow_freshwater_flux, + :iceberg_freshwater_flux, + :specific_humidity, + :sea_level_pressure, + :relative_humidity, + :downwelling_longwave_radiation, + :downwelling_shortwave_radiation, + :temperature, + :eastward_velocity, + :northward_velocity) + +filenames = Dict( + :river_freshwater_flux => "RYF.friver.1990_1991.nc", # Freshwater fluxes from rivers + :rain_freshwater_flux => "RYF.prra.1990_1991.nc", # Freshwater flux from rainfall + :snow_freshwater_flux => "RYF.prsn.1990_1991.nc", # Freshwater flux from snowfall + :iceberg_freshwater_flux => "RYF.licalvf.1990_1991.nc", # Freshwater flux from calving icebergs + :specific_humidity => "RYF.huss.1990_1991.nc", # Surface specific humidity + :sea_level_pressure => "RYF.psl.1990_1991.nc", # Sea level pressure + :relative_humidity => "RYF.rhuss.1990_1991.nc", # Surface relative humidity + :downwelling_longwave_radiation => "RYF.rlds.1990_1991.nc", # Downwelling longwave radiation + :downwelling_shortwave_radiation => "RYF.rsds.1990_1991.nc", # Downwelling shortwave radiation + :temperature => "RYF.tas.1990_1991.nc", # Near-surface air temperature + :eastward_velocity => "RYF.uas.1990_1991.nc", # Eastward near-surface wind + :northward_velocity => "RYF.vas.1990_1991.nc", # Northward near-surface wind +) + +jra55_short_names = Dict( + :river_freshwater_flux => "friver", # Freshwater fluxes from rivers + :rain_freshwater_flux => "prra", # Freshwater flux from rainfall + :snow_freshwater_flux => "prsn", # Freshwater flux from snowfall + :iceberg_freshwater_flux => "licalvf", # Freshwater flux from calving icebergs + :specific_humidity => "huss", # Surface specific humidity + :sea_level_pressure => "psl", # Sea level pressure + :relative_humidity => "rhuss", # Surface relative humidity + :downwelling_longwave_radiation => "rlds", # Downwelling longwave radiation + :downwelling_shortwave_radiation => "rsds", # Downwelling shortwave radiation + :temperature => "tas", # Near-surface air temperature + :eastward_velocity => "uas", # Eastward near-surface wind + :northward_velocity => "vas", # Northward near-surface wind +) + +field_time_series_short_names = Dict( + :river_freshwater_flux => "Fri", # Freshwater fluxes from rivers + :rain_freshwater_flux => "Fra", # Freshwater flux from rainfall + :snow_freshwater_flux => "Fsn", # Freshwater flux from snowfall + :iceberg_freshwater_flux => "Fic", # Freshwater flux from calving icebergs + :specific_humidity => "qa", # Surface specific humidity + :sea_level_pressure => "pa", # Sea level pressure + :relative_humidity => "rh", # Surface relative humidity + :downwelling_longwave_radiation => "Ql", # Downwelling longwave radiation + :downwelling_shortwave_radiation => "Qs", # Downwelling shortwave radiation + :temperature => "Ta", # Near-surface air temperature + :eastward_velocity => "ua", # Eastward near-surface wind + :northward_velocity => "va", # Northward near-surface wind +) + +urls = Dict( + :shortwave_radiation => "https://www.dropbox.com/scl/fi/z6fkvmd9oe3ycmaxta131/" * + "RYF.rsds.1990_1991.nc?rlkey=r7q6zcbj6a4fxsq0f8th7c4tc&dl=0", + + :river_freshwater_flux => "https://www.dropbox.com/scl/fi/21ggl4p74k4zvbf04nb67/" * + "RYF.friver.1990_1991.nc?rlkey=ny2qcjkk1cfijmwyqxsfm68fz&dl=0", + + :rain_freshwater_flux => "https://www.dropbox.com/scl/fi/5icl1gbd7f5hvyn656kjq/" * + "RYF.prra.1990_1991.nc?rlkey=iifyjm4ppwyd8ztcek4dtx0k8&dl=0", + + :snow_freshwater_flux => "https://www.dropbox.com/scl/fi/1r4ajjzb3643z93ads4x4/" * + "RYF.prsn.1990_1991.nc?rlkey=auyqpwn060cvy4w01a2yskfah&dl=0", + + :iceberg_freshwater_flux => "https://www.dropbox.com/scl/fi/44nc5y27ohvif7lkvpyv0/" * + "RYF.licalvf.1990_1991.nc?rlkey=w7rqu48y2baw1efmgrnmym0jk&dl=0", + + :specific_humidity => "https://www.dropbox.com/scl/fi/66z6ymfr4ghkynizydc29/" * + "RYF.huss.1990_1991.nc?rlkey=107yq04aew8lrmfyorj68v4td&dl=0", + + :sea_level_pressure => "https://www.dropbox.com/scl/fi/0fk332027oru1iiseykgp/" * + "RYF.psl.1990_1991.nc?rlkey=4xpr9uah741483aukok6d7ctt&dl=0", + + :relative_humidity => "https://www.dropbox.com/scl/fi/1agwsp0lzvntuyf8bm9la/" * + "RYF.rhuss.1990_1991.nc?rlkey=8cd0vs7iy1rw58b9pc9t68gtz&dl=0", + + :downwelling_longwave_radiation => "https://www.dropbox.com/scl/fi/y6r62szkirrivua5nqq61/" * + "RYF.rlds.1990_1991.nc?rlkey=wt9yq3cyrvs2rbowoirf4nkum&dl=0", + + :downwelling_shortwave_radiation => "https://www.dropbox.com/scl/fi/z6fkvmd9oe3ycmaxta131/" * + "RYF.rsds.1990_1991.nc?rlkey=r7q6zcbj6a4fxsq0f8th7c4tc&dl=0", + + :temperature => "https://www.dropbox.com/scl/fi/fpl0npwi476w635g6lke9/" * + "RYF.tas.1990_1991.nc?rlkey=0skb9pe6lgbfbiaoybe7m945s&dl=0", + + :eastward_velocity => "https://www.dropbox.com/scl/fi/86wetpqla2x97isp8092g/" * + "RYF.uas.1990_1991.nc?rlkey=rcaf18sh1yz0v9g4hjm1249j0&dl=0", + + :northward_velocity => "https://www.dropbox.com/scl/fi/d38sflo9ddljstd5jwgml/" * + "RYF.vas.1990_1991.nc?rlkey=f9y3e57kx8xrb40gbstarf0x6&dl=0", +) + +variable_is_three_dimensional(data::JRA55Metadata) = false + +# URLs for the JRA55 datasets specific to each version +function urls(metadata::JRA55Metadata) + return "https://ecco.jpl.nasa.gov/drive/files/ECCO2/cube92_latlon_quart_90S90N/monthly/" +end + +function download_dataset!(metadata::JRA55Metadata; + url = urls(metadata)) + + for data in metadata + filename = metadata_filename(data) + shortname = short_name(data) + + if !isfile(filename) + fileurl = joinpath(url, shortname, year, filename) + download(url, filepath) + end + end + + return nothing +end \ No newline at end of file diff --git a/src/DataWrangling/metadata.jl b/src/DataWrangling/metadata.jl new file mode 100644 index 00000000..523fdd2c --- /dev/null +++ b/src/DataWrangling/metadata.jl @@ -0,0 +1,58 @@ + +# Metadata holding the ECCO dataset information: +# - `name`: The name of the dataset. +# - `dates`: The dates of the dataset, in a `AbstractCFDateTime` format. +# - `version`: The version of the dataset, could be ECCO2Monthly, ECCO2Daily, or ECCO4Monthly. +struct Metadata{D, V} + name :: Symbol + dates :: D + version :: V +end + +Base.show(io::IO, metadata::Metadata) = + print(io, "Metadata:", '\n', + "├── field: $(metadata.name)", '\n', + "├── dates: $(metadata.dates)", '\n', + "└── data version: $(metadata.version)") + +# Treat Metadata as an array to allow iteration over the dates. +Base.getindex(metadata::Metadata, i::Int) = @inbounds Metadata(metadata.name, metadata.dates[i], metadata.version) +Base.length(metadata::Metadata) = length(metadata.dates) +Base.eltype(metadata::Metadata) = Base.eltype(metadata.dates) +Base.first(metadata::Metadata) = @inbounds Metadata(metadata.name, metadata.dates[1], metadata.version) +Base.last(metadata::Metadata) = @inbounds Metadata(metadata.name, metadata.dates[end], metadata.version) +Base.iterate(metadata::Metadata, i=1) = (@inline; (i % UInt) - 1 < length(metadata) ? (@inbounds Metadata(metadata.name, metadata.dates[i], metadata.version), i + 1) : nothing) + +Base.axes(metadata::Metadata{<:AbstractCFDateTime}) = 1 +Base.first(metadata::Metadata{<:AbstractCFDateTime}) = metadata +Base.last(metadata::Metadata{<:AbstractCFDateTime}) = metadata +Base.iterate(metadata::Metadata{<:AbstractCFDateTime}) = (metadata, nothing) +Base.iterate(::Metadata{<:AbstractCFDateTime}, ::Any) = nothing + +Base.size(data::Metadata{<:Any, <:JRA55ThreeHourly}) = (640, 320, 1, length(data.dates)) + + +""" + native_times(metadata; start_time = metadata.dates[1]) + +Extracts the time values from the given metadata and calculates the time difference +from the start time. + +# Arguments +- `metadata`: The metadata containing the date information. +- `start_time`: The start time for calculating the time difference. Defaults to the first date in the metadata. + +# Returns +An array of time differences in seconds. +""" +function native_times(metadata; start_time = first(metadata).dates) + times = zeros(length(metadata)) + for (t, data) in enumerate(metadata) + date = data.dates + time = date - start_time + time = Second(time).value + times[t] = time + end + + return times +end \ No newline at end of file From 42be4e9714aa0c3a065ae230cb8d9b8903d802ac Mon Sep 17 00:00:00 2001 From: Simone Silvestri <33547697+simone-silvestri@users.noreply.github.com> Date: Thu, 25 Jul 2024 17:46:44 -0400 Subject: [PATCH 002/104] going on... --- src/DataWrangling/JRA55.jl | 333 ------------ src/DataWrangling/jra55_field_time_series.jl | 533 ++++++++++++++----- 2 files changed, 391 insertions(+), 475 deletions(-) diff --git a/src/DataWrangling/JRA55.jl b/src/DataWrangling/JRA55.jl index 83d13264..8005df95 100644 --- a/src/DataWrangling/JRA55.jl +++ b/src/DataWrangling/JRA55.jl @@ -155,339 +155,6 @@ end new_backend(::JRA55NetCDFBackend, start, length) = JRA55NetCDFBackend(start, length) -""" - JRA55_field_time_series(variable_name; - architecture = CPU(), - location = nothing, - url = nothing, - filename = nothing, - shortname = nothing, - backend = InMemory(), - preprocess_chunk_size = 10, - preprocess_architecture = CPU(), - time_indices = nothing) - -Return a `FieldTimeSeries` containing atmospheric reanalysis data for `variable_name`, -which describes one of the variables in the "repeat year forcing" dataset derived -from the Japanese 55-year atmospheric reanalysis for driving ocean-sea-ice models (JRA55-do). -For more information about the derivation of the repeat year forcing dataset, see - -"Stewart et al., JRA55-do-based repeat year forcing datasets for driving ocean–sea-ice models", -Ocean Modelling, 2020, https://doi.org/10.1016/j.ocemod.2019.101557. - -The `variable_name`s (and their `shortname`s used in NetCDF files) -available from the JRA55-do are: - - - `:river_freshwater_flux` ("friver") - - `:rain_freshwater_flux` ("prra") - - `:snow_freshwater_flux` ("prsn") - - `:iceberg_freshwater_flux` ("licalvf") - - `:specific_humidity` ("huss") - - `:sea_level_pressure` ("psl") - - `:relative_humidity` ("rhuss") - - `:downwelling_longwave_radiation` ("rlds") - - `:downwelling_shortwave_radiation` ("rsds") - - `:temperature` ("ras") - - `:eastward_velocity` ("uas") - - `:northward_velocity` ("vas") - -Keyword arguments -================= - - - `architecture`: Architecture for the `FieldTimeSeries`. - Default: CPU() - - - `time_indices`: Indices of the timeseries to extract from file. - For example, `time_indices=1:3` returns a - `FieldTimeSeries` with the first three time snapshots - of `variable_name`. - - - `url`: The url accessed to download the data for `variable_name`. - Default: `ClimaOcean.JRA55.urls[variable_name]`. - - - `filename`: The name of the downloaded file. - Default: `ClimaOcean.JRA55.filenames[variable_name]`. - - - `shortname`: The "short name" of `variable_name` inside its NetCDF file. - Default: `ClimaOcean.JRA55.jra55_short_names[variable_name]`. - - - `interpolated_file`: file holding an Oceananigans compatible version of the JRA55 data. - If it does not exist it will be generated. - - - `time_chunks_in_memory`: number of fields held in memory. If `nothing` the whole timeseries is - loaded (not recommended). -""" -function JRA55_field_time_series(variable_name; - architecture = CPU(), - grid = nothing, - location = nothing, - url = nothing, - dir = download_jra55_cache, - filename = nothing, - shortname = nothing, - latitude = nothing, - longitude = nothing, - backend = InMemory(), - time_indexing = Cyclical(), - preprocess_chunk_size = 10, - preprocess_architecture = CPU(), - time_indices = nothing) - - # OnDisk backends do not support time interpolation! - # Disallow OnDisk for JRA55 dataset loading - if backend isa OnDisk - msg = string("We cannot load the JRA55 dataset with an `OnDisk` backend") - throw(ArgumentError(msg)) - end - - if isnothing(filename) && !(variable_name ∈ JRA55_variable_names) - variable_strs = Tuple(" - :$name \n" for name in JRA55_variable_names) - variables_msg = prod(variable_strs) - - msg = string("The variable :$variable_name is not provided by the JRA55-do dataset!", '\n', - "The variables provided by the JRA55-do dataset are:", '\n', - variables_msg) - - throw(ArgumentError(msg)) - end - - filepath = isnothing(filename) ? joinpath(dir, filenames[variable_name]) : joinpath(dir, filename) - - if !isnothing(filename) && !isfile(filepath) && isnothing(url) - throw(ArgumentError("A filename was provided without a url, but the file does not exist.\n \ - If intended, please provide both the filename and url that should be used \n \ - to download the new file.")) - end - - isnothing(filename) && (filename = filenames[variable_name]) - isnothing(shortname) && (shortname = jra55_short_names[variable_name]) - isnothing(url) && (url = urls[variable_name]) - - # Record some important user decisions - totally_in_memory = backend isa TotallyInMemory - on_native_grid = isnothing(grid) - !on_native_grid && backend isa JRA55NetCDFBackend && error("Can't use custom grid with JRA55NetCDFBackend.") - - jld2_filepath = joinpath(dir, string("JRA55_repeat_year_", variable_name, ".jld2")) - fts_name = field_time_series_short_names[variable_name] - - # Note, we don't re-use existing jld2 files. - isfile(filepath) || download(url, filepath) - isfile(jld2_filepath) && rm(jld2_filepath) - - # Determine default time indices - if totally_in_memory - # In this case, the whole time series is in memory. - # Either the time series is short, or we are doing a limited-area - # simulation, like in a single column. So, we conservatively - # set a default `time_indices = 1:2`. - isnothing(time_indices) && (time_indices = 1:2) - time_indices_in_memory = time_indices - native_fts_architecture = architecture - else - # In this case, part or all of the time series will be stored in a file. - # Note: if the user has provided a grid, we will have to preprocess the - # .nc JRA55 data into a .jld2 file. In this case, `time_indices` refers - # to the time_indices that we will preprocess; - # by default we choose all of them. The architecture is only the - # architecture used for preprocessing, which typically will be CPU() - # even if we would like the final FieldTimeSeries on the GPU. - isnothing(time_indices) && (time_indices = :) - - if backend isa JRA55NetCDFBackend - time_indices_in_memory = 1:length(backend) - native_fts_architecture = architecture - else # then `time_indices_in_memory` refers to preprocessing - maximum_index = min(preprocess_chunk_size, length(time_indices)) - time_indices_in_memory = 1:maximum_index - native_fts_architecture = preprocess_architecture - end - end - - # Set a default location. - if isnothing(location) - LX = LY = Center - else - LX, LY = location - end - - ds = Dataset(filepath) - - # Note that each file should have the variables - # - ds["time"]: time coordinate - # - ds["lon"]: longitude at the location of the variable - # - ds["lat"]: latitude at the location of the variable - # - ds["lon_bnds"]: bounding longitudes between which variables are averaged - # - ds["lat_bnds"]: bounding latitudes between which variables are averaged - # - ds[shortname]: the variable data - - # Nodes at the variable location - λc = ds["lon"][:] - φc = ds["lat"][:] - - # Interfaces for the "native" JRA55 grid - λn = ds["lon_bnds"][1, :] - φn = ds["lat_bnds"][1, :] - - # The .nc coordinates lon_bnds and lat_bnds do not include - # the last interface, so we push them here. - push!(φn, 90) - push!(λn, λn[1] + 360) - - # TODO: support loading just part of the JRA55 data. - # Probably with arguments that take latitude, longitude bounds. - i₁, i₂, j₁, j₂, TX = compute_bounding_indices(longitude, latitude, grid, LX, LY, λc, φc) - - native_times = ds["time"][time_indices] - data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] - λr = λn[i₁:i₂+1] - φr = φn[j₁:j₂+1] - Nrx, Nry, Nt = size(data) - close(ds) - - N = (Nrx, Nry) - H = min.(N, (3, 3)) - - JRA55_native_grid = LatitudeLongitudeGrid(native_fts_architecture, Float32; - halo = H, - size = N, - longitude = λr, - latitude = φr, - topology = (TX, Bounded, Flat)) - - boundary_conditions = FieldBoundaryConditions(JRA55_native_grid, (Center, Center, Nothing)) - times = jra55_times(native_times) - - if backend isa JRA55NetCDFBackend - fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; - backend, - time_indexing, - boundary_conditions, - path = filepath, - name = shortname) - - # Fill the data in a GPU-friendly manner - copyto!(interior(fts, :, :, 1, :), data) - fill_halo_regions!(fts) - - return fts - else - # Make times into an array for later preprocessing - if !totally_in_memory - times = collect(times) - end - - native_fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; - time_indexing, - boundary_conditions) - - # Fill the data in a GPU-friendly manner - copyto!(interior(native_fts, :, :, 1, :), data) - fill_halo_regions!(native_fts) - - if on_native_grid && totally_in_memory - return native_fts - - elseif totally_in_memory # but not on the native grid! - boundary_conditions = FieldBoundaryConditions(grid, (LX, LY, Nothing)) - fts = FieldTimeSeries{LX, LY, Nothing}(grid, times; time_indexing, boundary_conditions) - interpolate!(fts, native_fts) - return fts - end - end - - @info "Pre-processing JRA55 $variable_name data into a JLD2 file..." - - preprocessing_grid = on_native_grid ? JRA55_native_grid : grid - - # Re-open the dataset! - ds = Dataset(filepath) - all_datetimes = ds["time"][time_indices] - all_Nt = length(all_datetimes) - - all_times = jra55_times(all_datetimes) - - on_disk_fts = FieldTimeSeries{LX, LY, Nothing}(preprocessing_grid, all_times; - boundary_conditions, - backend = OnDisk(), - path = jld2_filepath, - name = fts_name) - - # Save data to disk, one field at a time - start_clock = time_ns() - n = 1 # on disk - m = 0 # in memory - - times_in_memory = all_times[time_indices_in_memory] - - fts = FieldTimeSeries{LX, LY, Nothing}(preprocessing_grid, times_in_memory; - boundary_conditions, - backend = InMemory(), - path = jld2_filepath, - name = fts_name) - - # Re-compute data - new_data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] - - if !on_native_grid - copyto!(interior(native_fts, :, :, 1, :), new_data[:, :, :]) - fill_halo_regions!(native_fts) - interpolate!(fts, native_fts) - else - copyto!(interior(fts, :, :, 1, :), new_data[:, :, :]) - end - - while n <= all_Nt - print(" ... processing time index $n of $all_Nt \r") - - if time_indices_in_memory isa Colon || n ∈ time_indices_in_memory - m += 1 - else # load new data - # Update time_indices - time_indices_in_memory = time_indices_in_memory .+ preprocess_chunk_size - n₁ = first(time_indices_in_memory) - - # Clip time_indices if they extend past the end of the dataset - if last(time_indices_in_memory) > all_Nt - time_indices_in_memory = UnitRange(n₁, all_Nt) - end - - # Re-compute times - new_times = jra55_times(all_times[time_indices_in_memory], all_times[n₁]) - native_fts.times = new_times - - # Re-compute data - new_data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] - fts.times = new_times - - if !on_native_grid - copyto!(interior(native_fts, :, :, 1, :), new_data[:, :, :]) - fill_halo_regions!(native_fts) - interpolate!(fts, native_fts) - else - copyto!(interior(fts, :, :, 1, :), new_data[:, :, :]) - end - - m = 1 # reset - end - - set!(on_disk_fts, fts[m], n, fts.times[m]) - - n += 1 - end - - elapsed = 1e-9 * (time_ns() - start_clock) - elapsed_str = prettytime(elapsed) - @info " ... done ($elapsed_str)" * repeat(" ", 20) - - close(ds) - - user_fts = FieldTimeSeries(jld2_filepath, fts_name; architecture, backend, time_indexing) - fill_halo_regions!(user_fts) - - return user_fts -end - const AA = Oceananigans.Architectures.AbstractArchitecture JRA55_prescribed_atmosphere(time_indices=Colon(); kw...) = diff --git a/src/DataWrangling/jra55_field_time_series.jl b/src/DataWrangling/jra55_field_time_series.jl index 1d6eb6ee..281bb793 100644 --- a/src/DataWrangling/jra55_field_time_series.jl +++ b/src/DataWrangling/jra55_field_time_series.jl @@ -44,29 +44,32 @@ new_backend(::JRA55NetCDFBackend{N}, start, length) where N = JRA55ONetCDFBacken on_native_grid(::JRA55NetCDFBackend{N}) where N = N function set!(fts::JRA55NetCDFFTS, path::JRA55Metadata=fts.path, name::String=fts.name) - - backend = fts.backend - start = backend.start - - # Set the JRA55 dataset based on the backend!s - - 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 - end - + + # Do different things based on the Backend... + ds = Dataset(path) + + # Note that each file should have the variables + # - ds["time"]: time coordinate + # - ds["lon"]: longitude at the location of the variable + # - ds["lat"]: latitude at the location of the variable + # - ds["lon_bnds"]: bounding longitudes between which variables are averaged + # - ds["lat_bnds"]: bounding latitudes between which variables are averaged + # - ds[shortname]: the variable data + + # Nodes at the variable location + λc = ds["lon"][:] + φc = ds["lat"][:] + LX, LY, LZ = location(fts) + i₁, i₂, j₁, j₂, TX = compute_bounding_indices(nothing, nothing, fts.grid, LX, LY, λc, φc) + + ti = time_indices(fts) + ti = collect(ti) + data = ds[name][i₁:i₂, j₁:j₂, ti] + close(ds) + + copyto!(interior(fts, :, :, 1, :), data) fill_halo_regions!(fts) - + return nothing end @@ -79,10 +82,12 @@ end Create a field time series object for ECCO data. -# Arguments: +Arguments: +=========== - metadata: An ECCOMetadata object containing information about the ECCO dataset. -# Keyword Arguments: +Keyword Arguments: +===================== - 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()). @@ -92,9 +97,10 @@ function JRA55_field_time_series(metadata::JRA55Metadata; architecture = CPU(), backend = JRA55NetCDFBackend(20), time_indexing = Cyclical(), - grid = nothing) + grid = nothing, + latitude = nothing, + longitude = nothing) - # ECCO data is too chunky to allow other backends backend = if backend isa JRA55NetCDFBackend JRA55NetCDFBackend(backend.length; on_native_grid = isnothing(grid)) @@ -105,14 +111,57 @@ function JRA55_field_time_series(metadata::JRA55Metadata; # Making sure all the required individual files are downloaded download_dataset!(metadata) + # Note that each file should have the variables + # - ds["time"]: time coordinate + # - ds["lon"]: longitude at the location of the variable + # - ds["lat"]: latitude at the location of the variable + # - ds["lon_bnds"]: bounding longitudes between which variables are averaged + # - ds["lat_bnds"]: bounding latitudes between which variables are averaged + # - ds[shortname]: the variable data + + # Nodes at the variable location + λc = ds["lon"][:] + φc = ds["lat"][:] + + # Interfaces for the "native" JRA55 grid + λn = ds["lon_bnds"][1, :] + φn = ds["lat_bnds"][1, :] + + # The .nc coordinates lon_bnds and lat_bnds do not include + # the last interface, so we push them here. + push!(φn, 90) + push!(λn, λn[1] + 360) + + # TODO: support loading just part of the JRA55 data. + # Probably with arguments that take latitude, longitude bounds. + i₁, i₂, j₁, j₂, TX = compute_bounding_indices(longitude, latitude, grid, LX, LY, λc, φc) + + native_times = ds["time"][time_indices] + data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] + λr = λn[i₁:i₂+1] + φr = φn[j₁:j₂+1] + Nrx, Nry, Nt = size(data) + close(ds) + + N = (Nrx, Nry) + H = min.(N, (3, 3)) + + JRA55_native_grid = LatitudeLongitudeGrid(architecture, Float32; + halo = H, + size = N, + longitude = λr, + latitude = φr, + topology = (TX, Bounded, Flat)) + + boundary_conditions = FieldBoundaryConditions(JRA55_native_grid, (Center, Center, Nothing)) + location = field_location(metadata) shortname = short_name(metadata) - JRA55_native_grid = jra55_native_grid() boundary_conditions = FieldBoundaryConditions(JRA55_native_grid, location) times = native_times(metadata) - fts_grid = isnothing(grid) ? ECCO_native_grid : grid + fts_grid = isnothing(grid) ? JRA55_native_grid : grid fts = FieldTimeSeries{location...}(fts_grid, times; backend, @@ -127,140 +176,340 @@ function JRA55_field_time_series(metadata::JRA55Metadata; return fts end -ECCO_field_time_series(variable_name::Symbol, version=ECCO4Monthly(); kw...) = - ECCO_field_time_series(ECCOMetadata(variable_name, all_ecco_dates(version), version); kw...) - -# Variable names for restoreable data -struct Temperature end -struct Salinity end -struct UVelocity end -struct VVelocity end +JRA55_field_time_series(variable_name::Symbol, version=JRA55RepeatYear(); kw...) = + JRA55_field_time_series(Metadata(variable_name, all_dates(version), version); kw...) -oceananigans_fieldname = Dict( - :temperature => Temperature(), - :salinity => Salinity(), - :u_velocity => UVelocity(), - :v_velocity => VVelocity()) -@inline Base.getindex(fields, i, j, k, ::Temperature) = @inbounds fields.T[i, j, k] -@inline Base.getindex(fields, i, j, k, ::Salinity) = @inbounds fields.S[i, j, k] -@inline Base.getindex(fields, i, j, k, ::UVelocity) = @inbounds fields.u[i, j, k] -@inline Base.getindex(fields, i, j, k, ::VVelocity) = @inbounds fields.v[i, j, k] """ - struct ECCORestoring{FTS, G, M, V, N} <: Function + JRA55_field_time_series(variable_name; + architecture = CPU(), + location = nothing, + url = nothing, + filename = nothing, + shortname = nothing, + backend = InMemory(), + preprocess_chunk_size = 10, + preprocess_architecture = CPU(), + time_indices = nothing) + +Return a `FieldTimeSeries` containing atmospheric reanalysis data for `variable_name`, +which describes one of the variables in the "repeat year forcing" dataset derived +from the Japanese 55-year atmospheric reanalysis for driving ocean-sea-ice models (JRA55-do). +For more information about the derivation of the repeat year forcing dataset, see + +"Stewart et al., JRA55-do-based repeat year forcing datasets for driving ocean–sea-ice models", +Ocean Modelling, 2020, https://doi.org/10.1016/j.ocemod.2019.101557. + +The `variable_name`s (and their `shortname`s used in NetCDF files) +available from the JRA55-do are: + + - `:river_freshwater_flux` ("friver") + - `:rain_freshwater_flux` ("prra") + - `:snow_freshwater_flux` ("prsn") + - `:iceberg_freshwater_flux` ("licalvf") + - `:specific_humidity` ("huss") + - `:sea_level_pressure` ("psl") + - `:relative_humidity` ("rhuss") + - `:downwelling_longwave_radiation` ("rlds") + - `:downwelling_shortwave_radiation` ("rsds") + - `:temperature` ("ras") + - `:eastward_velocity` ("uas") + - `:northward_velocity` ("vas") + +Keyword arguments +================= + + - `architecture`: Architecture for the `FieldTimeSeries`. + Default: CPU() + + - `time_indices`: Indices of the timeseries to extract from file. + For example, `time_indices=1:3` returns a + `FieldTimeSeries` with the first three time snapshots + of `variable_name`. + + - `url`: The url accessed to download the data for `variable_name`. + Default: `ClimaOcean.JRA55.urls[variable_name]`. + + - `filename`: The name of the downloaded file. + Default: `ClimaOcean.JRA55.filenames[variable_name]`. + + - `shortname`: The "short name" of `variable_name` inside its NetCDF file. + Default: `ClimaOcean.JRA55.jra55_short_names[variable_name]`. + + - `interpolated_file`: file holding an Oceananigans compatible version of the JRA55 data. + If it does not exist it will be generated. + + - `time_chunks_in_memory`: number of fields held in memory. If `nothing` the whole timeseries is + loaded (not recommended). +""" +function JRA55_field_time_series(variable_name; + architecture = CPU(), + grid = nothing, + location = nothing, + url = nothing, + dir = download_jra55_cache, + filename = nothing, + shortname = nothing, + latitude = nothing, + longitude = nothing, + backend = InMemory(), + time_indexing = Cyclical(), + preprocess_chunk_size = 10, + preprocess_architecture = CPU(), + time_indices = nothing) + + # OnDisk backends do not support time interpolation! + # Disallow OnDisk for JRA55 dataset loading + if backend isa OnDisk + msg = string("We cannot load the JRA55 dataset with an `OnDisk` backend") + throw(ArgumentError(msg)) + end -A struct representing ECCO restoring. + if isnothing(filename) && !(variable_name ∈ JRA55_variable_names) + variable_strs = Tuple(" - :$name \n" for name in JRA55_variable_names) + variables_msg = prod(variable_strs) -# Fields -- `ecco_fts`: The ECCO FTS on the native ECCO grid. -- `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. -""" -struct ECCORestoring{FTS, G, M, V, N} <: Function - ecco_fts :: FTS - ecco_grid :: G - mask :: M - variable_name :: V - λ⁻¹ :: N -end + msg = string("The variable :$variable_name is not provided by the JRA55-do dataset!", '\n', + "The variables provided by the JRA55-do dataset are:", '\n', + variables_msg) -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.λ⁻¹)) + throw(ArgumentError(msg)) + end -@inline function (p::ECCORestoring)(i, j, k, grid, clock, fields) + filepath = isnothing(filename) ? joinpath(dir, filenames[variable_name]) : joinpath(dir, filename) + + if !isnothing(filename) && !isfile(filepath) && isnothing(url) + throw(ArgumentError("A filename was provided without a url, but the file does not exist.\n \ + If intended, please provide both the filename and url that should be used \n \ + to download the new file.")) + end + + isnothing(filename) && (filename = filenames[variable_name]) + isnothing(shortname) && (shortname = jra55_short_names[variable_name]) + isnothing(url) && (url = urls[variable_name]) + + # Record some important user decisions + totally_in_memory = backend isa TotallyInMemory + on_native_grid = isnothing(grid) + !on_native_grid && backend isa JRA55NetCDFBackend && error("Can't use custom grid with JRA55NetCDFBackend.") + + jld2_filepath = joinpath(dir, string("JRA55_repeat_year_", variable_name, ".jld2")) + fts_name = field_time_series_short_names[variable_name] + + # Note, we don't re-use existing jld2 files. + isfile(filepath) || download(url, filepath) + isfile(jld2_filepath) && rm(jld2_filepath) + + # Determine default time indices + if totally_in_memory + # In this case, the whole time series is in memory. + # Either the time series is short, or we are doing a limited-area + # simulation, like in a single column. So, we conservatively + # set a default `time_indices = 1:2`. + isnothing(time_indices) && (time_indices = 1:2) + time_indices_in_memory = time_indices + native_fts_architecture = architecture + else + # In this case, part or all of the time series will be stored in a file. + # Note: if the user has provided a grid, we will have to preprocess the + # .nc JRA55 data into a .jld2 file. In this case, `time_indices` refers + # to the time_indices that we will preprocess; + # by default we choose all of them. The architecture is only the + # architecture used for preprocessing, which typically will be CPU() + # even if we would like the final FieldTimeSeries on the GPU. + isnothing(time_indices) && (time_indices = :) + + if backend isa JRA55NetCDFBackend + time_indices_in_memory = 1:length(backend) + native_fts_architecture = architecture + else # then `time_indices_in_memory` refers to preprocessing + maximum_index = min(preprocess_chunk_size, length(time_indices)) + time_indices_in_memory = 1:maximum_index + native_fts_architecture = preprocess_architecture + end + end + + # Set a default location. + if isnothing(location) + LX = LY = Center + else + LX, LY = location + end + + ds = Dataset(filepath) + + # Note that each file should have the variables + # - ds["time"]: time coordinate + # - ds["lon"]: longitude at the location of the variable + # - ds["lat"]: latitude at the location of the variable + # - ds["lon_bnds"]: bounding longitudes between which variables are averaged + # - ds["lat_bnds"]: bounding latitudes between which variables are averaged + # - ds[shortname]: the variable data + + # Nodes at the variable location + λc = ds["lon"][:] + φc = ds["lat"][:] + + # Interfaces for the "native" JRA55 grid + λn = ds["lon_bnds"][1, :] + φn = ds["lat_bnds"][1, :] + + # The .nc coordinates lon_bnds and lat_bnds do not include + # the last interface, so we push them here. + push!(φn, 90) + push!(λn, λn[1] + 360) + + # TODO: support loading just part of the JRA55 data. + # Probably with arguments that take latitude, longitude bounds. + i₁, i₂, j₁, j₂, TX = compute_bounding_indices(longitude, latitude, grid, LX, LY, λc, φc) + + native_times = ds["time"][time_indices] + data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] + λr = λn[i₁:i₂+1] + φr = φn[j₁:j₂+1] + Nrx, Nry, Nt = size(data) + close(ds) + + N = (Nrx, Nry) + H = min.(N, (3, 3)) + + JRA55_native_grid = LatitudeLongitudeGrid(native_fts_architecture, Float32; + halo = H, + size = N, + longitude = λr, + latitude = φr, + topology = (TX, Bounded, Flat)) + + boundary_conditions = FieldBoundaryConditions(JRA55_native_grid, (Center, Center, Nothing)) + times = jra55_times(native_times) - # Figure out all the inputs: time, location, and node - time = Time(clock.time) - loc = location(p.ecco_fts) + if backend isa JRA55NetCDFBackend + fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; + backend, + time_indexing, + boundary_conditions, + path = filepath, + name = shortname) + + # Fill the data in a GPU-friendly manner + copyto!(interior(fts, :, :, 1, :), data) + fill_halo_regions!(fts) + + return fts + else + # Make times into an array for later preprocessing + if !totally_in_memory + times = collect(times) + end - # Retrieve the variable to force - @inbounds var = fields[i, j, k, p.variable_name] + native_fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; + time_indexing, + boundary_conditions) - ecco_backend = p.ecco_fts.backend - native_grid = on_native_grid(ecco_backend) + # Fill the data in a GPU-friendly manner + copyto!(interior(native_fts, :, :, 1, :), data) + fill_halo_regions!(native_fts) - ecco_var = get_ecco_variable(Val(native_grid), p.ecco_fts, i, j, k, p.ecco_grid, grid, time) + if on_native_grid && totally_in_memory + return native_fts - # Extracting the mask value at the current node - mask = stateindex(p.mask, i, j, k, grid, clock.time, loc) + elseif totally_in_memory # but not on the native grid! + boundary_conditions = FieldBoundaryConditions(grid, (LX, LY, Nothing)) + fts = FieldTimeSeries{LX, LY, Nothing}(grid, times; time_indexing, boundary_conditions) + interpolate!(fts, native_fts) + return fts + end + end - return p.λ⁻¹ * mask * (ecco_var - var) -end + @info "Pre-processing JRA55 $variable_name data into a JLD2 file..." -# Differentiating between restoring done with an ECCO FTS -# that lives on the native ecco grid, that requires interpolation in space -# _inside_ the restoring function and restoring based on an ECCO -# FTS defined on the model grid that requires only time interpolation -@inline function get_ecco_variable(::Val{true}, ecco_fts, i, j, k, ecco_grid, grid, time) - # Extracting the ECCO field time series data and parameters - ecco_times = ecco_fts.times - ecco_data = ecco_fts.data - ecco_time_indexing = ecco_fts.time_indexing - ecco_backend = ecco_fts.backend - ecco_location = instantiated_location(ecco_fts) + preprocessing_grid = on_native_grid ? JRA55_native_grid : grid - X = node(i, j, k, grid, ecco_location...) + # Re-open the dataset! + ds = Dataset(filepath) + all_datetimes = ds["time"][time_indices] + all_Nt = length(all_datetimes) - # Interpolating the ECCO field time series data onto the current node and time - return interpolate(X, time, ecco_data, ecco_location, ecco_grid, ecco_times, ecco_backend, ecco_time_indexing) -end + all_times = jra55_times(all_datetimes) -@inline get_ecco_variable(::Val{false}, ecco_fts, i, j, k, ecco_grid, grid, time) = @inbounds ecco_fts[i, j, k, time] + on_disk_fts = FieldTimeSeries{LX, LY, Nothing}(preprocessing_grid, all_times; + boundary_conditions, + backend = OnDisk(), + path = jld2_filepath, + name = fts_name) -""" - ECCO_restoring_forcing(metadata::ECCOMetadata; - architecture = CPU(), - backend = ECCONetCDFBackend(2), - time_indexing = Cyclical(), - mask = 1, - timescale = 5days) - -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. - -# Keyword Arguments: -==================== -- `architecture`: The architecture. Typically `CPU` or `GPU` -- `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) -- `mask`: The mask value. Can be a function of `(x, y, z, time)`, an array or a number -- `timescale`: The restoring timescale. -""" -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...) -end + # Save data to disk, one field at a time + start_clock = time_ns() + n = 1 # on disk + m = 0 # in memory -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) + times_in_memory = all_times[time_indices_in_memory] - ecco_fts = ECCO_field_time_series(metadata; grid, architecture, time_indices_in_memory, time_indexing) - ecco_grid = ecco_fts.grid + fts = FieldTimeSeries{LX, LY, Nothing}(preprocessing_grid, times_in_memory; + boundary_conditions, + backend = InMemory(), + path = jld2_filepath, + name = fts_name) - # 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) + # Re-compute data + new_data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] + + if !on_native_grid + copyto!(interior(native_fts, :, :, 1, :), new_data[:, :, :]) + fill_halo_regions!(native_fts) + interpolate!(fts, native_fts) + else + copyto!(interior(fts, :, :, 1, :), new_data[:, :, :]) + end + + while n <= all_Nt + print(" ... processing time index $n of $all_Nt \r") + + if time_indices_in_memory isa Colon || n ∈ time_indices_in_memory + m += 1 + else # load new data + # Update time_indices + time_indices_in_memory = time_indices_in_memory .+ preprocess_chunk_size + n₁ = first(time_indices_in_memory) + + # Clip time_indices if they extend past the end of the dataset + if last(time_indices_in_memory) > all_Nt + time_indices_in_memory = UnitRange(n₁, all_Nt) + end + + # Re-compute times + new_times = jra55_times(all_times[time_indices_in_memory], all_times[n₁]) + native_fts.times = new_times + + # Re-compute data + new_data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] + fts.times = new_times + + if !on_native_grid + copyto!(interior(native_fts, :, :, 1, :), new_data[:, :, :]) + fill_halo_regions!(native_fts) + interpolate!(fts, native_fts) + else + copyto!(interior(fts, :, :, 1, :), new_data[:, :, :]) + end + + m = 1 # reset + end + + set!(on_disk_fts, fts[m], n, fts.times[m]) - return restoring_forcing -end \ No newline at end of file + n += 1 + end + + elapsed = 1e-9 * (time_ns() - start_clock) + elapsed_str = prettytime(elapsed) + @info " ... done ($elapsed_str)" * repeat(" ", 20) + + close(ds) + + user_fts = FieldTimeSeries(jld2_filepath, fts_name; architecture, backend, time_indexing) + fill_halo_regions!(user_fts) + + return user_fts +end From 1dab6e266147dabffb42456bc3d7a191f1c6c3ef Mon Sep 17 00:00:00 2001 From: Simone Silvestri <33547697+simone-silvestri@users.noreply.github.com> Date: Fri, 26 Jul 2024 14:45:38 -0400 Subject: [PATCH 003/104] comment --- src/DataWrangling/jra55_field_time_series.jl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/DataWrangling/jra55_field_time_series.jl b/src/DataWrangling/jra55_field_time_series.jl index 281bb793..b4aa02ac 100644 --- a/src/DataWrangling/jra55_field_time_series.jl +++ b/src/DataWrangling/jra55_field_time_series.jl @@ -80,11 +80,11 @@ end time_indexing = Cyclical(), grid = nothing) -Create a field time series object for ECCO data. +Create a field time series object for JRA55 data. Arguments: =========== -- metadata: An ECCOMetadata object containing information about the ECCO dataset. +- metadata: An JRA55Metadata object containing information about the JRA55 dataset. Keyword Arguments: ===================== @@ -163,11 +163,17 @@ function JRA55_field_time_series(metadata::JRA55Metadata; fts_grid = isnothing(grid) ? JRA55_native_grid : grid + path = if backend isa JRA55NetCDFBackend + metadata + else + file_path + end + fts = FieldTimeSeries{location...}(fts_grid, times; backend, time_indexing, boundary_conditions, - path = metadata, + path, name = shortname) # Let's set the data From 9d11295d5d61301616cdad822448adfa61a0cfa8 Mon Sep 17 00:00:00 2001 From: simone-silvestri Date: Thu, 1 Aug 2024 13:46:11 -0400 Subject: [PATCH 004/104] add some changes --- src/DataWrangling/jra55_field_time_series.jl | 308 +------------------ src/DataWrangling/jra55_metadata.jl | 9 +- 2 files changed, 22 insertions(+), 295 deletions(-) diff --git a/src/DataWrangling/jra55_field_time_series.jl b/src/DataWrangling/jra55_field_time_series.jl index b4aa02ac..649001d4 100644 --- a/src/DataWrangling/jra55_field_time_series.jl +++ b/src/DataWrangling/jra55_field_time_series.jl @@ -46,7 +46,8 @@ on_native_grid(::JRA55NetCDFBackend{N}) where N = N function set!(fts::JRA55NetCDFFTS, path::JRA55Metadata=fts.path, name::String=fts.name) # Do different things based on the Backend... - ds = Dataset(path) + filename = metadata_filename(path) + ds = filename isa String ? Dataset(filename) : Dataset(filename[1]) # Note that each file should have the variables # - ds["time"]: time coordinate @@ -63,11 +64,14 @@ function set!(fts::JRA55NetCDFFTS, path::JRA55Metadata=fts.path, name::String=ft i₁, i₂, j₁, j₂, TX = compute_bounding_indices(nothing, nothing, fts.grid, LX, LY, λc, φc) ti = time_indices(fts) - ti = collect(ti) - data = ds[name][i₁:i₂, j₁:j₂, ti] - close(ds) - copyto!(interior(fts, :, :, 1, :), data) + if on_native_grid(fts.backend) + ti = collect(ti) + data = ds[name][i₁:i₂, j₁:j₂, ti] + copyto!(interior(fts, :, :, 1, :), data) + end + + close(ds) fill_halo_regions!(fts) return nothing @@ -95,22 +99,20 @@ Keyword Arguments: """ function JRA55_field_time_series(metadata::JRA55Metadata; architecture = CPU(), - backend = JRA55NetCDFBackend(20), + time_indices_in_memory = 2, time_indexing = Cyclical(), grid = nothing, latitude = nothing, longitude = nothing) - backend = if backend isa JRA55NetCDFBackend - JRA55NetCDFBackend(backend.length; - on_native_grid = isnothing(grid)) - else - backend - end + backend = JRA55NetCDFBackend(time_indices_in_memory; on_native_grid = isnothing(grid)) # Making sure all the required individual files are downloaded download_dataset!(metadata) + filename = metadata_filename(metadata) + ds = Dataset(filename) + # Note that each file should have the variables # - ds["time"]: time coordinate # - ds["lon"]: longitude at the location of the variable @@ -163,18 +165,14 @@ function JRA55_field_time_series(metadata::JRA55Metadata; fts_grid = isnothing(grid) ? JRA55_native_grid : grid - path = if backend isa JRA55NetCDFBackend - metadata - else - file_path - end - + path = metadata + fts = FieldTimeSeries{location...}(fts_grid, times; backend, time_indexing, boundary_conditions, path, - name = shortname) + name = metadata.name) # Let's set the data set!(fts) @@ -185,8 +183,6 @@ end JRA55_field_time_series(variable_name::Symbol, version=JRA55RepeatYear(); kw...) = JRA55_field_time_series(Metadata(variable_name, all_dates(version), version); kw...) - - """ JRA55_field_time_series(variable_name; architecture = CPU(), @@ -249,273 +245,3 @@ Keyword arguments - `time_chunks_in_memory`: number of fields held in memory. If `nothing` the whole timeseries is loaded (not recommended). """ -function JRA55_field_time_series(variable_name; - architecture = CPU(), - grid = nothing, - location = nothing, - url = nothing, - dir = download_jra55_cache, - filename = nothing, - shortname = nothing, - latitude = nothing, - longitude = nothing, - backend = InMemory(), - time_indexing = Cyclical(), - preprocess_chunk_size = 10, - preprocess_architecture = CPU(), - time_indices = nothing) - - # OnDisk backends do not support time interpolation! - # Disallow OnDisk for JRA55 dataset loading - if backend isa OnDisk - msg = string("We cannot load the JRA55 dataset with an `OnDisk` backend") - throw(ArgumentError(msg)) - end - - if isnothing(filename) && !(variable_name ∈ JRA55_variable_names) - variable_strs = Tuple(" - :$name \n" for name in JRA55_variable_names) - variables_msg = prod(variable_strs) - - msg = string("The variable :$variable_name is not provided by the JRA55-do dataset!", '\n', - "The variables provided by the JRA55-do dataset are:", '\n', - variables_msg) - - throw(ArgumentError(msg)) - end - - filepath = isnothing(filename) ? joinpath(dir, filenames[variable_name]) : joinpath(dir, filename) - - if !isnothing(filename) && !isfile(filepath) && isnothing(url) - throw(ArgumentError("A filename was provided without a url, but the file does not exist.\n \ - If intended, please provide both the filename and url that should be used \n \ - to download the new file.")) - end - - isnothing(filename) && (filename = filenames[variable_name]) - isnothing(shortname) && (shortname = jra55_short_names[variable_name]) - isnothing(url) && (url = urls[variable_name]) - - # Record some important user decisions - totally_in_memory = backend isa TotallyInMemory - on_native_grid = isnothing(grid) - !on_native_grid && backend isa JRA55NetCDFBackend && error("Can't use custom grid with JRA55NetCDFBackend.") - - jld2_filepath = joinpath(dir, string("JRA55_repeat_year_", variable_name, ".jld2")) - fts_name = field_time_series_short_names[variable_name] - - # Note, we don't re-use existing jld2 files. - isfile(filepath) || download(url, filepath) - isfile(jld2_filepath) && rm(jld2_filepath) - - # Determine default time indices - if totally_in_memory - # In this case, the whole time series is in memory. - # Either the time series is short, or we are doing a limited-area - # simulation, like in a single column. So, we conservatively - # set a default `time_indices = 1:2`. - isnothing(time_indices) && (time_indices = 1:2) - time_indices_in_memory = time_indices - native_fts_architecture = architecture - else - # In this case, part or all of the time series will be stored in a file. - # Note: if the user has provided a grid, we will have to preprocess the - # .nc JRA55 data into a .jld2 file. In this case, `time_indices` refers - # to the time_indices that we will preprocess; - # by default we choose all of them. The architecture is only the - # architecture used for preprocessing, which typically will be CPU() - # even if we would like the final FieldTimeSeries on the GPU. - isnothing(time_indices) && (time_indices = :) - - if backend isa JRA55NetCDFBackend - time_indices_in_memory = 1:length(backend) - native_fts_architecture = architecture - else # then `time_indices_in_memory` refers to preprocessing - maximum_index = min(preprocess_chunk_size, length(time_indices)) - time_indices_in_memory = 1:maximum_index - native_fts_architecture = preprocess_architecture - end - end - - # Set a default location. - if isnothing(location) - LX = LY = Center - else - LX, LY = location - end - - ds = Dataset(filepath) - - # Note that each file should have the variables - # - ds["time"]: time coordinate - # - ds["lon"]: longitude at the location of the variable - # - ds["lat"]: latitude at the location of the variable - # - ds["lon_bnds"]: bounding longitudes between which variables are averaged - # - ds["lat_bnds"]: bounding latitudes between which variables are averaged - # - ds[shortname]: the variable data - - # Nodes at the variable location - λc = ds["lon"][:] - φc = ds["lat"][:] - - # Interfaces for the "native" JRA55 grid - λn = ds["lon_bnds"][1, :] - φn = ds["lat_bnds"][1, :] - - # The .nc coordinates lon_bnds and lat_bnds do not include - # the last interface, so we push them here. - push!(φn, 90) - push!(λn, λn[1] + 360) - - # TODO: support loading just part of the JRA55 data. - # Probably with arguments that take latitude, longitude bounds. - i₁, i₂, j₁, j₂, TX = compute_bounding_indices(longitude, latitude, grid, LX, LY, λc, φc) - - native_times = ds["time"][time_indices] - data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] - λr = λn[i₁:i₂+1] - φr = φn[j₁:j₂+1] - Nrx, Nry, Nt = size(data) - close(ds) - - N = (Nrx, Nry) - H = min.(N, (3, 3)) - - JRA55_native_grid = LatitudeLongitudeGrid(native_fts_architecture, Float32; - halo = H, - size = N, - longitude = λr, - latitude = φr, - topology = (TX, Bounded, Flat)) - - boundary_conditions = FieldBoundaryConditions(JRA55_native_grid, (Center, Center, Nothing)) - times = jra55_times(native_times) - - if backend isa JRA55NetCDFBackend - fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; - backend, - time_indexing, - boundary_conditions, - path = filepath, - name = shortname) - - # Fill the data in a GPU-friendly manner - copyto!(interior(fts, :, :, 1, :), data) - fill_halo_regions!(fts) - - return fts - else - # Make times into an array for later preprocessing - if !totally_in_memory - times = collect(times) - end - - native_fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; - time_indexing, - boundary_conditions) - - # Fill the data in a GPU-friendly manner - copyto!(interior(native_fts, :, :, 1, :), data) - fill_halo_regions!(native_fts) - - if on_native_grid && totally_in_memory - return native_fts - - elseif totally_in_memory # but not on the native grid! - boundary_conditions = FieldBoundaryConditions(grid, (LX, LY, Nothing)) - fts = FieldTimeSeries{LX, LY, Nothing}(grid, times; time_indexing, boundary_conditions) - interpolate!(fts, native_fts) - return fts - end - end - - @info "Pre-processing JRA55 $variable_name data into a JLD2 file..." - - preprocessing_grid = on_native_grid ? JRA55_native_grid : grid - - # Re-open the dataset! - ds = Dataset(filepath) - all_datetimes = ds["time"][time_indices] - all_Nt = length(all_datetimes) - - all_times = jra55_times(all_datetimes) - - on_disk_fts = FieldTimeSeries{LX, LY, Nothing}(preprocessing_grid, all_times; - boundary_conditions, - backend = OnDisk(), - path = jld2_filepath, - name = fts_name) - - # Save data to disk, one field at a time - start_clock = time_ns() - n = 1 # on disk - m = 0 # in memory - - times_in_memory = all_times[time_indices_in_memory] - - fts = FieldTimeSeries{LX, LY, Nothing}(preprocessing_grid, times_in_memory; - boundary_conditions, - backend = InMemory(), - path = jld2_filepath, - name = fts_name) - - # Re-compute data - new_data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] - - if !on_native_grid - copyto!(interior(native_fts, :, :, 1, :), new_data[:, :, :]) - fill_halo_regions!(native_fts) - interpolate!(fts, native_fts) - else - copyto!(interior(fts, :, :, 1, :), new_data[:, :, :]) - end - - while n <= all_Nt - print(" ... processing time index $n of $all_Nt \r") - - if time_indices_in_memory isa Colon || n ∈ time_indices_in_memory - m += 1 - else # load new data - # Update time_indices - time_indices_in_memory = time_indices_in_memory .+ preprocess_chunk_size - n₁ = first(time_indices_in_memory) - - # Clip time_indices if they extend past the end of the dataset - if last(time_indices_in_memory) > all_Nt - time_indices_in_memory = UnitRange(n₁, all_Nt) - end - - # Re-compute times - new_times = jra55_times(all_times[time_indices_in_memory], all_times[n₁]) - native_fts.times = new_times - - # Re-compute data - new_data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] - fts.times = new_times - - if !on_native_grid - copyto!(interior(native_fts, :, :, 1, :), new_data[:, :, :]) - fill_halo_regions!(native_fts) - interpolate!(fts, native_fts) - else - copyto!(interior(fts, :, :, 1, :), new_data[:, :, :]) - end - - m = 1 # reset - end - - set!(on_disk_fts, fts[m], n, fts.times[m]) - - n += 1 - end - - elapsed = 1e-9 * (time_ns() - start_clock) - elapsed_str = prettytime(elapsed) - @info " ... done ($elapsed_str)" * repeat(" ", 20) - - close(ds) - - user_fts = FieldTimeSeries(jld2_filepath, fts_name; architecture, backend, time_indexing) - fill_halo_regions!(user_fts) - - return user_fts -end diff --git a/src/DataWrangling/jra55_metadata.jl b/src/DataWrangling/jra55_metadata.jl index e9b84fe9..75a8dfc3 100644 --- a/src/DataWrangling/jra55_metadata.jl +++ b/src/DataWrangling/jra55_metadata.jl @@ -30,7 +30,7 @@ end # Convenience functions short_name(data::JRA55Metadata) = jra55_short_names[data.name] -field_location(data::JRA55Metadata) = jra55_location[data.name] +field_location(data::JRA55Metadata) = (Center, Center, Center) # A list of all variables provided in the JRA55 dataset: JRA55_variable_names = (:river_freshwater_flux, @@ -91,7 +91,7 @@ field_time_series_short_names = Dict( :northward_velocity => "va", # Northward near-surface wind ) -urls = Dict( +jra55_repeat_year_urls = Dict( :shortwave_radiation => "https://www.dropbox.com/scl/fi/z6fkvmd9oe3ycmaxta131/" * "RYF.rsds.1990_1991.nc?rlkey=r7q6zcbj6a4fxsq0f8th7c4tc&dl=0", @@ -135,10 +135,12 @@ urls = Dict( variable_is_three_dimensional(data::JRA55Metadata) = false # URLs for the JRA55 datasets specific to each version -function urls(metadata::JRA55Metadata) +function urls(metadata::Metadata{<:Any, <:JRA55MultipleYears}) return "https://ecco.jpl.nasa.gov/drive/files/ECCO2/cube92_latlon_quart_90S90N/monthly/" end +urls(metadata::Metadata{<:Any, <:JRA55RepeatYear}) = jra55_repeat_year_urls[metadata.name] + function download_dataset!(metadata::JRA55Metadata; url = urls(metadata)) @@ -147,7 +149,6 @@ function download_dataset!(metadata::JRA55Metadata; shortname = short_name(data) if !isfile(filename) - fileurl = joinpath(url, shortname, year, filename) download(url, filepath) end end From 7f4af8822759dca18384ad545c7e54b0f32e34fe Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Fri, 8 Nov 2024 15:37:27 +0100 Subject: [PATCH 005/104] in their folder --- src/DataWrangling/{ => JRA55}/JRA55.jl | 0 src/DataWrangling/{ => JRA55}/jra55_field_time_series.jl | 0 src/DataWrangling/{ => JRA55}/jra55_metadata.jl | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/DataWrangling/{ => JRA55}/JRA55.jl (100%) rename src/DataWrangling/{ => JRA55}/jra55_field_time_series.jl (100%) rename src/DataWrangling/{ => JRA55}/jra55_metadata.jl (100%) diff --git a/src/DataWrangling/JRA55.jl b/src/DataWrangling/JRA55/JRA55.jl similarity index 100% rename from src/DataWrangling/JRA55.jl rename to src/DataWrangling/JRA55/JRA55.jl diff --git a/src/DataWrangling/jra55_field_time_series.jl b/src/DataWrangling/JRA55/jra55_field_time_series.jl similarity index 100% rename from src/DataWrangling/jra55_field_time_series.jl rename to src/DataWrangling/JRA55/jra55_field_time_series.jl diff --git a/src/DataWrangling/jra55_metadata.jl b/src/DataWrangling/JRA55/jra55_metadata.jl similarity index 100% rename from src/DataWrangling/jra55_metadata.jl rename to src/DataWrangling/JRA55/jra55_metadata.jl From 8d9401e953ccbcb437c736b7cef641db0042c51b Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Fri, 8 Nov 2024 15:45:41 +0100 Subject: [PATCH 006/104] modernize --- src/DataWrangling/DataWrangling.jl | 2 +- src/DataWrangling/ECCO/ECCO_metadata.jl | 41 +- src/DataWrangling/JRA55/JRA55.jl | 573 +----------------- .../JRA55/JRA55_prescribed_atmosphere.jl | 92 +++ .../JRA55/jra55_field_time_series.jl | 502 ++++++++++----- src/DataWrangling/JRA55/jra55_metadata.jl | 6 +- src/DataWrangling/ecco_metadata.jl | 140 ----- src/DataWrangling/metadata.jl | 38 +- 8 files changed, 494 insertions(+), 900 deletions(-) create mode 100644 src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl delete mode 100644 src/DataWrangling/ecco_metadata.jl diff --git a/src/DataWrangling/DataWrangling.jl b/src/DataWrangling/DataWrangling.jl index c0b95688..d118b9cb 100644 --- a/src/DataWrangling/DataWrangling.jl +++ b/src/DataWrangling/DataWrangling.jl @@ -73,7 +73,7 @@ end include("metadata.jl") include("inpaint_mask.jl") -include("JRA55.jl") +include("JRA55/JRA55.jl") include("ECCO/ECCO.jl") using .ECCO diff --git a/src/DataWrangling/ECCO/ECCO_metadata.jl b/src/DataWrangling/ECCO/ECCO_metadata.jl index 8d933f1f..6d3bcb98 100644 --- a/src/DataWrangling/ECCO/ECCO_metadata.jl +++ b/src/DataWrangling/ECCO/ECCO_metadata.jl @@ -10,21 +10,7 @@ struct ECCO2Monthly end struct ECCO2Daily end struct ECCO4Monthly end -""" - ECCOMetadata{D, V} - -Metadata holding the ECCO dataset information: -- `name`: The name of the dataset. -- `dates`: The dates of the dataset, in a `AbstractCFDateTime` format. -- `version`: The version of the dataset, could be ECCO2Monthly, ECCO2Daily, or ECCO4Monthly. -- `dir`: The directory where the dataset is stored. -""" -struct ECCOMetadata{D, V} - name :: Symbol - dates :: D - version :: V - dir :: String -end +const ECCOMetadata{D, V} = Union{Metadata{T, V<:ECCO4Monthly}, Metadata{T, V<:ECCO2Daily}, Metadata{T, V<:ECCO2Monthly}} where {T, V} Base.show(io::IO, metadata::ECCOMetadata) = print(io, "ECCOMetadata:", '\n', @@ -60,32 +46,11 @@ function ECCOMetadata(name::Symbol; version = ECCO4Monthly(), dir = download_ECCO_cache) - return ECCOMetadata(name, dates, version, dir) + return Metadata(name, dates, version, dir) end ECCOMetadata(name::Symbol, date, version=ECCO4Monthly(); dir=download_ECCO_cache) = - ECCOMetadata(name, date, version, dir) - -# Treat ECCOMetadata as an array to allow iteration over the dates. -Base.length(metadata::ECCOMetadata) = length(metadata.dates) -Base.eltype(metadata::ECCOMetadata) = Base.eltype(metadata.dates) -@propagate_inbounds Base.getindex(m::ECCOMetadata, i::Int) = ECCOMetadata(m.name, m.dates[i], m.version, m.dir) -@propagate_inbounds Base.first(m::ECCOMetadata) = ECCOMetadata(m.name, m.dates[1], m.version, m.dir) -@propagate_inbounds Base.last(m::ECCOMetadata) = ECCOMetadata(m.name, m.dates[end], m.version, m.dir) - -@inline function Base.iterate(m::ECCOMetadata, i=1) - if (i % UInt) - 1 < length(m) - return ECCOMetadata(m.name, m.dates[i], m.version, m.dir), i + 1 - else - return nothing - end -end - -Base.axes(metadata::ECCOMetadata{<:AbstractCFDateTime}) = 1 -Base.first(metadata::ECCOMetadata{<:AbstractCFDateTime}) = metadata -Base.last(metadata::ECCOMetadata{<:AbstractCFDateTime}) = metadata -Base.iterate(metadata::ECCOMetadata{<:AbstractCFDateTime}) = (metadata, nothing) -Base.iterate(::ECCOMetadata{<:AbstractCFDateTime}, ::Any) = nothing + Metadata(name, date, version, dir) Base.size(data::ECCOMetadata{<:Any, <:ECCO2Daily}) = (1440, 720, 50, length(data.dates)) Base.size(data::ECCOMetadata{<:Any, <:ECCO2Monthly}) = (1440, 720, 50, length(data.dates)) diff --git a/src/DataWrangling/JRA55/JRA55.jl b/src/DataWrangling/JRA55/JRA55.jl index 3266f2d0..069b58bd 100644 --- a/src/DataWrangling/JRA55/JRA55.jl +++ b/src/DataWrangling/JRA55/JRA55.jl @@ -1,5 +1,7 @@ module JRA55 +export JRA55_field_time_series, JRA55_prescribed_atmosphere + using Oceananigans using Oceananigans.Units @@ -26,573 +28,8 @@ import Oceananigans.Fields: set! import Oceananigans.OutputReaders: new_backend, update_field_time_series! using Downloads: download -include("jra55_metadata.jl") - -download_jra55_cache::String = "" -function __init__() - global download_jra55_cache = @get_scratch!("JRA55") -end - -compute_bounding_nodes(::Nothing, ::Nothing, LH, hnodes) = nothing -compute_bounding_nodes(bounds, ::Nothing, LH, hnodes) = bounds - -function compute_bounding_nodes(x::Number, ::Nothing, LH, hnodes) - ϵ = convert(typeof(x), 0.001) # arbitrary? - return (x - ϵ, x + ϵ) -end - -# TODO: remove the allowscalar -function compute_bounding_nodes(::Nothing, grid, LH, hnodes) - hg = hnodes(grid, LH()) - h₁ = @allowscalar minimum(hg) - h₂ = @allowscalar maximum(hg) - return h₁, h₂ -end - -function compute_bounding_indices(::Nothing, hc) - Nh = length(hc) - return 1, Nh -end - -function compute_bounding_indices(bounds::Tuple, hc) - h₁, h₂ = bounds - Nh = length(hc) - - # The following should work. If ᵒ are the extrema of nodes we want to - # interpolate to, and the following is a sketch of the JRA55 native grid, - # - # 1 2 3 4 5 - # | | | | | | - # | x ᵒ | x | x | x ᵒ | x | - # | | | | | | - # 1 2 3 4 5 6 - # - # then for example, we should find that (iᵢ, i₂) = (1, 5). - # So we want to reduce the first index by one, and limit them - # both by the available data. There could be some mismatch due - # to the use of different coordinate systems (ie whether λ ∈ (0, 360) - # which we may also need to handle separately. - i₁ = searchsortedfirst(hc, h₁) - i₂ = searchsortedfirst(hc, h₂) - i₁ = max(1, i₁ - 1) - i₂ = min(Nh, i₂) - - return i₁, i₂ -end - -infer_longitudinal_topology(::Nothing) = Periodic - -function infer_longitudinal_topology(λbounds) - λ₁, λ₂ = λbounds - TX = λ₂ - λ₁ ≈ 360 ? Periodic : Bounded - return TX -end - -function compute_bounding_indices(longitude, latitude, grid, LX, LY, λc, φc) - λbounds = compute_bounding_nodes(longitude, grid, LX, λnodes) - φbounds = compute_bounding_nodes(latitude, grid, LY, φnodes) - - i₁, i₂ = compute_bounding_indices(λbounds, λc) - j₁, j₂ = compute_bounding_indices(φbounds, φc) - TX = infer_longitudinal_topology(λbounds) - - return i₁, i₂, j₁, j₂, TX -end - -# Convert dates to range until Oceananigans supports dates natively -function jra55_times(native_times, start_time=native_times[1]) - - times = [] - for native_time in native_times - time = native_time - start_time - time = Second(time).value - push!(times, time) - end - - return times -end - -struct JRA55NetCDFBackend <: AbstractInMemoryBackend{Int} - start :: Int - length :: Int -end - -""" - JRA55NetCDFBackend(length) - -Represents a JRA55 FieldTimeSeries backed by JRA55 native .nc files. -""" -JRA55NetCDFBackend(length) = JRA55NetCDFBackend(1, length) - -Base.length(backend::JRA55NetCDFBackend) = backend.length -Base.summary(backend::JRA55NetCDFBackend) = string("JRA55NetCDFBackend(", backend.start, ", ", backend.length, ")") - -const JRA55NetCDFFTS = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend} - -function set!(fts::JRA55NetCDFFTS, path::String=fts.path, name::String=fts.name) - - ds = Dataset(path) - - # Note that each file should have the variables - # - ds["time"]: time coordinate - # - ds["lon"]: longitude at the location of the variable - # - ds["lat"]: latitude at the location of the variable - # - ds["lon_bnds"]: bounding longitudes between which variables are averaged - # - ds["lat_bnds"]: bounding latitudes between which variables are averaged - # - ds[shortname]: the variable data - - # Nodes at the variable location - λc = ds["lon"][:] - φc = ds["lat"][:] - LX, LY, LZ = location(fts) - i₁, i₂, j₁, j₂, TX = compute_bounding_indices(nothing, nothing, fts.grid, LX, LY, λc, φc) - - ti = time_indices(fts) - ti = collect(ti) - data = ds[name][i₁:i₂, j₁:j₂, ti] - close(ds) - - copyto!(interior(fts, :, :, 1, :), data) - fill_halo_regions!(fts) - - return nothing -end - -new_backend(::JRA55NetCDFBackend, start, length) = JRA55NetCDFBackend(start, length) - -""" - JRA55_field_time_series(variable_name; - architecture = CPU(), - time_indices = nothing, - latitude = nothing, - longitude = nothing, - location = nothing, - url = nothing, - filename = nothing, - shortname = nothing, - backend = InMemory(), - preprocess_chunk_size = 10, - preprocess_architecture = CPU()) - -Return a `FieldTimeSeries` containing atmospheric reanalysis data for `variable_name`, -which describes one of the variables in the "repeat year forcing" dataset derived -from the Japanese 55-year atmospheric reanalysis for driving ocean-sea-ice models (JRA55-do). -For more information about the derivation of the repeat year forcing dataset, see - -"Stewart et al., JRA55-do-based repeat year forcing datasets for driving ocean–sea-ice models", -Ocean Modelling, 2020, https://doi.org/10.1016/j.ocemod.2019.101557. - -The `variable_name`s (and their `shortname`s used in NetCDF files) -available from the JRA55-do are: - - - `:river_freshwater_flux` ("friver") - - `:rain_freshwater_flux` ("prra") - - `:snow_freshwater_flux` ("prsn") - - `:iceberg_freshwater_flux` ("licalvf") - - `:specific_humidity` ("huss") - - `:sea_level_pressure` ("psl") - - `:relative_humidity` ("rhuss") - - `:downwelling_longwave_radiation` ("rlds") - - `:downwelling_shortwave_radiation` ("rsds") - - `:temperature` ("ras") - - `:eastward_velocity` ("uas") - - `:northward_velocity` ("vas") - -Keyword arguments -================= - - - `architecture`: Architecture for the `FieldTimeSeries`. - Default: CPU() - - - `time_indices`: Indices of the timeseries to extract from file. - For example, `time_indices=1:3` returns a - `FieldTimeSeries` with the first three time snapshots - of `variable_name`. - - - `latitude`: Guiding latitude bounds for the resulting grid. - Used to slice the data when loading into memory. - Default: nothing, which retains the latitude range of the native grid. - - - `longitude`: Guiding longitude bounds for the resulting grid. - Used to slice the data when loading into memory. - Default: nothing, which retains the longitude range of the native grid. - - - `url`: The url accessed to download the data for `variable_name`. - Default: `ClimaOcean.JRA55.urls[variable_name]`. - - - `filename`: The name of the downloaded file. - Default: `ClimaOcean.JRA55.filenames[variable_name]`. - - - `shortname`: The "short name" of `variable_name` inside its NetCDF file. - Default: `ClimaOcean.JRA55.jra55_short_names[variable_name]`. - - - `interpolated_file`: file holding an Oceananigans compatible version of the JRA55 data. - If it does not exist it will be generated. - - - `time_chunks_in_memory`: number of fields held in memory. If `nothing` the whole timeseries is - loaded (not recommended). -""" -function JRA55_field_time_series(variable_name; - architecture = CPU(), - grid = nothing, - location = nothing, - url = nothing, - dir = download_jra55_cache, - filename = nothing, - shortname = nothing, - latitude = nothing, - longitude = nothing, - backend = InMemory(), - time_indexing = Cyclical(), - preprocess_chunk_size = 10, - preprocess_architecture = CPU(), - time_indices = nothing) - - # OnDisk backends do not support time interpolation! - # Disallow OnDisk for JRA55 dataset loading - if backend isa OnDisk - msg = string("We cannot load the JRA55 dataset with an `OnDisk` backend") - throw(ArgumentError(msg)) - end - - if isnothing(filename) && !(variable_name ∈ JRA55_variable_names) - variable_strs = Tuple(" - :$name \n" for name in JRA55_variable_names) - variables_msg = prod(variable_strs) - - msg = string("The variable :$variable_name is not provided by the JRA55-do dataset!", '\n', - "The variables provided by the JRA55-do dataset are:", '\n', - variables_msg) - - throw(ArgumentError(msg)) - end - - filepath = isnothing(filename) ? joinpath(dir, filenames[variable_name]) : joinpath(dir, filename) - - if !isnothing(filename) && !isfile(filepath) && isnothing(url) - throw(ArgumentError("A filename was provided without a url, but the file does not exist.\n \ - If intended, please provide both the filename and url that should be used \n \ - to download the new file.")) - end - - isnothing(filename) && (filename = filenames[variable_name]) - isnothing(shortname) && (shortname = jra55_short_names[variable_name]) - isnothing(url) && (url = urls[variable_name]) - - # Record some important user decisions - totally_in_memory = backend isa TotallyInMemory - on_native_grid = isnothing(grid) - !on_native_grid && backend isa JRA55NetCDFBackend && error("Can't use custom grid with JRA55NetCDFBackend.") - - jld2_filepath = joinpath(dir, string("JRA55_repeat_year_", variable_name, ".jld2")) - fts_name = field_time_series_short_names[variable_name] - - # Note, we don't re-use existing jld2 files. - isfile(filepath) || download(url, filepath) - isfile(jld2_filepath) && rm(jld2_filepath) - - # Determine default time indices - if totally_in_memory - # In this case, the whole time series is in memory. - # Either the time series is short, or we are doing a limited-area - # simulation, like in a single column. So, we conservatively - # set a default `time_indices = 1:2`. - isnothing(time_indices) && (time_indices = 1:2) - time_indices_in_memory = time_indices - native_fts_architecture = architecture - else - # In this case, part or all of the time series will be stored in a file. - # Note: if the user has provided a grid, we will have to preprocess the - # .nc JRA55 data into a .jld2 file. In this case, `time_indices` refers - # to the time_indices that we will preprocess; - # by default we choose all of them. The architecture is only the - # architecture used for preprocessing, which typically will be CPU() - # even if we would like the final FieldTimeSeries on the GPU. - isnothing(time_indices) && (time_indices = :) - - if backend isa JRA55NetCDFBackend - time_indices_in_memory = 1:length(backend) - native_fts_architecture = architecture - else # then `time_indices_in_memory` refers to preprocessing - maximum_index = min(preprocess_chunk_size, length(time_indices)) - time_indices_in_memory = 1:maximum_index - native_fts_architecture = preprocess_architecture - end - end - - # Set a default location. - if isnothing(location) - LX = LY = Center - else - LX, LY = location - end - - ds = Dataset(filepath) - - # Note that each file should have the variables - # - ds["time"]: time coordinate - # - ds["lon"]: longitude at the location of the variable - # - ds["lat"]: latitude at the location of the variable - # - ds["lon_bnds"]: bounding longitudes between which variables are averaged - # - ds["lat_bnds"]: bounding latitudes between which variables are averaged - # - ds[shortname]: the variable data - - # Nodes at the variable location - λc = ds["lon"][:] - φc = ds["lat"][:] - - # Interfaces for the "native" JRA55 grid - λn = ds["lon_bnds"][1, :] - φn = ds["lat_bnds"][1, :] - - # The .nc coordinates lon_bnds and lat_bnds do not include - # the last interface, so we push them here. - push!(φn, 90) - push!(λn, λn[1] + 360) - - # TODO: support loading just part of the JRA55 data. - # Probably with arguments that take latitude, longitude bounds. - i₁, i₂, j₁, j₂, TX = compute_bounding_indices(longitude, latitude, grid, LX, LY, λc, φc) - - native_times = ds["time"][time_indices] - data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] - λr = λn[i₁:i₂+1] - φr = φn[j₁:j₂+1] - Nrx, Nry, Nt = size(data) - close(ds) - - N = (Nrx, Nry) - H = min.(N, (3, 3)) - - JRA55_native_grid = LatitudeLongitudeGrid(native_fts_architecture, Float32; - halo = H, - size = N, - longitude = λr, - latitude = φr, - topology = (TX, Bounded, Flat)) - - boundary_conditions = FieldBoundaryConditions(JRA55_native_grid, (Center, Center, Nothing)) - times = jra55_times(native_times) - - if backend isa JRA55NetCDFBackend - fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; - backend, - time_indexing, - boundary_conditions, - path = filepath, - name = shortname) - - # Fill the data in a GPU-friendly manner - copyto!(interior(fts, :, :, 1, :), data) - fill_halo_regions!(fts) - - return fts - else - # Make times into an array for later preprocessing - if !totally_in_memory - times = collect(times) - end - - native_fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; - time_indexing, - boundary_conditions) - - # Fill the data in a GPU-friendly manner - copyto!(interior(native_fts, :, :, 1, :), data) - fill_halo_regions!(native_fts) - - if on_native_grid && totally_in_memory - return native_fts - - elseif totally_in_memory # but not on the native grid! - boundary_conditions = FieldBoundaryConditions(grid, (LX, LY, Nothing)) - fts = FieldTimeSeries{LX, LY, Nothing}(grid, times; time_indexing, boundary_conditions) - interpolate!(fts, native_fts) - return fts - end - end - - @info "Pre-processing JRA55 $variable_name data into a JLD2 file..." - - preprocessing_grid = on_native_grid ? JRA55_native_grid : grid - - # Re-open the dataset! - ds = Dataset(filepath) - all_datetimes = ds["time"][time_indices] - all_Nt = length(all_datetimes) - - all_times = jra55_times(all_datetimes) - - on_disk_fts = FieldTimeSeries{LX, LY, Nothing}(preprocessing_grid, all_times; - boundary_conditions, - backend = OnDisk(), - path = jld2_filepath, - name = fts_name) - - # Save data to disk, one field at a time - start_clock = time_ns() - n = 1 # on disk - m = 0 # in memory - - times_in_memory = all_times[time_indices_in_memory] - - fts = FieldTimeSeries{LX, LY, Nothing}(preprocessing_grid, times_in_memory; - boundary_conditions, - backend = InMemory(), - path = jld2_filepath, - name = fts_name) - - # Re-compute data - new_data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] - - if !on_native_grid - copyto!(interior(native_fts, :, :, 1, :), new_data[:, :, :]) - fill_halo_regions!(native_fts) - interpolate!(fts, native_fts) - else - copyto!(interior(fts, :, :, 1, :), new_data[:, :, :]) - end - - while n <= all_Nt - print(" ... processing time index $n of $all_Nt \r") - - if time_indices_in_memory isa Colon || n ∈ time_indices_in_memory - m += 1 - else # load new data - # Update time_indices - time_indices_in_memory = time_indices_in_memory .+ preprocess_chunk_size - n₁ = first(time_indices_in_memory) - - # Clip time_indices if they extend past the end of the dataset - if last(time_indices_in_memory) > all_Nt - time_indices_in_memory = UnitRange(n₁, all_Nt) - end - - # Re-compute times - new_times = jra55_times(all_times[time_indices_in_memory], all_times[n₁]) - native_fts.times = new_times - - # Re-compute data - new_data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] - fts.times = new_times - - if !on_native_grid - copyto!(interior(native_fts, :, :, 1, :), new_data[:, :, :]) - fill_halo_regions!(native_fts) - interpolate!(fts, native_fts) - else - copyto!(interior(fts, :, :, 1, :), new_data[:, :, :]) - end - - m = 1 # reset - end - - set!(on_disk_fts, fts[m], n, fts.times[m]) - - n += 1 - end - - elapsed = 1e-9 * (time_ns() - start_clock) - elapsed_str = prettytime(elapsed) - @info " ... done ($elapsed_str)" * repeat(" ", 20) - - close(ds) - - user_fts = FieldTimeSeries(jld2_filepath, fts_name; architecture, backend, time_indexing) - fill_halo_regions!(user_fts) - - return user_fts -end - -const AA = Oceananigans.Architectures.AbstractArchitecture - -JRA55_prescribed_atmosphere(time_indices=Colon(); kw...) = - JRA55_prescribed_atmosphere(CPU(), time_indices; kw...) - -JRA55_prescribed_atmosphere(arch::Distributed, time_indices=Colon(); kw...) = - JRA55_prescribed_atmosphere(child_architecture(arch), time_indices; kw...) - -# TODO: allow the user to pass dates -""" - JRA55_prescribed_atmosphere(architecture::AA, time_indices=Colon(); - backend = nothing, - time_indexing = Cyclical(), - reference_height = 10, # meters - include_rivers_and_icebergs = false, - other_kw...) - -Return a `PrescribedAtmosphere` representing JRA55 reanalysis data. -""" -function JRA55_prescribed_atmosphere(architecture::AA, time_indices=Colon(); - backend = nothing, - time_indexing = Cyclical(), - reference_height = 10, # meters - include_rivers_and_icebergs = false, - other_kw...) - - if isnothing(backend) # apply a default - Ni = try - length(time_indices) - catch - Inf - end - - # Manufacture a default for the number of fields to keep InMemory - Nf = min(24, Ni) - backend = JRA55NetCDFBackend(Nf) - end - - kw = (; time_indices, time_indexing, backend, architecture) - kw = merge(kw, other_kw) - - ua = JRA55_field_time_series(:eastward_velocity; kw...) - va = JRA55_field_time_series(:northward_velocity; kw...) - Ta = JRA55_field_time_series(:temperature; kw...) - qa = JRA55_field_time_series(:specific_humidity; kw...) - pa = JRA55_field_time_series(:sea_level_pressure; kw...) - Fra = JRA55_field_time_series(:rain_freshwater_flux; kw...) - Fsn = JRA55_field_time_series(:snow_freshwater_flux; kw...) - Ql = JRA55_field_time_series(:downwelling_longwave_radiation; kw...) - Qs = JRA55_field_time_series(:downwelling_shortwave_radiation; kw...) - - freshwater_flux = (rain = Fra, - snow = Fsn) - - # Remember that rivers and icebergs are on a different grid and have - # a different frequency than the rest of the JRA55 data. We use `PrescribedAtmospheres` - # "auxiliary_freshwater_flux" feature to represent them. - if include_rivers_and_icebergs - Fri = JRA55_field_time_series(:river_freshwater_flux; kw...) - Fic = JRA55_field_time_series(:iceberg_freshwater_flux; kw...) - auxiliary_freshwater_flux = (rivers = Fri, icebergs = Fic) - else - auxiliary_freshwater_flux = nothing - end - - times = ua.times - - velocities = (u = ua, - v = va) - - tracers = (T = Ta, - q = qa) - - pressure = pa - - downwelling_radiation = TwoBandDownwellingRadiation(shortwave=Qs, longwave=Ql) - - FT = eltype(ua) - reference_height = convert(FT, reference_height) - - atmosphere = PrescribedAtmosphere(times, FT; - velocities, - freshwater_flux, - auxiliary_freshwater_flux, - tracers, - downwelling_radiation, - reference_height, - pressure) - - return atmosphere -end +include("JRA55_metadata.jl") +include("JRA55_field_time_series.jl") +include("JRA55_prescribed_atmosphere.jl") end # module diff --git a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl new file mode 100644 index 00000000..8f5eec37 --- /dev/null +++ b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl @@ -0,0 +1,92 @@ + +const AA = Oceananigans.Architectures.AbstractArchitecture + +JRA55_prescribed_atmosphere(time_indices=Colon(); kw...) = + JRA55_prescribed_atmosphere(CPU(), time_indices; kw...) + +JRA55_prescribed_atmosphere(arch::Distributed, time_indices=Colon(); kw...) = + JRA55_prescribed_atmosphere(child_architecture(arch), time_indices; kw...) + +# TODO: allow the user to pass dates +""" + JRA55_prescribed_atmosphere(architecture::AA, time_indices=Colon(); + backend = nothing, + time_indexing = Cyclical(), + reference_height = 10, # meters + include_rivers_and_icebergs = false, + other_kw...) + +Return a `PrescribedAtmosphere` representing JRA55 reanalysis data. +""" +function JRA55_prescribed_atmosphere(architecture::AA, time_indices=Colon(); + backend = nothing, + time_indexing = Cyclical(), + reference_height = 10, # meters + include_rivers_and_icebergs = false, + other_kw...) + + if isnothing(backend) # apply a default + Ni = try + length(time_indices) + catch + Inf + end + + # Manufacture a default for the number of fields to keep InMemory + Nf = min(24, Ni) + backend = JRA55NetCDFBackend(Nf) + end + + kw = (; time_indices, time_indexing, backend, architecture) + kw = merge(kw, other_kw) + + ua = JRA55_field_time_series(:eastward_velocity; kw...) + va = JRA55_field_time_series(:northward_velocity; kw...) + Ta = JRA55_field_time_series(:temperature; kw...) + qa = JRA55_field_time_series(:specific_humidity; kw...) + pa = JRA55_field_time_series(:sea_level_pressure; kw...) + Fra = JRA55_field_time_series(:rain_freshwater_flux; kw...) + Fsn = JRA55_field_time_series(:snow_freshwater_flux; kw...) + Ql = JRA55_field_time_series(:downwelling_longwave_radiation; kw...) + Qs = JRA55_field_time_series(:downwelling_shortwave_radiation; kw...) + + freshwater_flux = (rain = Fra, + snow = Fsn) + + # Remember that rivers and icebergs are on a different grid and have + # a different frequency than the rest of the JRA55 data. We use `PrescribedAtmospheres` + # "auxiliary_freshwater_flux" feature to represent them. + if include_rivers_and_icebergs + Fri = JRA55_field_time_series(:river_freshwater_flux; kw...) + Fic = JRA55_field_time_series(:iceberg_freshwater_flux; kw...) + auxiliary_freshwater_flux = (rivers = Fri, icebergs = Fic) + else + auxiliary_freshwater_flux = nothing + end + + times = ua.times + + velocities = (u = ua, + v = va) + + tracers = (T = Ta, + q = qa) + + pressure = pa + + downwelling_radiation = TwoBandDownwellingRadiation(shortwave=Qs, longwave=Ql) + + FT = eltype(ua) + reference_height = convert(FT, reference_height) + + atmosphere = PrescribedAtmosphere(times, FT; + velocities, + freshwater_flux, + auxiliary_freshwater_flux, + tracers, + downwelling_radiation, + reference_height, + pressure) + + return atmosphere +end diff --git a/src/DataWrangling/JRA55/jra55_field_time_series.jl b/src/DataWrangling/JRA55/jra55_field_time_series.jl index 649001d4..a9004f91 100644 --- a/src/DataWrangling/JRA55/jra55_field_time_series.jl +++ b/src/DataWrangling/JRA55/jra55_field_time_series.jl @@ -1,53 +1,108 @@ -using Oceananigans.Units -using Oceananigans.Grids: node, on_architecture -using Oceananigans.Fields: interpolate!, interpolate, location, instantiated_location -using Oceananigans.OutputReaders: Cyclical, TotallyInMemory, AbstractInMemoryBackend, FlavorOfFTS, time_indices -using Oceananigans.Utils: Time -using CUDA: @allowscalar -using Base +download_jra55_cache::String = "" +function __init__() + global download_jra55_cache = @get_scratch!("JRA55") +end + +compute_bounding_nodes(::Nothing, ::Nothing, LH, hnodes) = nothing +compute_bounding_nodes(bounds, ::Nothing, LH, hnodes) = bounds + +function compute_bounding_nodes(x::Number, ::Nothing, LH, hnodes) + ϵ = convert(typeof(x), 0.001) # arbitrary? + return (x - ϵ, x + ϵ) +end + +# TODO: remove the allowscalar +function compute_bounding_nodes(::Nothing, grid, LH, hnodes) + hg = hnodes(grid, LH()) + h₁ = @allowscalar minimum(hg) + h₂ = @allowscalar maximum(hg) + return h₁, h₂ +end + +function compute_bounding_indices(::Nothing, hc) + Nh = length(hc) + return 1, Nh +end + +function compute_bounding_indices(bounds::Tuple, hc) + h₁, h₂ = bounds + Nh = length(hc) + + # The following should work. If ᵒ are the extrema of nodes we want to + # interpolate to, and the following is a sketch of the JRA55 native grid, + # + # 1 2 3 4 5 + # | | | | | | + # | x ᵒ | x | x | x ᵒ | x | + # | | | | | | + # 1 2 3 4 5 6 + # + # then for example, we should find that (iᵢ, i₂) = (1, 5). + # So we want to reduce the first index by one, and limit them + # both by the available data. There could be some mismatch due + # to the use of different coordinate systems (ie whether λ ∈ (0, 360) + # which we may also need to handle separately. + i₁ = searchsortedfirst(hc, h₁) + i₂ = searchsortedfirst(hc, h₂) + i₁ = max(1, i₁ - 1) + i₂ = min(Nh, i₂) + + return i₁, i₂ +end -using NCDatasets -using JLD2 -using Dates +infer_longitudinal_topology(::Nothing) = Periodic -using ClimaOcean: stateindex -using ClimaOcean.DataWrangling: native_times +function infer_longitudinal_topology(λbounds) + λ₁, λ₂ = λbounds + TX = λ₂ - λ₁ ≈ 360 ? Periodic : Bounded + return TX +end + +function compute_bounding_indices(longitude, latitude, grid, LX, LY, λc, φc) + λbounds = compute_bounding_nodes(longitude, grid, LX, λnodes) + φbounds = compute_bounding_nodes(latitude, grid, LY, φnodes) + + i₁, i₂ = compute_bounding_indices(λbounds, λc) + j₁, j₂ = compute_bounding_indices(φbounds, φc) + TX = infer_longitudinal_topology(λbounds) + + return i₁, i₂, j₁, j₂, TX +end -import Oceananigans.Fields: set! -import Oceananigans.OutputReaders: new_backend, update_field_time_series! +# Convert dates to range until Oceananigans supports dates natively +function jra55_times(native_times, start_time=native_times[1]) -@inline instantiate(T::DataType) = T() -@inline instantiate(T) = T + times = [] + for native_time in native_times + time = native_time - start_time + time = Second(time).value + push!(times, time) + end + + return times +end -struct JRA55NetCDFBackend{N} <: AbstractInMemoryBackend{Int} +struct JRA55NetCDFBackend <: AbstractInMemoryBackend{Int} start :: Int length :: Int - - JRA55NetCDFBackend{N}(start::Int, length::Int) where N = new{N}(start, length) end """ JRA55NetCDFBackend(length) -Represents an JRA55 FieldTimeSeries backed by JRA55 native .nc files. -Each time instance is stored in an individual file. +Represents a JRA55 FieldTimeSeries backed by JRA55 native .nc files. """ -JRA55NetCDFBackend(length; on_native_grid = false) = JRA55NetCDFBackend{on_native_grid}(1, length) +JRA55NetCDFBackend(length) = JRA55NetCDFBackend(1, length) -Base.length(backend::JRA55NetCDFBackend) = backend.length +Base.length(backend::JRA55NetCDFBackend) = backend.length Base.summary(backend::JRA55NetCDFBackend) = string("JRA55NetCDFBackend(", backend.start, ", ", backend.length, ")") -const JRA55NetCDFFTS{N} = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <: JRA55NetCDFBackend{N}} where N +const JRA55NetCDFFTS = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend} -new_backend(::JRA55NetCDFBackend{N}, start, length) where N = JRA55ONetCDFBackend{N}(start, length) -on_native_grid(::JRA55NetCDFBackend{N}) where N = N +function set!(fts::JRA55NetCDFFTS, path::String=fts.path, name::String=fts.name) -function set!(fts::JRA55NetCDFFTS, path::JRA55Metadata=fts.path, name::String=fts.name) - - # Do different things based on the Backend... - filename = metadata_filename(path) - ds = filename isa String ? Dataset(filename) : Dataset(filename[1]) + ds = Dataset(path) # Note that each file should have the variables # - ds["time"]: time coordinate @@ -64,54 +119,185 @@ function set!(fts::JRA55NetCDFFTS, path::JRA55Metadata=fts.path, name::String=ft i₁, i₂, j₁, j₂, TX = compute_bounding_indices(nothing, nothing, fts.grid, LX, LY, λc, φc) ti = time_indices(fts) - - if on_native_grid(fts.backend) - ti = collect(ti) - data = ds[name][i₁:i₂, j₁:j₂, ti] - copyto!(interior(fts, :, :, 1, :), data) - end - + ti = collect(ti) + data = ds[name][i₁:i₂, j₁:j₂, ti] close(ds) + + copyto!(interior(fts, :, :, 1, :), data) fill_halo_regions!(fts) - + return nothing end +new_backend(::JRA55NetCDFBackend, start, length) = JRA55NetCDFBackend(start, length) + """ - JRA55_field_time_series(metadata::ECCOMetadata; - architecture = CPU(), - time_indices_in_memory = 2, - time_indexing = Cyclical(), - grid = nothing) - -Create a field time series object for JRA55 data. - -Arguments: -=========== -- metadata: An JRA55Metadata object containing information about the JRA55 dataset. - -Keyword Arguments: -===================== -- 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()). -- grid: if not a `nothing`, the ECCO data is directly interpolated on the `grid`, + JRA55_field_time_series(variable_name; + architecture = CPU(), + time_indices = nothing, + latitude = nothing, + longitude = nothing, + location = nothing, + url = nothing, + filename = nothing, + shortname = nothing, + backend = InMemory(), + preprocess_chunk_size = 10, + preprocess_architecture = CPU()) + +Return a `FieldTimeSeries` containing atmospheric reanalysis data for `variable_name`, +which describes one of the variables in the "repeat year forcing" dataset derived +from the Japanese 55-year atmospheric reanalysis for driving ocean-sea-ice models (JRA55-do). +For more information about the derivation of the repeat year forcing dataset, see + +"Stewart et al., JRA55-do-based repeat year forcing datasets for driving ocean–sea-ice models", +Ocean Modelling, 2020, https://doi.org/10.1016/j.ocemod.2019.101557. + +The `variable_name`s (and their `shortname`s used in NetCDF files) +available from the JRA55-do are: + + - `:river_freshwater_flux` ("friver") + - `:rain_freshwater_flux` ("prra") + - `:snow_freshwater_flux` ("prsn") + - `:iceberg_freshwater_flux` ("licalvf") + - `:specific_humidity` ("huss") + - `:sea_level_pressure` ("psl") + - `:relative_humidity` ("rhuss") + - `:downwelling_longwave_radiation` ("rlds") + - `:downwelling_shortwave_radiation` ("rsds") + - `:temperature` ("ras") + - `:eastward_velocity` ("uas") + - `:northward_velocity` ("vas") + +Keyword arguments +================= + + - `architecture`: Architecture for the `FieldTimeSeries`. + Default: CPU() + + - `time_indices`: Indices of the timeseries to extract from file. + For example, `time_indices=1:3` returns a + `FieldTimeSeries` with the first three time snapshots + of `variable_name`. + + - `latitude`: Guiding latitude bounds for the resulting grid. + Used to slice the data when loading into memory. + Default: nothing, which retains the latitude range of the native grid. + + - `longitude`: Guiding longitude bounds for the resulting grid. + Used to slice the data when loading into memory. + Default: nothing, which retains the longitude range of the native grid. + + - `url`: The url accessed to download the data for `variable_name`. + Default: `ClimaOcean.JRA55.urls[variable_name]`. + + - `filename`: The name of the downloaded file. + Default: `ClimaOcean.JRA55.filenames[variable_name]`. + + - `shortname`: The "short name" of `variable_name` inside its NetCDF file. + Default: `ClimaOcean.JRA55.jra55_short_names[variable_name]`. + + - `interpolated_file`: file holding an Oceananigans compatible version of the JRA55 data. + If it does not exist it will be generated. + + - `time_chunks_in_memory`: number of fields held in memory. If `nothing` the whole timeseries is + loaded (not recommended). """ -function JRA55_field_time_series(metadata::JRA55Metadata; - architecture = CPU(), - time_indices_in_memory = 2, - time_indexing = Cyclical(), +function JRA55_field_time_series(variable_name; + architecture = CPU(), grid = nothing, + location = nothing, + url = nothing, + dir = download_jra55_cache, + filename = nothing, + shortname = nothing, latitude = nothing, - longitude = nothing) + longitude = nothing, + backend = InMemory(), + time_indexing = Cyclical(), + preprocess_chunk_size = 10, + preprocess_architecture = CPU(), + time_indices = nothing) + + # OnDisk backends do not support time interpolation! + # Disallow OnDisk for JRA55 dataset loading + if backend isa OnDisk + msg = string("We cannot load the JRA55 dataset with an `OnDisk` backend") + throw(ArgumentError(msg)) + end - backend = JRA55NetCDFBackend(time_indices_in_memory; on_native_grid = isnothing(grid)) + if isnothing(filename) && !(variable_name ∈ JRA55_variable_names) + variable_strs = Tuple(" - :$name \n" for name in JRA55_variable_names) + variables_msg = prod(variable_strs) - # Making sure all the required individual files are downloaded - download_dataset!(metadata) + msg = string("The variable :$variable_name is not provided by the JRA55-do dataset!", '\n', + "The variables provided by the JRA55-do dataset are:", '\n', + variables_msg) - filename = metadata_filename(metadata) - ds = Dataset(filename) + throw(ArgumentError(msg)) + end + + filepath = isnothing(filename) ? joinpath(dir, filenames[variable_name]) : joinpath(dir, filename) + + if !isnothing(filename) && !isfile(filepath) && isnothing(url) + throw(ArgumentError("A filename was provided without a url, but the file does not exist.\n \ + If intended, please provide both the filename and url that should be used \n \ + to download the new file.")) + end + + isnothing(filename) && (filename = filenames[variable_name]) + isnothing(shortname) && (shortname = jra55_short_names[variable_name]) + isnothing(url) && (url = urls[variable_name]) + + # Record some important user decisions + totally_in_memory = backend isa TotallyInMemory + on_native_grid = isnothing(grid) + !on_native_grid && backend isa JRA55NetCDFBackend && error("Can't use custom grid with JRA55NetCDFBackend.") + + jld2_filepath = joinpath(dir, string("JRA55_repeat_year_", variable_name, ".jld2")) + fts_name = field_time_series_short_names[variable_name] + + # Note, we don't re-use existing jld2 files. + isfile(filepath) || download(url, filepath) + isfile(jld2_filepath) && rm(jld2_filepath) + + # Determine default time indices + if totally_in_memory + # In this case, the whole time series is in memory. + # Either the time series is short, or we are doing a limited-area + # simulation, like in a single column. So, we conservatively + # set a default `time_indices = 1:2`. + isnothing(time_indices) && (time_indices = 1:2) + time_indices_in_memory = time_indices + native_fts_architecture = architecture + else + # In this case, part or all of the time series will be stored in a file. + # Note: if the user has provided a grid, we will have to preprocess the + # .nc JRA55 data into a .jld2 file. In this case, `time_indices` refers + # to the time_indices that we will preprocess; + # by default we choose all of them. The architecture is only the + # architecture used for preprocessing, which typically will be CPU() + # even if we would like the final FieldTimeSeries on the GPU. + isnothing(time_indices) && (time_indices = :) + + if backend isa JRA55NetCDFBackend + time_indices_in_memory = 1:length(backend) + native_fts_architecture = architecture + else # then `time_indices_in_memory` refers to preprocessing + maximum_index = min(preprocess_chunk_size, length(time_indices)) + time_indices_in_memory = 1:maximum_index + native_fts_architecture = preprocess_architecture + end + end + + # Set a default location. + if isnothing(location) + LX = LY = Center + else + LX, LY = location + end + + ds = Dataset(filepath) # Note that each file should have the variables # - ds["time"]: time coordinate @@ -148,7 +334,7 @@ function JRA55_field_time_series(metadata::JRA55Metadata; N = (Nrx, Nry) H = min.(N, (3, 3)) - JRA55_native_grid = LatitudeLongitudeGrid(architecture, Float32; + JRA55_native_grid = LatitudeLongitudeGrid(native_fts_architecture, Float32; halo = H, size = N, longitude = λr, @@ -156,92 +342,134 @@ function JRA55_field_time_series(metadata::JRA55Metadata; topology = (TX, Bounded, Flat)) boundary_conditions = FieldBoundaryConditions(JRA55_native_grid, (Center, Center, Nothing)) + times = jra55_times(native_times) - location = field_location(metadata) - shortname = short_name(metadata) + if backend isa JRA55NetCDFBackend + fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; + backend, + time_indexing, + boundary_conditions, + path = filepath, + name = shortname) - boundary_conditions = FieldBoundaryConditions(JRA55_native_grid, location) - times = native_times(metadata) + # Fill the data in a GPU-friendly manner + copyto!(interior(fts, :, :, 1, :), data) + fill_halo_regions!(fts) + + return fts + else + # Make times into an array for later preprocessing + if !totally_in_memory + times = collect(times) + end + + native_fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; + time_indexing, + boundary_conditions) + + # Fill the data in a GPU-friendly manner + copyto!(interior(native_fts, :, :, 1, :), data) + fill_halo_regions!(native_fts) + + if on_native_grid && totally_in_memory + return native_fts + + elseif totally_in_memory # but not on the native grid! + boundary_conditions = FieldBoundaryConditions(grid, (LX, LY, Nothing)) + fts = FieldTimeSeries{LX, LY, Nothing}(grid, times; time_indexing, boundary_conditions) + interpolate!(fts, native_fts) + return fts + end + end - fts_grid = isnothing(grid) ? JRA55_native_grid : grid + @info "Pre-processing JRA55 $variable_name data into a JLD2 file..." - path = metadata - - fts = FieldTimeSeries{location...}(fts_grid, times; - backend, - time_indexing, - boundary_conditions, - path, - name = metadata.name) + preprocessing_grid = on_native_grid ? JRA55_native_grid : grid - # Let's set the data - set!(fts) + # Re-open the dataset! + ds = Dataset(filepath) + all_datetimes = ds["time"][time_indices] + all_Nt = length(all_datetimes) - return fts -end + all_times = jra55_times(all_datetimes) -JRA55_field_time_series(variable_name::Symbol, version=JRA55RepeatYear(); kw...) = - JRA55_field_time_series(Metadata(variable_name, all_dates(version), version); kw...) + on_disk_fts = FieldTimeSeries{LX, LY, Nothing}(preprocessing_grid, all_times; + boundary_conditions, + backend = OnDisk(), + path = jld2_filepath, + name = fts_name) -""" - JRA55_field_time_series(variable_name; - architecture = CPU(), - location = nothing, - url = nothing, - filename = nothing, - shortname = nothing, - backend = InMemory(), - preprocess_chunk_size = 10, - preprocess_architecture = CPU(), - time_indices = nothing) + # Save data to disk, one field at a time + start_clock = time_ns() + n = 1 # on disk + m = 0 # in memory -Return a `FieldTimeSeries` containing atmospheric reanalysis data for `variable_name`, -which describes one of the variables in the "repeat year forcing" dataset derived -from the Japanese 55-year atmospheric reanalysis for driving ocean-sea-ice models (JRA55-do). -For more information about the derivation of the repeat year forcing dataset, see + times_in_memory = all_times[time_indices_in_memory] -"Stewart et al., JRA55-do-based repeat year forcing datasets for driving ocean–sea-ice models", -Ocean Modelling, 2020, https://doi.org/10.1016/j.ocemod.2019.101557. + fts = FieldTimeSeries{LX, LY, Nothing}(preprocessing_grid, times_in_memory; + boundary_conditions, + backend = InMemory(), + path = jld2_filepath, + name = fts_name) -The `variable_name`s (and their `shortname`s used in NetCDF files) -available from the JRA55-do are: + # Re-compute data + new_data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] - - `:river_freshwater_flux` ("friver") - - `:rain_freshwater_flux` ("prra") - - `:snow_freshwater_flux` ("prsn") - - `:iceberg_freshwater_flux` ("licalvf") - - `:specific_humidity` ("huss") - - `:sea_level_pressure` ("psl") - - `:relative_humidity` ("rhuss") - - `:downwelling_longwave_radiation` ("rlds") - - `:downwelling_shortwave_radiation` ("rsds") - - `:temperature` ("ras") - - `:eastward_velocity` ("uas") - - `:northward_velocity` ("vas") + if !on_native_grid + copyto!(interior(native_fts, :, :, 1, :), new_data[:, :, :]) + fill_halo_regions!(native_fts) + interpolate!(fts, native_fts) + else + copyto!(interior(fts, :, :, 1, :), new_data[:, :, :]) + end -Keyword arguments -================= + while n <= all_Nt + print(" ... processing time index $n of $all_Nt \r") - - `architecture`: Architecture for the `FieldTimeSeries`. - Default: CPU() + if time_indices_in_memory isa Colon || n ∈ time_indices_in_memory + m += 1 + else # load new data + # Update time_indices + time_indices_in_memory = time_indices_in_memory .+ preprocess_chunk_size + n₁ = first(time_indices_in_memory) - - `time_indices`: Indices of the timeseries to extract from file. - For example, `time_indices=1:3` returns a - `FieldTimeSeries` with the first three time snapshots - of `variable_name`. + # Clip time_indices if they extend past the end of the dataset + if last(time_indices_in_memory) > all_Nt + time_indices_in_memory = UnitRange(n₁, all_Nt) + end - - `url`: The url accessed to download the data for `variable_name`. - Default: `ClimaOcean.JRA55.urls[variable_name]`. + # Re-compute times + new_times = jra55_times(all_times[time_indices_in_memory], all_times[n₁]) + native_fts.times = new_times - - `filename`: The name of the downloaded file. - Default: `ClimaOcean.JRA55.filenames[variable_name]`. + # Re-compute data + new_data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] + fts.times = new_times - - `shortname`: The "short name" of `variable_name` inside its NetCDF file. - Default: `ClimaOcean.JRA55.jra55_short_names[variable_name]`. + if !on_native_grid + copyto!(interior(native_fts, :, :, 1, :), new_data[:, :, :]) + fill_halo_regions!(native_fts) + interpolate!(fts, native_fts) + else + copyto!(interior(fts, :, :, 1, :), new_data[:, :, :]) + end - - `interpolated_file`: file holding an Oceananigans compatible version of the JRA55 data. - If it does not exist it will be generated. + m = 1 # reset + end - - `time_chunks_in_memory`: number of fields held in memory. If `nothing` the whole timeseries is - loaded (not recommended). -""" + set!(on_disk_fts, fts[m], n, fts.times[m]) + + n += 1 + end + + elapsed = 1e-9 * (time_ns() - start_clock) + elapsed_str = prettytime(elapsed) + @info " ... done ($elapsed_str)" * repeat(" ", 20) + + close(ds) + + user_fts = FieldTimeSeries(jld2_filepath, fts_name; architecture, backend, time_indexing) + fill_halo_regions!(user_fts) + + return user_fts +end \ No newline at end of file diff --git a/src/DataWrangling/JRA55/jra55_metadata.jl b/src/DataWrangling/JRA55/jra55_metadata.jl index 75a8dfc3..52669a0b 100644 --- a/src/DataWrangling/JRA55/jra55_metadata.jl +++ b/src/DataWrangling/JRA55/jra55_metadata.jl @@ -9,7 +9,7 @@ using ClimaOcean.DataWrangling: Metadata struct JRA55MultipleYears end struct JRA55RepeatYear end -const JRA55Metadata{T} = Union{Metadata{T, <:JRA55MultipleYears}, Metadata{<:JRA55RepeatYear}} where T +const JRA55Metadata{T, V} = Union{Metadata{T, V<:JRA55MultipleYears}, Metadata{T, V<:JRA55RepeatYear}} where {T, V} Base.size(data::JRA55Metadata) = (640, 320, length(data.dates)) Base.size(::JRA55Metadata{<:AbstractCFDateTime}) = (640, 320, 1) @@ -29,8 +29,8 @@ function metadata_filename(metadata::Metadata{<:AbstractCFDateTime, <:JRA55Repea end # Convenience functions -short_name(data::JRA55Metadata) = jra55_short_names[data.name] -field_location(data::JRA55Metadata) = (Center, Center, Center) +short_name(data::JRA55Metadata) = jra55_short_names[data.name] +location(data::JRA55Metadata) = (Center, Center, Center) # A list of all variables provided in the JRA55 dataset: JRA55_variable_names = (:river_freshwater_flux, diff --git a/src/DataWrangling/ecco_metadata.jl b/src/DataWrangling/ecco_metadata.jl deleted file mode 100644 index c2e61f5c..00000000 --- a/src/DataWrangling/ecco_metadata.jl +++ /dev/null @@ -1,140 +0,0 @@ -using CFTime -using Dates -import Dates: year, month, day -import Oceananigans.Fields: set! -import Base - -using ClimaOcean.DataWrangling: Metadata - -struct ECCO2Monthly end -struct ECCO2Daily end -struct ECCO4Monthly end - -const ECCOMetadata{T} = Union{Metadata{T, <:ECCO4Monthly}, Metadata{T, <:ECCO2Daily}, Metadata{T, <:ECCO2Monthly}} where T - -Base.size(data::Metadata{<:Any, <:ECCO2Daily}) = (1440, 720, 50, length(data.dates)) -Base.size(data::Metadata{<:Any, <:ECCO2Monthly}) = (1440, 720, 50, length(data.dates)) -Base.size(data::Metadata{<:Any, <:ECCO4Monthly}) = (720, 360, 50, length(data.dates)) - -Base.size(::Metadata{<:AbstractCFDateTime, <:ECCO2Daily}) = (1440, 720, 50, 1) -Base.size(::Metadata{<:AbstractCFDateTime, <:ECCO2Monthly}) = (1440, 720, 50, 1) -Base.size(::Metadata{<:AbstractCFDateTime, <:ECCO4Monthly}) = (720, 360, 50, 1) - -# The whole range of dates in the different dataset versions -all_ecco_dates(::ECCO4Monthly) = DateTimeProlepticGregorian(1992, 1, 1) : Month(1) : DateTimeProlepticGregorian(2023, 12, 1) -all_ecco_dates(::ECCO2Monthly) = DateTimeProlepticGregorian(1992, 1, 1) : Month(1) : DateTimeProlepticGregorian(2023, 12, 1) -all_ecco_dates(::ECCO2Daily) = DateTimeProlepticGregorian(1992, 1, 4) : Day(1) : DateTimeProlepticGregorian(2023, 12, 31) - -# File name generation specific to each Dataset version -function metadata_filename(metadata::Metadata{<:AbstractCFDateTime, <:ECCO4Monthly}) - shortname = short_name(metadata) - yearstr = string(Dates.year(metadata.dates)) - monthstr = string(Dates.month(metadata.dates), pad=2) - return shortname * "_" * yearstr * "_" * monthstr * ".nc" -end - -function metadata_filename(metadata::ECCOMetadata{<:AbstractCFDateTime}) - shortname = short_name(metadata) - yearstr = string(Dates.year(metadata.dates)) - monthstr = string(Dates.month(metadata.dates), pad=2) - postfix = variable_is_three_dimensional(metadata) ? ".1440x720x50." : ".1440x720." - - if metadata.version isa ECCO2Monthly - return shortname * postfix * yearstr * monthstr * ".nc" - elseif metadata.version isa ECCO2Daily - daystr = string(Dates.day(metadata.dates), pad=2) - return shortname * postfix * yearstr * monthstr * daystr * ".nc" - end -end - -# Convenience functions -short_name(data::Metadata{<:Any, <:ECCO2Daily}) = ecco2_short_names[data.name] -short_name(data::Metadata{<:Any, <:ECCO2Monthly}) = ecco2_short_names[data.name] -short_name(data::Metadata{<:Any, <:ECCO4Monthly}) = ecco4_short_names[data.name] - -field_location(data::ECCOMetadata) = ecco_location[data.name] - -variable_is_three_dimensional(data::ECCOMetadata) = - data.name == :temperature || - data.name == :salinity || - data.name == :u_velocity || - data.name == :v_velocity - -ecco4_short_names = Dict( - :temperature => "THETA", - :salinity => "SALT", - :u_velocity => "EVEL", - :v_velocity => "NVEL", - :sea_ice_thickness => "SIheff", - :sea_ice_area_fraction => "SIarea" -) - -ecco2_short_names = Dict( - :temperature => "THETA", - :salinity => "SALT", - :u_velocity => "UVEL", - :v_velocity => "VVEL", - :sea_ice_thickness => "SIheff", - :sea_ice_area_fraction => "SIarea" -) - -ecco_location = Dict( - :temperature => (Center, Center, Center), - :salinity => (Center, Center, Center), - :sea_ice_thickness => (Center, Center, Nothing), - :sea_ice_area_fraction => (Center, Center, Nothing), - :u_velocity => (Face, Center, Center), - :v_velocity => (Center, Face, Center), -) - -# URLs for the ECCO datasets specific to each version -urls(::Metadata{<:Any, <:ECCO2Monthly}) = "https://ecco.jpl.nasa.gov/drive/files/ECCO2/cube92_latlon_quart_90S90N/monthly/" -urls(::Metadata{<:Any, <:ECCO2Daily}) = "https://ecco.jpl.nasa.gov/drive/files/ECCO2/cube92_latlon_quart_90S90N/daily/" -urls(::Metadata{<:Any, <:ECCO4Monthly}) = "https://ecco.jpl.nasa.gov/drive/files/Version4/Release4/interp_monthly/" - -""" - download_dataset!(metadata::Metadata) - -Download the dataset specified by the given metadata. If the metadata contains a single date, -the dataset is downloaded directly. If the metadata contains multiple dates, the dataset is -downloaded for each date individually. -The data download requires a username and password to be provided in the ECCO_USERNAME and ECCO_PASSWORD -environment variables. This can be done by exporting the environment variables in the shell before running the script, -or by launching julia with - -ECCO_USERNAME=myuser ECCO_PASSWORD=mypasswrd julia - -# Arguments -- `metadata::Metadata`: The metadata specifying the dataset to be downloaded. -""" -function download_dataset!(metadata::ECCOMetadata; - url = urls(metadata)) - - username = get(ENV, "ECCO_USERNAME", nothing) - password = get(ENV, "ECCO_PASSWORD", nothing) - - for data in metadata - filename = metadata_filename(data) - shortname = short_name(data) - - if !isfile(filename) - - isnothing(username) && throw(ArgumentError("Could not find the username for $(url). Please provide a username in the ECCO_USERNAME environment variable.")) - isnothing(password) && throw(ArgumentError("Could not find the username for $(url). Please provide a password in the ECCO_PASSWORD environment variable.")) - - # Version specific download file url - if data.version isa ECCO2Monthly || data.version isa ECCO2Daily - fileurl = joinpath(url, shortname, filename) - elseif data.version isa ECCO4Monthly - year = string(Dates.year(data.dates)) - fileurl = joinpath(url, shortname, year, filename) - end - - cmd = `wget --http-user=$(username) --http-passwd=$(password) $(fileurl)` - - run(cmd) - end - end - - return nothing -end \ No newline at end of file diff --git a/src/DataWrangling/metadata.jl b/src/DataWrangling/metadata.jl index 523fdd2c..df8de0dd 100644 --- a/src/DataWrangling/metadata.jl +++ b/src/DataWrangling/metadata.jl @@ -1,12 +1,19 @@ -# Metadata holding the ECCO dataset information: -# - `name`: The name of the dataset. -# - `dates`: The dates of the dataset, in a `AbstractCFDateTime` format. -# - `version`: The version of the dataset, could be ECCO2Monthly, ECCO2Daily, or ECCO4Monthly. + +""" + Metadata{D, V} + +Metadata holding the dataset information: +- `name`: The name of the dataset. +- `dates`: The dates of the dataset, in a `AbstractCFDateTime` format. +- `version`: The version of the dataset, could be ECCO2Monthly, ECCO2Daily, or ECCO4Monthly. +- `dir`: The directory where the dataset is stored. +""" struct Metadata{D, V} name :: Symbol dates :: D version :: V + dir :: String end Base.show(io::IO, metadata::Metadata) = @@ -15,13 +22,21 @@ Base.show(io::IO, metadata::Metadata) = "├── dates: $(metadata.dates)", '\n', "└── data version: $(metadata.version)") + # Treat Metadata as an array to allow iteration over the dates. -Base.getindex(metadata::Metadata, i::Int) = @inbounds Metadata(metadata.name, metadata.dates[i], metadata.version) -Base.length(metadata::Metadata) = length(metadata.dates) -Base.eltype(metadata::Metadata) = Base.eltype(metadata.dates) -Base.first(metadata::Metadata) = @inbounds Metadata(metadata.name, metadata.dates[1], metadata.version) -Base.last(metadata::Metadata) = @inbounds Metadata(metadata.name, metadata.dates[end], metadata.version) -Base.iterate(metadata::Metadata, i=1) = (@inline; (i % UInt) - 1 < length(metadata) ? (@inbounds Metadata(metadata.name, metadata.dates[i], metadata.version), i + 1) : nothing) +Base.length(metadata::Metadata) = length(metadata.dates) +Base.eltype(metadata::Metadata) = Base.eltype(metadata.dates) +@propagate_inbounds Base.getindex(m::Metadata, i::Int) = Metadata(m.name, m.dates[i], m.version, m.dir) +@propagate_inbounds Base.first(m::Metadata) = Metadata(m.name, m.dates[1], m.version, m.dir) +@propagate_inbounds Base.last(m::Metadata) = Metadata(m.name, m.dates[end], m.version, m.dir) + +@inline function Base.iterate(m::Metadata, i=1) + if (i % UInt) - 1 < length(m) + return ECCOMetadata(m.name, m.dates[i], m.version, m.dir), i + 1 + else + return nothing + end +end Base.axes(metadata::Metadata{<:AbstractCFDateTime}) = 1 Base.first(metadata::Metadata{<:AbstractCFDateTime}) = metadata @@ -29,9 +44,6 @@ Base.last(metadata::Metadata{<:AbstractCFDateTime}) = metadata Base.iterate(metadata::Metadata{<:AbstractCFDateTime}) = (metadata, nothing) Base.iterate(::Metadata{<:AbstractCFDateTime}, ::Any) = nothing -Base.size(data::Metadata{<:Any, <:JRA55ThreeHourly}) = (640, 320, 1, length(data.dates)) - - """ native_times(metadata; start_time = metadata.dates[1]) From a7a3268419419fa75ada68e2b6d03573701d3ee2 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Fri, 8 Nov 2024 16:23:34 +0100 Subject: [PATCH 007/104] start changing stuff --- .../JRA55/jra55_field_time_series.jl | 6 +-- src/DataWrangling/JRA55/jra55_metadata.jl | 41 ++++++------------- 2 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/DataWrangling/JRA55/jra55_field_time_series.jl b/src/DataWrangling/JRA55/jra55_field_time_series.jl index a9004f91..a11be48f 100644 --- a/src/DataWrangling/JRA55/jra55_field_time_series.jl +++ b/src/DataWrangling/JRA55/jra55_field_time_series.jl @@ -132,9 +132,9 @@ end new_backend(::JRA55NetCDFBackend, start, length) = JRA55NetCDFBackend(start, length) """ - JRA55_field_time_series(variable_name; - architecture = CPU(), - time_indices = nothing, + JRA55_field_time_series(variable_name, [architecture = CPU()]; + version = JRA55RepeatYear(), + dates = all_JRA55_dates(version), latitude = nothing, longitude = nothing, location = nothing, diff --git a/src/DataWrangling/JRA55/jra55_metadata.jl b/src/DataWrangling/JRA55/jra55_metadata.jl index 52669a0b..b309dd00 100644 --- a/src/DataWrangling/JRA55/jra55_metadata.jl +++ b/src/DataWrangling/JRA55/jra55_metadata.jl @@ -14,6 +14,10 @@ const JRA55Metadata{T, V} = Union{Metadata{T, V<:JRA55MultipleYears}, Metadata{T Base.size(data::JRA55Metadata) = (640, 320, length(data.dates)) Base.size(::JRA55Metadata{<:AbstractCFDateTime}) = (640, 320, 1) +# The whole range of dates in the different dataset versions +all_JRA55_times(::JRA55RepeatYear) = DateTimeProlepticGregorian(1990, 1, 1) : Hour(3) : DateTimeProlepticGregorian(1991, 1, 1) +all_JRA55_times(::JRA55MultipleYears) = DateTimeProlepticGregorian(1958, 1, 1) : Hour(3) : DateTimeProlepticGregorian(2018, 12, 1) + # File name generation specific to each Dataset version function metadata_filename(metadata::Metadata{<:AbstractCFDateTime, <:JRA55MultipleYears}) # fix the filename @@ -23,9 +27,8 @@ end # File name generation specific to each Dataset version function metadata_filename(metadata::Metadata{<:AbstractCFDateTime, <:JRA55RepeatYear}) - # fix the filename - - return filename + shortname = short_name(metadata) + return "RYF." * shortname * ".1990_1991.nc" end # Convenience functions @@ -46,22 +49,7 @@ JRA55_variable_names = (:river_freshwater_flux, :eastward_velocity, :northward_velocity) -filenames = Dict( - :river_freshwater_flux => "RYF.friver.1990_1991.nc", # Freshwater fluxes from rivers - :rain_freshwater_flux => "RYF.prra.1990_1991.nc", # Freshwater flux from rainfall - :snow_freshwater_flux => "RYF.prsn.1990_1991.nc", # Freshwater flux from snowfall - :iceberg_freshwater_flux => "RYF.licalvf.1990_1991.nc", # Freshwater flux from calving icebergs - :specific_humidity => "RYF.huss.1990_1991.nc", # Surface specific humidity - :sea_level_pressure => "RYF.psl.1990_1991.nc", # Sea level pressure - :relative_humidity => "RYF.rhuss.1990_1991.nc", # Surface relative humidity - :downwelling_longwave_radiation => "RYF.rlds.1990_1991.nc", # Downwelling longwave radiation - :downwelling_shortwave_radiation => "RYF.rsds.1990_1991.nc", # Downwelling shortwave radiation - :temperature => "RYF.tas.1990_1991.nc", # Near-surface air temperature - :eastward_velocity => "RYF.uas.1990_1991.nc", # Eastward near-surface wind - :northward_velocity => "RYF.vas.1990_1991.nc", # Northward near-surface wind -) - -jra55_short_names = Dict( +JRA55_short_names = Dict( :river_freshwater_flux => "friver", # Freshwater fluxes from rivers :rain_freshwater_flux => "prra", # Freshwater flux from rainfall :snow_freshwater_flux => "prsn", # Freshwater flux from snowfall @@ -91,6 +79,8 @@ field_time_series_short_names = Dict( :northward_velocity => "va", # Northward near-surface wind ) +urls(metadata::Metadata{<:Any, <:JRA55RepeatYear}) = jra55_repeat_year_urls[metadata.name] + jra55_repeat_year_urls = Dict( :shortwave_radiation => "https://www.dropbox.com/scl/fi/z6fkvmd9oe3ycmaxta131/" * "RYF.rsds.1990_1991.nc?rlkey=r7q6zcbj6a4fxsq0f8th7c4tc&dl=0", @@ -134,22 +124,17 @@ jra55_repeat_year_urls = Dict( variable_is_three_dimensional(data::JRA55Metadata) = false -# URLs for the JRA55 datasets specific to each version -function urls(metadata::Metadata{<:Any, <:JRA55MultipleYears}) - return "https://ecco.jpl.nasa.gov/drive/files/ECCO2/cube92_latlon_quart_90S90N/monthly/" -end - urls(metadata::Metadata{<:Any, <:JRA55RepeatYear}) = jra55_repeat_year_urls[metadata.name] function download_dataset!(metadata::JRA55Metadata; - url = urls(metadata)) + url = urls(metadata), + dir = download_jra55_cache) for data in metadata filename = metadata_filename(data) - shortname = short_name(data) - + if !isfile(filename) - download(url, filepath) + download(url, dir) end end From f8e7fe72970924b0098fc03ba4a9e4efee734125 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Fri, 8 Nov 2024 16:54:02 +0100 Subject: [PATCH 008/104] starting a bit --- src/DataWrangling/JRA55/jra55_metadata.jl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/DataWrangling/JRA55/jra55_metadata.jl b/src/DataWrangling/JRA55/jra55_metadata.jl index b309dd00..9777703a 100644 --- a/src/DataWrangling/JRA55/jra55_metadata.jl +++ b/src/DataWrangling/JRA55/jra55_metadata.jl @@ -15,14 +15,17 @@ Base.size(data::JRA55Metadata) = (640, 320, length(data.dates)) Base.size(::JRA55Metadata{<:AbstractCFDateTime}) = (640, 320, 1) # The whole range of dates in the different dataset versions -all_JRA55_times(::JRA55RepeatYear) = DateTimeProlepticGregorian(1990, 1, 1) : Hour(3) : DateTimeProlepticGregorian(1991, 1, 1) -all_JRA55_times(::JRA55MultipleYears) = DateTimeProlepticGregorian(1958, 1, 1) : Hour(3) : DateTimeProlepticGregorian(2018, 12, 1) +all_JRA55_times(::JRA55RepeatYear) = DateTimeProlepticGregorian(1990, 1, 1, 3) : Hour(3) : DateTimeProlepticGregorian(1991, 1, 1) +all_JRA55_times(::JRA55MultipleYears) = DateTimeProlepticGregorian(1958, 1, 1) : Hour(3) : DateTimeProlepticGregorian(2021, 1, 1) # File name generation specific to each Dataset version function metadata_filename(metadata::Metadata{<:AbstractCFDateTime, <:JRA55MultipleYears}) # fix the filename - - return filename + shortname = short_name(metadata) + year = Dates.year(metadata.dates) + suffix = "_input4MIPs_atmosphericState_OMIP_MRI-JRA55-do-1-5-0_gr_" + dates = "($year)01010130-($year)12312330.nc" + return shortname * suffix * dates * ".nc" end # File name generation specific to each Dataset version From fdef030e645744a7b2115b314805e46d82f8231d Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 5 Dec 2024 08:33:13 +0100 Subject: [PATCH 009/104] let's go --- src/DataWrangling/metadata.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/DataWrangling/metadata.jl b/src/DataWrangling/metadata.jl index df8de0dd..4c9f49ae 100644 --- a/src/DataWrangling/metadata.jl +++ b/src/DataWrangling/metadata.jl @@ -1,12 +1,10 @@ - - """ Metadata{D, V} Metadata holding the dataset information: - `name`: The name of the dataset. - `dates`: The dates of the dataset, in a `AbstractCFDateTime` format. -- `version`: The version of the dataset, could be ECCO2Monthly, ECCO2Daily, or ECCO4Monthly. +- `version`: The version of the dataset, could be ECCO2Monthly, ECCO2Daily, ECCO4Monthly, JRA55RepeatYear, or JRA55MultipleYears. - `dir`: The directory where the dataset is stored. """ struct Metadata{D, V} From f4d63a1c4e46d46e4655dc52ab2b518c09ba627f Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 5 Dec 2024 09:51:05 +0100 Subject: [PATCH 010/104] correct alias --- src/DataWrangling/ECCO/ECCO_metadata.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataWrangling/ECCO/ECCO_metadata.jl b/src/DataWrangling/ECCO/ECCO_metadata.jl index 0babb1a2..3b8d1163 100644 --- a/src/DataWrangling/ECCO/ECCO_metadata.jl +++ b/src/DataWrangling/ECCO/ECCO_metadata.jl @@ -13,7 +13,7 @@ struct ECCO2Monthly end struct ECCO2Daily end struct ECCO4Monthly end -const ECCOMetadata{D, V} = Union{Metadata{T, V<:ECCO4Monthly}, Metadata{T, V<:ECCO2Daily}, Metadata{T, V<:ECCO2Monthly}} where {T, V} +const ECCOMetadata{D, V} = Union{Metadata{D, V<:ECCO4Monthly}, Metadata{D, V<:ECCO2Daily}, Metadata{D, V<:ECCO2Monthly}} where {D, V} Base.show(io::IO, metadata::ECCOMetadata) = print(io, "ECCOMetadata:", '\n', From a130e73d7df960bce1169867ef393ff25b50a4bd Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 5 Dec 2024 09:53:36 +0100 Subject: [PATCH 011/104] correct dates --- src/DataWrangling/JRA55/jra55_metadata.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DataWrangling/JRA55/jra55_metadata.jl b/src/DataWrangling/JRA55/jra55_metadata.jl index 9777703a..5dfa6dd2 100644 --- a/src/DataWrangling/JRA55/jra55_metadata.jl +++ b/src/DataWrangling/JRA55/jra55_metadata.jl @@ -15,8 +15,8 @@ Base.size(data::JRA55Metadata) = (640, 320, length(data.dates)) Base.size(::JRA55Metadata{<:AbstractCFDateTime}) = (640, 320, 1) # The whole range of dates in the different dataset versions -all_JRA55_times(::JRA55RepeatYear) = DateTimeProlepticGregorian(1990, 1, 1, 3) : Hour(3) : DateTimeProlepticGregorian(1991, 1, 1) -all_JRA55_times(::JRA55MultipleYears) = DateTimeProlepticGregorian(1958, 1, 1) : Hour(3) : DateTimeProlepticGregorian(2021, 1, 1) +all_JRA55_times(::JRA55RepeatYear) = DateTimeProlepticGregorian(1990, 1, 1) : Hour(3) : DateTimeProlepticGregorian(1991, 1, 1) +all_JRA55_times(::JRA55MultipleYears) = DateTimeProlepticGregorian(1958, 1, 1) : Hour(3) : DateTimeProlepticGregorian(2021, 1, 1) # File name generation specific to each Dataset version function metadata_filename(metadata::Metadata{<:AbstractCFDateTime, <:JRA55MultipleYears}) From 08b377fc49e682159c666cbe223c9050c91f26b9 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Mon, 23 Dec 2024 09:40:28 +0100 Subject: [PATCH 012/104] syntax --- src/DataWrangling/JRA55/JRA55.jl | 4 ++-- .../JRA55/JRA55_prescribed_atmosphere.jl | 22 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55.jl b/src/DataWrangling/JRA55/JRA55.jl index 069b58bd..7f27ed08 100644 --- a/src/DataWrangling/JRA55/JRA55.jl +++ b/src/DataWrangling/JRA55/JRA55.jl @@ -1,6 +1,6 @@ module JRA55 -export JRA55_field_time_series, JRA55_prescribed_atmosphere +export JRA55_field_time_series, JRA55PrescribedAtmosphere using Oceananigans using Oceananigans.Units @@ -30,6 +30,6 @@ using Downloads: download include("JRA55_metadata.jl") include("JRA55_field_time_series.jl") -include("JRA55_prescribed_atmosphere.jl") +include("JRA55PrescribedAtmosphere.jl") end # module diff --git a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl index 8f5eec37..42f9730b 100644 --- a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl +++ b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl @@ -1,24 +1,24 @@ const AA = Oceananigans.Architectures.AbstractArchitecture -JRA55_prescribed_atmosphere(time_indices=Colon(); kw...) = - JRA55_prescribed_atmosphere(CPU(), time_indices; kw...) +JRA55PrescribedAtmosphere(time_indices=Colon(); kw...) = + JRA55PrescribedAtmosphere(CPU(), time_indices; kw...) -JRA55_prescribed_atmosphere(arch::Distributed, time_indices=Colon(); kw...) = - JRA55_prescribed_atmosphere(child_architecture(arch), time_indices; kw...) +JRA55PrescribedAtmosphere(arch::Distributed, time_indices=Colon(); kw...) = + JRA55PrescribedAtmosphere(child_architecture(arch), time_indices; kw...) # TODO: allow the user to pass dates """ - JRA55_prescribed_atmosphere(architecture::AA, time_indices=Colon(); - backend = nothing, - time_indexing = Cyclical(), - reference_height = 10, # meters - include_rivers_and_icebergs = false, - other_kw...) + JRA55PrescribedAtmosphere(architecture::AA, time_indices=Colon(); + backend = nothing, + time_indexing = Cyclical(), + reference_height = 10, # meters + include_rivers_and_icebergs = false, + other_kw...) Return a `PrescribedAtmosphere` representing JRA55 reanalysis data. """ -function JRA55_prescribed_atmosphere(architecture::AA, time_indices=Colon(); +function JRA55PrescribedAtmosphere(architecture::AA, time_indices=Colon(); backend = nothing, time_indexing = Cyclical(), reference_height = 10, # meters From ba4bce0ade15ae4c95bf6a5e16b515d7ac1f5c9c Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 12 Feb 2025 11:49:29 +0100 Subject: [PATCH 013/104] more updates --- src/DataWrangling/ECCO/ECCO_metadata.jl | 11 +++-- src/DataWrangling/ECCO/ECCO_restoring.jl | 8 ++-- src/DataWrangling/JRA55/JRA55.jl | 2 + .../JRA55/JRA55_prescribed_atmosphere.jl | 37 +++++++++-------- .../JRA55/jra55_field_time_series.jl | 40 +++++++++---------- src/DataWrangling/JRA55/jra55_metadata.jl | 24 ++++++----- src/DataWrangling/metadata.jl | 17 +++++++- 7 files changed, 79 insertions(+), 60 deletions(-) diff --git a/src/DataWrangling/ECCO/ECCO_metadata.jl b/src/DataWrangling/ECCO/ECCO_metadata.jl index c9882e69..b0c24504 100644 --- a/src/DataWrangling/ECCO/ECCO_metadata.jl +++ b/src/DataWrangling/ECCO/ECCO_metadata.jl @@ -4,18 +4,17 @@ using ClimaOcean.DataWrangling using ClimaOcean.DataWrangling: netrc_downloader import Dates: year, month, day - -using Base: @propagate_inbounds using Downloads import Oceananigans.Fields: set!, location import Base +import ClimaOcean.DataWrangling: all_dates struct ECCO2Monthly end struct ECCO2Daily end struct ECCO4Monthly end -const ECCOMetadata{D, V} = Union{Metadata{D, V<:ECCO4Monthly}, Metadata{D, V<:ECCO2Daily}, Metadata{D, V<:ECCO2Monthly}} where {D, V} +const ECCOMetadata{D, V} = Metadata{D, V} where {D, V<:Union{<:ECCO2Monthly, <:ECCO2Daily, <:ECCO4Monthly}} Base.show(io::IO, metadata::ECCOMetadata) = print(io, "ECCOMetadata:", '\n', @@ -77,9 +76,9 @@ Base.size(::ECCOMetadata{<:AbstractCFDateTime, <:ECCO2Monthly}) = (1440, 720, 50 Base.size(::ECCOMetadata{<:AbstractCFDateTime, <:ECCO4Monthly}) = (720, 360, 50, 1) # The whole range of dates in the different dataset versions -all_ECCO_dates(::ECCO4Monthly) = DateTimeProlepticGregorian(1992, 1, 1) : Month(1) : DateTimeProlepticGregorian(2023, 12, 1) -all_ECCO_dates(::ECCO2Monthly) = DateTimeProlepticGregorian(1992, 1, 1) : Month(1) : DateTimeProlepticGregorian(2023, 12, 1) -all_ECCO_dates(::ECCO2Daily) = DateTimeProlepticGregorian(1992, 1, 4) : Day(1) : DateTimeProlepticGregorian(2023, 12, 31) +all_dates(::ECCO4Monthly) = DateTimeProlepticGregorian(1992, 1, 1) : Month(1) : DateTimeProlepticGregorian(2023, 12, 1) +all_dates(::ECCO2Monthly) = DateTimeProlepticGregorian(1992, 1, 1) : Month(1) : DateTimeProlepticGregorian(2023, 12, 1) +all_dates(::ECCO2Daily) = DateTimeProlepticGregorian(1992, 1, 4) : Day(1) : DateTimeProlepticGregorian(2023, 12, 31) # File names of metadata containing multiple dates metadata_filename(metadata) = [metadata_filename(metadatum) for metadatum in metadata] diff --git a/src/DataWrangling/ECCO/ECCO_restoring.jl b/src/DataWrangling/ECCO/ECCO_restoring.jl index f0b4ffb8..712735c6 100644 --- a/src/DataWrangling/ECCO/ECCO_restoring.jl +++ b/src/DataWrangling/ECCO/ECCO_restoring.jl @@ -166,7 +166,7 @@ function ECCOFieldTimeSeries(metadata::ECCOMetadata, grid::AbstractGrid; end ECCOFieldTimeSeries(variable_name::Symbol, version=ECCO4Monthly(); kw...) = - ECCOFieldTimeSeries(ECCOMetadata(variable_name, all_ECCO_dates(version), version); kw...) + ECCOFieldTimeSeries(ECCOMetadata(variable_name, all_dates(version), version); kw...) # Variable names for restorable data struct Temperature end @@ -239,7 +239,7 @@ end """ ECCORestoring(variable_name::Symbol, [ arch_or_grid = CPU(), ]; version = ECCO4Monthly(), - dates = all_ECCO_dates(version), + dates = all_dates(version), time_indices_in_memory = 2, time_indexing = Cyclical(), mask = 1, @@ -285,7 +285,7 @@ Keyword Arguments - `version`: The version of the ECCO dataset. Default: `ECCO4Monthly()`. -- `dates`: The dates to use for the ECCO dataset. Default: `all_ECCO_dates(version)`. +- `dates`: The dates to use for the ECCO dataset. Default: `all_dates(version)`. - `time_indices_in_memory`: The number of time indices to keep in memory. The number is chosen based on a trade-off between increased performance (more indices in memory) and reduced @@ -308,7 +308,7 @@ Keyword Arguments function ECCORestoring(variable_name::Symbol, arch_or_grid = CPU(); version = ECCO4Monthly(), - dates = all_ECCO_dates(version), + dates = all_dates(version), dir = download_ECCO_cache, kw...) diff --git a/src/DataWrangling/JRA55/JRA55.jl b/src/DataWrangling/JRA55/JRA55.jl index 33fd9905..3b2d5bad 100644 --- a/src/DataWrangling/JRA55/JRA55.jl +++ b/src/DataWrangling/JRA55/JRA55.jl @@ -13,6 +13,8 @@ using Oceananigans.Grids: λnodes, φnodes, on_architecture using Oceananigans.Fields: interpolate! using Oceananigans.OutputReaders: Cyclical, TotallyInMemory, AbstractInMemoryBackend, FlavorOfFTS, time_indices +using ClimaOcean: @root + using ClimaOcean.OceanSeaIceModels: PrescribedAtmosphere, TwoBandDownwellingRadiation diff --git a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl index 42f9730b..e8643e29 100644 --- a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl +++ b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl @@ -12,18 +12,21 @@ JRA55PrescribedAtmosphere(arch::Distributed, time_indices=Colon(); kw...) = JRA55PrescribedAtmosphere(architecture::AA, time_indices=Colon(); backend = nothing, time_indexing = Cyclical(), + dates = all_dates(metadata), reference_height = 10, # meters include_rivers_and_icebergs = false, other_kw...) Return a `PrescribedAtmosphere` representing JRA55 reanalysis data. """ -function JRA55PrescribedAtmosphere(architecture::AA, time_indices=Colon(); - backend = nothing, - time_indexing = Cyclical(), - reference_height = 10, # meters - include_rivers_and_icebergs = false, - other_kw...) +function JRA55PrescribedAtmosphere(metadata::JRA55Metadata, arch_or_grid = CPU(); + backend = nothing, + time_indexing = Cyclical(), + reference_height = 10, # meters + include_rivers_and_icebergs = false, + other_kw...) + + time_indices = JRA55_time_indices(metadata.version, metadata.dates) if isnothing(backend) # apply a default Ni = try @@ -40,15 +43,15 @@ function JRA55PrescribedAtmosphere(architecture::AA, time_indices=Colon(); kw = (; time_indices, time_indexing, backend, architecture) kw = merge(kw, other_kw) - ua = JRA55_field_time_series(:eastward_velocity; kw...) - va = JRA55_field_time_series(:northward_velocity; kw...) - Ta = JRA55_field_time_series(:temperature; kw...) - qa = JRA55_field_time_series(:specific_humidity; kw...) - pa = JRA55_field_time_series(:sea_level_pressure; kw...) - Fra = JRA55_field_time_series(:rain_freshwater_flux; kw...) - Fsn = JRA55_field_time_series(:snow_freshwater_flux; kw...) - Ql = JRA55_field_time_series(:downwelling_longwave_radiation; kw...) - Qs = JRA55_field_time_series(:downwelling_shortwave_radiation; kw...) + ua = JRA55FieldTimeSeries(:eastward_velocity; kw...) + va = JRA55FieldTimeSeries(:northward_velocity; kw...) + Ta = JRA55FieldTimeSeries(:temperature; kw...) + qa = JRA55FieldTimeSeries(:specific_humidity; kw...) + pa = JRA55FieldTimeSeries(:sea_level_pressure; kw...) + Fra = JRA55FieldTimeSeries(:rain_freshwater_flux; kw...) + Fsn = JRA55FieldTimeSeries(:snow_freshwater_flux; kw...) + Ql = JRA55FieldTimeSeries(:downwelling_longwave_radiation; kw...) + Qs = JRA55FieldTimeSeries(:downwelling_shortwave_radiation; kw...) freshwater_flux = (rain = Fra, snow = Fsn) @@ -57,8 +60,8 @@ function JRA55PrescribedAtmosphere(architecture::AA, time_indices=Colon(); # a different frequency than the rest of the JRA55 data. We use `PrescribedAtmospheres` # "auxiliary_freshwater_flux" feature to represent them. if include_rivers_and_icebergs - Fri = JRA55_field_time_series(:river_freshwater_flux; kw...) - Fic = JRA55_field_time_series(:iceberg_freshwater_flux; kw...) + Fri = JRA55FieldTimeSeries(:river_freshwater_flux; kw...) + Fic = JRA55FieldTimeSeries(:iceberg_freshwater_flux; kw...) auxiliary_freshwater_flux = (rivers = Fri, icebergs = Fic) else auxiliary_freshwater_flux = nothing diff --git a/src/DataWrangling/JRA55/jra55_field_time_series.jl b/src/DataWrangling/JRA55/jra55_field_time_series.jl index 1043ab2f..1fa1d0ed 100644 --- a/src/DataWrangling/JRA55/jra55_field_time_series.jl +++ b/src/DataWrangling/JRA55/jra55_field_time_series.jl @@ -1,3 +1,4 @@ +using ClimaOcean.DataWrangling: all_dates, native_times download_JRA55_cache::String = "" @@ -71,19 +72,6 @@ function compute_bounding_indices(longitude, latitude, grid, LX, LY, λc, φc) return i₁, i₂, j₁, j₂, TX end -# Convert dates to range until Oceananigans supports dates natively -function JRA55_times(native_times, start_time=native_times[1]) - - times = [] - for native_time in native_times - time = native_time - start_time - time = Second(time).value - push!(times, time) - end - - return times -end - struct JRA55NetCDFBackend <: AbstractInMemoryBackend{Int} start :: Int length :: Int @@ -221,13 +209,21 @@ Keyword arguments - `time_chunks_in_memory`: number of fields held in memory. If `nothing` then the whole timeseries is loaded (not recommended). """ -function JRA55FieldTimeSeries(variable_name, architecture=CPU(); +function JRA55FieldTimeSeries(variable_name::Symbol, + arch_or_grid = CPU(); version = JRA55RepeatYear(), - dates = all_JRA55_dates(version), + dates = all_dates(version), + dir = download_JRA55_cache, + kw...) + + metadata = Metadata(variable_name, dates, version, dir) + + return JRA55FieldTimeSeries(metadata, arch_or_grid; kw...) +end + +function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(); grid = nothing, location = nothing, - url = nothing, - dir = download_JRA55_cache, filename = nothing, shortname = nothing, latitude = nothing, @@ -237,7 +233,7 @@ function JRA55FieldTimeSeries(variable_name, architecture=CPU(); preprocess_chunk_size = 10, preprocess_architecture = CPU()) - time_indices = JRA55_time_indices(dates) + time_indices = JRA55_time_indices(metadata.version, metadata.dates) # OnDisk backends do not support time interpolation! # Disallow OnDisk for JRA55 dataset loading @@ -346,7 +342,7 @@ function JRA55FieldTimeSeries(variable_name, architecture=CPU(); # Probably with arguments that take latitude, longitude bounds. i₁, i₂, j₁, j₂, TX = compute_bounding_indices(longitude, latitude, grid, LX, LY, λc, φc) - native_times = ds["time"][time_indices] + native_JRA55_times = ds["time"][time_indices] data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] λr = λn[i₁:i₂+1] φr = φn[j₁:j₂+1] @@ -364,7 +360,7 @@ function JRA55FieldTimeSeries(variable_name, architecture=CPU(); topology = (TX, Bounded, Flat)) boundary_conditions = FieldBoundaryConditions(JRA55_native_grid, (Center, Center, Nothing)) - times = JRA55_times(native_times) + times = native_times(native_JRA55_times) if backend isa JRA55NetCDFBackend fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; @@ -413,7 +409,7 @@ function JRA55FieldTimeSeries(variable_name, architecture=CPU(); all_datetimes = ds["time"][time_indices] all_Nt = length(all_datetimes) - all_times = JRA55_times(all_datetimes) + all_times = native_times(all_datetimes) on_disk_fts = FieldTimeSeries{LX, LY, Nothing}(preprocessing_grid, all_times; boundary_conditions, @@ -461,7 +457,7 @@ function JRA55FieldTimeSeries(variable_name, architecture=CPU(); end # Re-compute times - new_times = JRA55_times(all_times[time_indices_in_memory], all_times[n₁]) + new_times = native_times(all_times[time_indices_in_memory], all_times[n₁]) native_fts.times = new_times # Re-compute data diff --git a/src/DataWrangling/JRA55/jra55_metadata.jl b/src/DataWrangling/JRA55/jra55_metadata.jl index 5dfa6dd2..513b1ac7 100644 --- a/src/DataWrangling/JRA55/jra55_metadata.jl +++ b/src/DataWrangling/JRA55/jra55_metadata.jl @@ -5,18 +5,24 @@ import Oceananigans.Fields: set! import Base using ClimaOcean.DataWrangling: Metadata +import ClimaOcean.DataWrangling: all_dates struct JRA55MultipleYears end struct JRA55RepeatYear end -const JRA55Metadata{T, V} = Union{Metadata{T, V<:JRA55MultipleYears}, Metadata{T, V<:JRA55RepeatYear}} where {T, V} +const JRA55Metadata{T, V} = Metadata{T, V} where {T, V<:Union{<:JRA55MultipleYears, <:JRA55RepeatYear}} Base.size(data::JRA55Metadata) = (640, 320, length(data.dates)) Base.size(::JRA55Metadata{<:AbstractCFDateTime}) = (640, 320, 1) # The whole range of dates in the different dataset versions -all_JRA55_times(::JRA55RepeatYear) = DateTimeProlepticGregorian(1990, 1, 1) : Hour(3) : DateTimeProlepticGregorian(1991, 1, 1) -all_JRA55_times(::JRA55MultipleYears) = DateTimeProlepticGregorian(1958, 1, 1) : Hour(3) : DateTimeProlepticGregorian(2021, 1, 1) +all_dates(::JRA55RepeatYear) = DateTimeProlepticGregorian(1990, 1, 1) : Hour(3) : DateTimeProlepticGregorian(1991, 1, 1) +all_dates(::JRA55MultipleYears) = DateTimeProlepticGregorian(1958, 1, 1) : Hour(3) : DateTimeProlepticGregorian(2021, 1, 1) + +function JRA55_time_indices(version, dates) + all_JRA55_dates = all_dates(version) + return findall(all_JRA55_dates .== dates) +end # File name generation specific to each Dataset version function metadata_filename(metadata::Metadata{<:AbstractCFDateTime, <:JRA55MultipleYears}) @@ -35,7 +41,7 @@ function metadata_filename(metadata::Metadata{<:AbstractCFDateTime, <:JRA55Repea end # Convenience functions -short_name(data::JRA55Metadata) = jra55_short_names[data.name] +short_name(data::JRA55Metadata) = JRA55_short_names[data.name] location(data::JRA55Metadata) = (Center, Center, Center) # A list of all variables provided in the JRA55 dataset: @@ -82,9 +88,7 @@ field_time_series_short_names = Dict( :northward_velocity => "va", # Northward near-surface wind ) -urls(metadata::Metadata{<:Any, <:JRA55RepeatYear}) = jra55_repeat_year_urls[metadata.name] - -jra55_repeat_year_urls = Dict( +JRA55_repeat_year_urls = Dict( :shortwave_radiation => "https://www.dropbox.com/scl/fi/z6fkvmd9oe3ycmaxta131/" * "RYF.rsds.1990_1991.nc?rlkey=r7q6zcbj6a4fxsq0f8th7c4tc&dl=0", @@ -127,11 +131,13 @@ jra55_repeat_year_urls = Dict( variable_is_three_dimensional(data::JRA55Metadata) = false -urls(metadata::Metadata{<:Any, <:JRA55RepeatYear}) = jra55_repeat_year_urls[metadata.name] +urls(metadata::Metadata{<:Any, <:JRA55RepeatYear}) = JRA55_repeat_year_urls[metadata.name] +# TODO: +# urls(metadata::Metadata{<:Any, <:JRA55MultipleYears}) = ... function download_dataset!(metadata::JRA55Metadata; url = urls(metadata), - dir = download_jra55_cache) + dir = download_JRA55_cache) for data in metadata filename = metadata_filename(data) diff --git a/src/DataWrangling/metadata.jl b/src/DataWrangling/metadata.jl index 4c9f49ae..8fc93dc5 100644 --- a/src/DataWrangling/metadata.jl +++ b/src/DataWrangling/metadata.jl @@ -1,3 +1,7 @@ +using CFTime +using Dates +using Base: @propagate_inbounds + """ Metadata{D, V} @@ -24,13 +28,14 @@ Base.show(io::IO, metadata::Metadata) = # Treat Metadata as an array to allow iteration over the dates. Base.length(metadata::Metadata) = length(metadata.dates) Base.eltype(metadata::Metadata) = Base.eltype(metadata.dates) + @propagate_inbounds Base.getindex(m::Metadata, i::Int) = Metadata(m.name, m.dates[i], m.version, m.dir) @propagate_inbounds Base.first(m::Metadata) = Metadata(m.name, m.dates[1], m.version, m.dir) @propagate_inbounds Base.last(m::Metadata) = Metadata(m.name, m.dates[end], m.version, m.dir) @inline function Base.iterate(m::Metadata, i=1) if (i % UInt) - 1 < length(m) - return ECCOMetadata(m.name, m.dates[i], m.version, m.dir), i + 1 + return Metadata(m.name, m.dates[i], m.version, m.dir), i + 1 else return nothing end @@ -65,4 +70,12 @@ function native_times(metadata; start_time = first(metadata).dates) end return times -end \ No newline at end of file +end + +""" + all_dates(metadata) + +Extracts all the dates of the given metadata formatted using the `DateTimeProlepticGregorian` type. +Needs to be extended by any new dataset version. +""" +all_dates(metadata) = all_dates(metadata.version) From 68dfdb09970a2607d16461b704eb5a275ab7c5f5 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 12 Feb 2025 12:57:31 +0100 Subject: [PATCH 014/104] all this should work? --- src/DataWrangling/DataWrangling.jl | 2 + src/DataWrangling/ECCO/ECCO_metadata.jl | 8 +-- src/DataWrangling/ECCO/ECCO_restoring.jl | 31 +-------- .../JRA55/jra55_field_time_series.jl | 67 +++++++------------ src/DataWrangling/JRA55/jra55_metadata.jl | 60 +++++++++++------ src/DataWrangling/metadata.jl | 18 +++-- test/test_ecco.jl | 8 +-- 7 files changed, 85 insertions(+), 109 deletions(-) diff --git a/src/DataWrangling/DataWrangling.jl b/src/DataWrangling/DataWrangling.jl index b45f3d75..b1f2e064 100644 --- a/src/DataWrangling/DataWrangling.jl +++ b/src/DataWrangling/DataWrangling.jl @@ -1,5 +1,7 @@ module DataWrangling +export Metadata + using Oceananigans using Downloads using Printf diff --git a/src/DataWrangling/ECCO/ECCO_metadata.jl b/src/DataWrangling/ECCO/ECCO_metadata.jl index b0c24504..11e73fe0 100644 --- a/src/DataWrangling/ECCO/ECCO_metadata.jl +++ b/src/DataWrangling/ECCO/ECCO_metadata.jl @@ -1,14 +1,14 @@ using CFTime using Dates using ClimaOcean.DataWrangling -using ClimaOcean.DataWrangling: netrc_downloader +using ClimaOcean.DataWrangling: netrc_downloader, metadata_path import Dates: year, month, day using Downloads import Oceananigans.Fields: set!, location import Base -import ClimaOcean.DataWrangling: all_dates +import ClimaOcean.DataWrangling: all_dates, metadata_filename struct ECCO2Monthly end struct ECCO2Daily end @@ -80,9 +80,6 @@ all_dates(::ECCO4Monthly) = DateTimeProlepticGregorian(1992, 1, 1) : Month(1) : all_dates(::ECCO2Monthly) = DateTimeProlepticGregorian(1992, 1, 1) : Month(1) : DateTimeProlepticGregorian(2023, 12, 1) all_dates(::ECCO2Daily) = DateTimeProlepticGregorian(1992, 1, 4) : Day(1) : DateTimeProlepticGregorian(2023, 12, 31) -# File names of metadata containing multiple dates -metadata_filename(metadata) = [metadata_filename(metadatum) for metadatum in metadata] - # File name generation specific to each Dataset version function metadata_filename(metadata::ECCOMetadata{<:AbstractCFDateTime, <:ECCO4Monthly}) shortname = short_name(metadata) @@ -106,7 +103,6 @@ function metadata_filename(metadata::ECCOMetadata{<:AbstractCFDateTime}) end # Convenience functions -metadata_path(metadata) = joinpath(metadata.dir, metadata_filename(metadata)) short_name(data::ECCOMetadata{<:Any, <:ECCO2Daily}) = ECCO2_short_names[data.name] short_name(data::ECCOMetadata{<:Any, <:ECCO2Monthly}) = ECCO2_short_names[data.name] short_name(data::ECCOMetadata{<:Any, <:ECCO4Monthly}) = ECCO4_short_names[data.name] diff --git a/src/DataWrangling/ECCO/ECCO_restoring.jl b/src/DataWrangling/ECCO/ECCO_restoring.jl index 712735c6..337d0cbc 100644 --- a/src/DataWrangling/ECCO/ECCO_restoring.jl +++ b/src/DataWrangling/ECCO/ECCO_restoring.jl @@ -10,7 +10,7 @@ using JLD2 using Dates: Second using ClimaOcean: stateindex -using ClimaOcean.DataWrangling: NearestNeighborInpainting +using ClimaOcean.DataWrangling: NearestNeighborInpainting, native_times import Oceananigans.Fields: set! import Oceananigans.Forcings: regularize_forcing @@ -78,33 +78,6 @@ function set!(fts::ECCOFieldTimeSeries) return nothing end -""" - ECCO_times(metadata; start_time = first(metadata).dates) - -Extract the time values from the given metadata and calculates the time difference -from the start time. - -Arguments -========= -- `metadata`: The metadata containing the date information. -- `start_time`: The start time for calculating the time difference. Defaults to the first date in the metadata. - -Returns -======= -An array of time differences in seconds. -""" -function ECCO_times(metadata; start_time = first(metadata).dates) - times = zeros(length(metadata)) - for (t, data) in enumerate(metadata) - date = data.dates - time = date - start_time - time = Second(time).value - times[t] = time - end - - return times -end - """ ECCOFieldTimeSeries(metadata::ECCOMetadata [, arch_or_grid=CPU() ]; time_indices_in_memory = 2, @@ -156,7 +129,7 @@ function ECCOFieldTimeSeries(metadata::ECCOMetadata, grid::AbstractGrid; inpainting isa Int && (inpainting = NearestNeighborInpainting(inpainting)) backend = ECCONetCDFBackend(time_indices_in_memory, metadata; on_native_grid, inpainting, cache_inpainted_data) - times = ECCO_times(metadata) + times = native_times(metadata) loc = LX, LY, LZ = location(metadata) boundary_conditions = FieldBoundaryConditions(grid, loc) fts = FieldTimeSeries{LX, LY, LZ}(grid, times; backend, time_indexing, boundary_conditions) diff --git a/src/DataWrangling/JRA55/jra55_field_time_series.jl b/src/DataWrangling/JRA55/jra55_field_time_series.jl index 1fa1d0ed..c604e213 100644 --- a/src/DataWrangling/JRA55/jra55_field_time_series.jl +++ b/src/DataWrangling/JRA55/jra55_field_time_series.jl @@ -1,4 +1,5 @@ using ClimaOcean.DataWrangling: all_dates, native_times +using Oceananigans.Grids: AbstractGrid download_JRA55_cache::String = "" @@ -89,6 +90,7 @@ Base.summary(backend::JRA55NetCDFBackend) = string("JRA55NetCDFBackend(", backen const JRA55NetCDFFTS = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend} +# TODO: This will need to change when we add a method for JRA55MultipleYears function set!(fts::JRA55NetCDFFTS, path::String=fts.path, name::String=fts.name) ds = Dataset(path) @@ -194,15 +196,6 @@ Keyword arguments Used to slice the data when loading into memory. Default: nothing, which retains the longitude range of the native grid. -- `url`: The url accessed to download the data for `variable_name`. - Default: `ClimaOcean.JRA55.urls[variable_name]`. - -- `filename`: The name of the downloaded file. - Default: `ClimaOcean.JRA55.filenames[variable_name]`. - -- `shortname`: The "short name" of `variable_name` inside its NetCDF file. - Default: `ClimaOcean.JRA55.JRA55_short_names[variable_name]`. - - `interpolated_file`: file holding an Oceananigans compatible version of the JRA55 data. If it does not exist it will be generated. @@ -221,11 +214,7 @@ function JRA55FieldTimeSeries(variable_name::Symbol, return JRA55FieldTimeSeries(metadata, arch_or_grid; kw...) end -function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(); - grid = nothing, - location = nothing, - filename = nothing, - shortname = nothing, +function JRA55FieldTimeSeries(metadata::JRA55Metadata, arch_or_grid = CPU(); latitude = nothing, longitude = nothing, backend = InMemory(), @@ -233,7 +222,25 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(); preprocess_chunk_size = 10, preprocess_architecture = CPU()) + # First thing: we download the dataset! + download_dataset!(metadata) + + # Unpack metadata details time_indices = JRA55_time_indices(metadata.version, metadata.dates) + shortname = short_name(metadata) + dir = metadata.dir + variable_name = metadata.name + + filepath = metadata_path(metadata) # Might be multiple paths!!! + filepath = filepath isa AbstractArray ? first(filepath) : filepath + + if arch_or_grid isa AbstractGrid + grid = arch_or_grid + architecture = Oceananigans.Grids.architecture(grid) + else + grid = nothing + architecture = arch_or_grid + end # OnDisk backends do not support time interpolation! # Disallow OnDisk for JRA55 dataset loading @@ -242,7 +249,7 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(); throw(ArgumentError(msg)) end - if isnothing(filename) && !(variable_name ∈ JRA55_variable_names) + if !(variable_name ∈ JRA55_variable_names) variable_strs = Tuple(" - :$name \n" for name in JRA55_variable_names) variables_msg = prod(variable_strs) @@ -253,18 +260,6 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(); throw(ArgumentError(msg)) end - filepath = isnothing(filename) ? joinpath(dir, filenames[variable_name]) : joinpath(dir, filename) - - if !isnothing(filename) && !isfile(filepath) && isnothing(url) - throw(ArgumentError("A filename was provided without a url, but the file does not exist.\n \ - If intended, please provide both the filename and url that should be used \n \ - to download the new file.")) - end - - isnothing(filename) && (filename = filenames[variable_name]) - isnothing(shortname) && (shortname = JRA55_short_names[variable_name]) - isnothing(url) && (url = urls[variable_name]) - # Record some important user decisions totally_in_memory = backend isa TotallyInMemory on_native_grid = isnothing(grid) @@ -273,12 +268,6 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(); jld2_filepath = joinpath(dir, string("JRA55_repeat_year_", variable_name, ".jld2")) fts_name = field_time_series_short_names[variable_name] - # Note, we don't re-use existing jld2 files. - @root begin - isfile(filepath) || download(url, filepath; progress=download_progress) - isfile(jld2_filepath) && rm(jld2_filepath) - end - # Determine default time indices if totally_in_memory # In this case, the whole time series is in memory. @@ -308,13 +297,6 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(); end end - # Set a default location. - if isnothing(location) - LX = LY = Center - else - LX, LY = location - end - ds = Dataset(filepath) # Note that each file should have the variables @@ -340,9 +322,8 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(); # TODO: support loading just part of the JRA55 data. # Probably with arguments that take latitude, longitude bounds. - i₁, i₂, j₁, j₂, TX = compute_bounding_indices(longitude, latitude, grid, LX, LY, λc, φc) + i₁, i₂, j₁, j₂, TX = compute_bounding_indices(longitude, latitude, grid, Center, Center, λc, φc) - native_JRA55_times = ds["time"][time_indices] data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] λr = λn[i₁:i₂+1] φr = φn[j₁:j₂+1] @@ -360,7 +341,7 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(); topology = (TX, Bounded, Flat)) boundary_conditions = FieldBoundaryConditions(JRA55_native_grid, (Center, Center, Nothing)) - times = native_times(native_JRA55_times) + times = native_times(metadata) if backend isa JRA55NetCDFBackend fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; diff --git a/src/DataWrangling/JRA55/jra55_metadata.jl b/src/DataWrangling/JRA55/jra55_metadata.jl index 513b1ac7..977afb50 100644 --- a/src/DataWrangling/JRA55/jra55_metadata.jl +++ b/src/DataWrangling/JRA55/jra55_metadata.jl @@ -1,11 +1,17 @@ using CFTime using Dates +using Downloads + +using ClimaOcean.DataWrangling +using ClimaOcean.DataWrangling: Metadata, metadata_path, download_progress + import Dates: year, month, day import Oceananigans.Fields: set! import Base +import ClimaOcean.DataWrangling: all_dates, metadata_filename -using ClimaOcean.DataWrangling: Metadata -import ClimaOcean.DataWrangling: all_dates +import Oceananigans.Fields: set!, location +import ClimaOcean.DataWrangling: all_dates, metadata_filename struct JRA55MultipleYears end struct JRA55RepeatYear end @@ -21,25 +27,32 @@ all_dates(::JRA55MultipleYears) = DateTimeProlepticGregorian(1958, 1, 1) : Hour( function JRA55_time_indices(version, dates) all_JRA55_dates = all_dates(version) - return findall(all_JRA55_dates .== dates) -end + indices = Int[] + + for date in dates + index = findfirst(x -> x == date, all_JRA55_dates) + push!(indices, index) + end -# File name generation specific to each Dataset version -function metadata_filename(metadata::Metadata{<:AbstractCFDateTime, <:JRA55MultipleYears}) - # fix the filename - shortname = short_name(metadata) - year = Dates.year(metadata.dates) - suffix = "_input4MIPs_atmosphericState_OMIP_MRI-JRA55-do-1-5-0_gr_" - dates = "($year)01010130-($year)12312330.nc" - return shortname * suffix * dates * ".nc" + return indices end # File name generation specific to each Dataset version -function metadata_filename(metadata::Metadata{<:AbstractCFDateTime, <:JRA55RepeatYear}) +function metadata_filename(metadata::Metadata{<:Any, <:JRA55RepeatYear}) # No difference shortname = short_name(metadata) return "RYF." * shortname * ".1990_1991.nc" end +# TODO: Implement this function for JRA55MultipleYears +# function metadata_filename(metadata::Metadata{<:AbstractCFDateTime, <:JRA55MultipleYears}) +# # fix the filename +# shortname = short_name(metadata) +# year = Dates.year(metadata.dates) +# suffix = "_input4MIPs_atmosphericState_OMIP_MRI-JRA55-do-1-5-0_gr_" +# dates = "($year)01010130-($year)12312330.nc" +# return shortname * suffix * dates * ".nc" +# end + # Convenience functions short_name(data::JRA55Metadata) = JRA55_short_names[data.name] location(data::JRA55Metadata) = (Center, Center, Center) @@ -131,19 +144,22 @@ JRA55_repeat_year_urls = Dict( variable_is_three_dimensional(data::JRA55Metadata) = false -urls(metadata::Metadata{<:Any, <:JRA55RepeatYear}) = JRA55_repeat_year_urls[metadata.name] +urls(metadata::Metadata{<:Any, <:JRA55RepeatYear}) = JRA55_repeat_year_urls[metadata.name] # TODO: # urls(metadata::Metadata{<:Any, <:JRA55MultipleYears}) = ... -function download_dataset!(metadata::JRA55Metadata; - url = urls(metadata), - dir = download_JRA55_cache) +metadata_url(prefix, m::Metadata{<:Any, <:JRA55RepeatYear}) = prefix # No specific name for this url + +# TODO: This will need to change when we add a method for JRA55MultipleYears +function download_dataset!(metadata::JRA55Metadata; url = urls(metadata)) + + asyncmap(metadata, ntasks=10) do metadatum # Distribute the download among tasks + + fileurl = metadata_url(url, metadatum) + filepath = metadata_path(metadatum) - for data in metadata - filename = metadata_filename(data) - - if !isfile(filename) - download(url, dir) + if !isfile(filepath) + Downloads.download(fileurl, filepath; progress=download_progress) end end diff --git a/src/DataWrangling/metadata.jl b/src/DataWrangling/metadata.jl index 8fc93dc5..79423463 100644 --- a/src/DataWrangling/metadata.jl +++ b/src/DataWrangling/metadata.jl @@ -47,20 +47,25 @@ Base.last(metadata::Metadata{<:AbstractCFDateTime}) = metadata Base.iterate(metadata::Metadata{<:AbstractCFDateTime}) = (metadata, nothing) Base.iterate(::Metadata{<:AbstractCFDateTime}, ::Any) = nothing +metadata_path(metadata) = joinpath(metadata.dir, metadata_filename(metadata)) + + """ - native_times(metadata; start_time = metadata.dates[1]) + native_times(metadata; start_time = first(metadata).dates) -Extracts the time values from the given metadata and calculates the time difference +Extract the time values from the given metadata and calculates the time difference from the start time. -# Arguments +Arguments +========= - `metadata`: The metadata containing the date information. - `start_time`: The start time for calculating the time difference. Defaults to the first date in the metadata. -# Returns +Returns +======= An array of time differences in seconds. """ -function native_times(metadata; start_time = first(metadata).dates) +function native_times(metadata; start_time=first(metadata).dates) times = zeros(length(metadata)) for (t, data) in enumerate(metadata) date = data.dates @@ -79,3 +84,6 @@ Extracts all the dates of the given metadata formatted using the `DateTimeProlep Needs to be extended by any new dataset version. """ all_dates(metadata) = all_dates(metadata.version) + +# File names of metadata containing multiple dates +metadata_filename(metadata) = [metadata_filename(metadatum) for metadatum in metadata] diff --git a/test/test_ecco.jl b/test/test_ecco.jl index 04a125d3..3910ae0e 100644 --- a/test/test_ecco.jl +++ b/test/test_ecco.jl @@ -5,7 +5,7 @@ using Dates using ClimaOcean using ClimaOcean.ECCO -using ClimaOcean.ECCO: ECCO_field, metadata_path, ECCO_times +using ClimaOcean.ECCO: ECCO_field, metadata_path, native_times using ClimaOcean.DataWrangling: NearestNeighborInpainting using Oceananigans.Grids: topology @@ -47,8 +47,8 @@ inpainting = NearestNeighborInpainting(2) @test Nz == size(metadata)[3] @test Nt == size(metadata)[4] - @test fts.times[1] == ECCO_times(metadata)[1] - @test fts.times[end] == ECCO_times(metadata)[end] + @test fts.times[1] == native_times(metadata)[1] + @test fts.times[end] == native_times(metadata)[end] datum = first(metadata) ψ = ECCO_field(datum, architecture=arch, inpainting=NearestNeighborInpainting(2)) @@ -207,7 +207,7 @@ end rate = 1 / 1000.0, inpainting) - times = ECCO_times(t_restoring.field_time_series.backend.metadata) + times = native_times(t_restoring.field_time_series.backend.metadata) ocean = ocean_simulation(grid, forcing = (; T = t_restoring)) ocean.model.clock.time = times[3] + 2 * Units.days From f212e885d2f434bf904b5be128ae5e0bf65030a7 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 12 Feb 2025 12:59:04 +0100 Subject: [PATCH 015/104] clear comments --- src/DataWrangling/JRA55/jra55_field_time_series.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/DataWrangling/JRA55/jra55_field_time_series.jl b/src/DataWrangling/JRA55/jra55_field_time_series.jl index c604e213..f1a0129a 100644 --- a/src/DataWrangling/JRA55/jra55_field_time_series.jl +++ b/src/DataWrangling/JRA55/jra55_field_time_series.jl @@ -144,13 +144,9 @@ new_backend(::JRA55NetCDFBackend, start, length) = JRA55NetCDFBackend(start, len dates = all_JRA55_dates(version), latitude = nothing, longitude = nothing, - location = nothing, - url = nothing, dir = download_JRA55_cache, filename = nothing, shortname = nothing, - latitude = nothing, - longitude = nothing, backend = InMemory(), time_indexing = Cyclical(), preprocess_chunk_size = 10, From 1a68d4e8aa4d90c4d097187d7616e1a41c48c1e4 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 12 Feb 2025 12:59:51 +0100 Subject: [PATCH 016/104] vestigial code --- compact_data.jl | 71 ------------------------------------------------- 1 file changed, 71 deletions(-) delete mode 100644 compact_data.jl diff --git a/compact_data.jl b/compact_data.jl deleted file mode 100644 index 3cdf3179..00000000 --- a/compact_data.jl +++ /dev/null @@ -1,71 +0,0 @@ -using Oceananigans -using Oceananigans.Grids: halo_size -using Oceananigans.Fields: location -using JLD2 - -const Nx = 256 -const Ny = 128 -const Nz = 1 -const nx = 256 ÷ 2 -const ny = 128 ÷ 2 - -grid = TripolarGrid(size=(Nx, Ny, Nz)) -Hx, Hy, Hz = halo_size(grid) - -function read_bathymetry(prefix) - bottom_height = zeros(Nx, Ny) - - for xrank in 0:1, yrank in 0:1 - rank = yrank + 2 * xrank - file = jldopen(prefix * "_$(rank).jld2") - irange = nx * xrank + 1 : nx * (xrank + 1) - jrange = ny * yrank + 1 : ny * (yrank + 1) - - data = file["serialized/grid"].immersed_boundary.bottom_height[Hx+1:nx+Hx, Hy+1:ny+Hy, 1] - bottom_height[irange, jrange] .= data - close(file) - end - - return bottom_height -end - -bottom_height = read_bathymetry("surface_fields") - -grid = ImmersedBoundaryGrid(grid, GridFittedBottom(bottom_height)) - -file0 = jldopen("surface_fields_0.jld2") -iters = keys(file0["timeseries/t"]) -times = Float64[file0["timeseries/t/$(iter)"] for iter in iters] -close(file0) - -utmp = FieldTimeSeries{Face, Center, Nothing}(grid, times; backend=OnDisk(), path="surface_fields.jld2", name="u") -vtmp = FieldTimeSeries{Center, Face, Nothing}(grid, times; backend=OnDisk(), path="surface_fields.jld2", name="v") -Ttmp = FieldTimeSeries{Center, Center, Nothing}(grid, times; backend=OnDisk(), path="surface_fields.jld2", name="T") -Stmp = FieldTimeSeries{Center, Center, Nothing}(grid, times; backend=OnDisk(), path="surface_fields.jld2", name="S") -etmp = FieldTimeSeries{Center, Center, Nothing}(grid, times; backend=OnDisk(), path="surface_fields.jld2", name="e") - -function set_distributed_field_time_series!(fts, prefix) - field = Field{location(fts)...}(grid) - Ny = size(fts, 2) - for (idx, iter) in enumerate(iters) - @info "doing iter $idx of $(length(iters))" - for xrank in 0:1, yrank in 0:1 - rank = yrank + 2 * xrank - file = jldopen(prefix * "_$(rank).jld2") - irange = nx * xrank + 1 : nx * (xrank + 1) - jrange = ny * yrank + 1 : ny * (yrank + 1) - data = file["timeseries/$(fts.name)/$(iter)"][Hx+1:nx+Hx, Hy+1:ny+Hy, 1] - - interior(field, irange, jrange, 1) .= data - close(file) - end - - set!(fts, field, idx) - end -end - -set_distributed_field_time_series!(utmp, "surface_fields") -set_distributed_field_time_series!(vtmp, "surface_fields") -set_distributed_field_time_series!(Ttmp, "surface_fields") -set_distributed_field_time_series!(Stmp, "surface_fields") -set_distributed_field_time_series!(etmp, "surface_fields") \ No newline at end of file From c318e54498bbbd9bbf01de94e017c7ba11301d70 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 12 Feb 2025 13:06:56 +0100 Subject: [PATCH 017/104] works --- src/DataWrangling/JRA55/jra55_field_time_series.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataWrangling/JRA55/jra55_field_time_series.jl b/src/DataWrangling/JRA55/jra55_field_time_series.jl index f1a0129a..77b2ef1c 100644 --- a/src/DataWrangling/JRA55/jra55_field_time_series.jl +++ b/src/DataWrangling/JRA55/jra55_field_time_series.jl @@ -245,7 +245,7 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, arch_or_grid = CPU(); throw(ArgumentError(msg)) end - if !(variable_name ∈ JRA55_variable_names) + if !(variable_name ∈ JRA55_variable_names) variable_strs = Tuple(" - :$name \n" for name in JRA55_variable_names) variables_msg = prod(variable_strs) From 79b593534370d11124b97690198e8042e7329b24 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 12 Feb 2025 13:21:31 +0100 Subject: [PATCH 018/104] veeery much simplified --- .../JRA55/jra55_field_time_series.jl | 171 +++--------------- 1 file changed, 22 insertions(+), 149 deletions(-) diff --git a/src/DataWrangling/JRA55/jra55_field_time_series.jl b/src/DataWrangling/JRA55/jra55_field_time_series.jl index 77b2ef1c..006df82f 100644 --- a/src/DataWrangling/JRA55/jra55_field_time_series.jl +++ b/src/DataWrangling/JRA55/jra55_field_time_series.jl @@ -176,13 +176,15 @@ The `variable_name`s (and their `shortname`s used in NetCDF files) available fro Keyword arguments ================= -- `architecture`: Architecture for the `FieldTimeSeries`. - Default: CPU() +- `architecture`: Architecture for the `FieldTimeSeries`. Default: CPU() -- `time_indices`: Indices of the timeseries to extract from file. - For example, `time_indices=1:3` returns a - `FieldTimeSeries` with the first three time snapshots - of `variable_name`. +- `dates`: The date(s) of the metadata. Note this can either be a single date, + representing a snapshot, or a range of dates, representing a time-series. + Default: `all_dates(version)` (see `all_dates`). + +- `version`: The data version. The only supported versions is `JRA55RepeatYear()` + +- `dir`: The directory of the data file. Default: `ClimaOcean.JRA55.download_JRA55_cache`. - `latitude`: Guiding latitude bounds for the resulting grid. Used to slice the data when loading into memory. @@ -192,14 +194,13 @@ Keyword arguments Used to slice the data when loading into memory. Default: nothing, which retains the longitude range of the native grid. -- `interpolated_file`: file holding an Oceananigans compatible version of the JRA55 data. - If it does not exist it will be generated. - -- `time_chunks_in_memory`: number of fields held in memory. If `nothing` then the whole timeseries - is loaded (not recommended). +- `backend`: Backend for the `FieldTimeSeries`. The two options are + * `InMemory()`: the whole time series is loaded into memory. + * `JRA55NetCDFBackend(total_time_instances_in_memory)`: only a subset of the time series is loaded into memory. + Default: `InMemory()`. """ function JRA55FieldTimeSeries(variable_name::Symbol, - arch_or_grid = CPU(); + architecture = CPU(); version = JRA55RepeatYear(), dates = all_dates(version), dir = download_JRA55_cache, @@ -207,16 +208,14 @@ function JRA55FieldTimeSeries(variable_name::Symbol, metadata = Metadata(variable_name, dates, version, dir) - return JRA55FieldTimeSeries(metadata, arch_or_grid; kw...) + return JRA55FieldTimeSeries(metadata, architecture; kw...) end -function JRA55FieldTimeSeries(metadata::JRA55Metadata, arch_or_grid = CPU(); +function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture = CPU(); latitude = nothing, longitude = nothing, backend = InMemory(), - time_indexing = Cyclical(), - preprocess_chunk_size = 10, - preprocess_architecture = CPU()) + time_indexing = Cyclical()) # First thing: we download the dataset! download_dataset!(metadata) @@ -224,24 +223,15 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, arch_or_grid = CPU(); # Unpack metadata details time_indices = JRA55_time_indices(metadata.version, metadata.dates) shortname = short_name(metadata) - dir = metadata.dir variable_name = metadata.name filepath = metadata_path(metadata) # Might be multiple paths!!! filepath = filepath isa AbstractArray ? first(filepath) : filepath - if arch_or_grid isa AbstractGrid - grid = arch_or_grid - architecture = Oceananigans.Grids.architecture(grid) - else - grid = nothing - architecture = arch_or_grid - end - # OnDisk backends do not support time interpolation! # Disallow OnDisk for JRA55 dataset loading - if backend isa OnDisk - msg = string("We cannot load the JRA55 dataset with an `OnDisk` backend") + if !(backend isa JRA55NetCDFBackend) && !(backend isa TotallyInMemory) + msg = string("We cannot load the JRA55 dataset with an `OnDisk` or a `PartiallyInMemory` backend") throw(ArgumentError(msg)) end @@ -258,11 +248,6 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, arch_or_grid = CPU(); # Record some important user decisions totally_in_memory = backend isa TotallyInMemory - on_native_grid = isnothing(grid) - !on_native_grid && backend isa JRA55NetCDFBackend && error("Can't use custom grid with JRA55NetCDFBackend.") - - jld2_filepath = joinpath(dir, string("JRA55_repeat_year_", variable_name, ".jld2")) - fts_name = field_time_series_short_names[variable_name] # Determine default time indices if totally_in_memory @@ -270,7 +255,6 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, arch_or_grid = CPU(); # Either the time series is short, or we are doing a limited-area # simulation, like in a single column. So, we conservatively # set a default `time_indices = 1:2`. - isnothing(time_indices) && (time_indices = 1:2) time_indices_in_memory = time_indices native_fts_architecture = architecture else @@ -281,16 +265,8 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, arch_or_grid = CPU(); # by default we choose all of them. The architecture is only the # architecture used for preprocessing, which typically will be CPU() # even if we would like the final FieldTimeSeries on the GPU. - isnothing(time_indices) && (time_indices = :) - - if backend isa JRA55NetCDFBackend - time_indices_in_memory = 1:length(backend) - native_fts_architecture = architecture - else # then `time_indices_in_memory` refers to preprocessing - maximum_index = min(preprocess_chunk_size, length(time_indices)) - time_indices_in_memory = 1:maximum_index - native_fts_architecture = preprocess_architecture - end + time_indices_in_memory = 1:length(backend) + native_fts_architecture = architecture end ds = Dataset(filepath) @@ -353,118 +329,15 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, arch_or_grid = CPU(); return fts else - # Make times into an array for later preprocessing - if !totally_in_memory - times = collect(times) - end - native_fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; time_indexing, + backend, boundary_conditions) # Fill the data in a GPU-friendly manner copyto!(interior(native_fts, :, :, 1, :), data) fill_halo_regions!(native_fts) - if on_native_grid && totally_in_memory - return native_fts - - elseif totally_in_memory # but not on the native grid! - boundary_conditions = FieldBoundaryConditions(grid, (LX, LY, Nothing)) - fts = FieldTimeSeries{LX, LY, Nothing}(grid, times; time_indexing, boundary_conditions) - interpolate!(fts, native_fts) - return fts - end - end - - @info "Pre-processing JRA55 $variable_name data into a JLD2 file..." - - preprocessing_grid = on_native_grid ? JRA55_native_grid : grid - - # Re-open the dataset! - ds = Dataset(filepath) - all_datetimes = ds["time"][time_indices] - all_Nt = length(all_datetimes) - - all_times = native_times(all_datetimes) - - on_disk_fts = FieldTimeSeries{LX, LY, Nothing}(preprocessing_grid, all_times; - boundary_conditions, - backend = OnDisk(), - path = jld2_filepath, - name = fts_name) - - # Save data to disk, one field at a time - start_clock = time_ns() - n = 1 # on disk - m = 0 # in memory - - times_in_memory = all_times[time_indices_in_memory] - - fts = FieldTimeSeries{LX, LY, Nothing}(preprocessing_grid, times_in_memory; - boundary_conditions, - backend = InMemory(), - path = jld2_filepath, - name = fts_name) - - # Re-compute data - new_data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] - - if !on_native_grid - copyto!(interior(native_fts, :, :, 1, :), new_data[:, :, :]) - fill_halo_regions!(native_fts) - interpolate!(fts, native_fts) - else - copyto!(interior(fts, :, :, 1, :), new_data[:, :, :]) + return native_fts end - - while n <= all_Nt - print(" ... processing time index $n of $all_Nt \r") - - if time_indices_in_memory isa Colon || n ∈ time_indices_in_memory - m += 1 - else # load new data - # Update time_indices - time_indices_in_memory = time_indices_in_memory .+ preprocess_chunk_size - n₁ = first(time_indices_in_memory) - - # Clip time_indices if they extend past the end of the dataset - if last(time_indices_in_memory) > all_Nt - time_indices_in_memory = UnitRange(n₁, all_Nt) - end - - # Re-compute times - new_times = native_times(all_times[time_indices_in_memory], all_times[n₁]) - native_fts.times = new_times - - # Re-compute data - new_data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] - fts.times = new_times - - if !on_native_grid - copyto!(interior(native_fts, :, :, 1, :), new_data[:, :, :]) - fill_halo_regions!(native_fts) - interpolate!(fts, native_fts) - else - copyto!(interior(fts, :, :, 1, :), new_data[:, :, :]) - end - - m = 1 # reset - end - - set!(on_disk_fts, fts[m], n, fts.times[m]) - - n += 1 - end - - elapsed = 1e-9 * (time_ns() - start_clock) - elapsed_str = prettytime(elapsed) - @info " ... done ($elapsed_str)" * repeat(" ", 20) - - close(ds) - - user_fts = FieldTimeSeries(jld2_filepath, fts_name; architecture, backend, time_indexing) - fill_halo_regions!(user_fts) - - return user_fts end From 91e1169e7b324f46ffce2c01361c13fc3e60a94c Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 12 Feb 2025 13:21:53 +0100 Subject: [PATCH 019/104] this part is already done --- src/DataWrangling/JRA55/jra55_field_time_series.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/DataWrangling/JRA55/jra55_field_time_series.jl b/src/DataWrangling/JRA55/jra55_field_time_series.jl index 006df82f..bb24bce6 100644 --- a/src/DataWrangling/JRA55/jra55_field_time_series.jl +++ b/src/DataWrangling/JRA55/jra55_field_time_series.jl @@ -292,8 +292,6 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture = CPU(); push!(φn, 90) push!(λn, λn[1] + 360) - # TODO: support loading just part of the JRA55 data. - # Probably with arguments that take latitude, longitude bounds. i₁, i₂, j₁, j₂, TX = compute_bounding_indices(longitude, latitude, grid, Center, Center, λc, φc) data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] From b6fb198fa90803d2c8a3775a777f8ab0f5c18d43 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 12 Feb 2025 14:20:10 +0100 Subject: [PATCH 020/104] back to working --- .../JRA55/JRA55_prescribed_atmosphere.jl | 60 +++++++------------ .../JRA55/jra55_field_time_series.jl | 2 +- .../PrescribedAtmospheres.jl | 18 +++--- 3 files changed, 31 insertions(+), 49 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl index e8643e29..0c16ce5b 100644 --- a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl +++ b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl @@ -1,57 +1,42 @@ - const AA = Oceananigans.Architectures.AbstractArchitecture -JRA55PrescribedAtmosphere(time_indices=Colon(); kw...) = - JRA55PrescribedAtmosphere(CPU(), time_indices; kw...) - -JRA55PrescribedAtmosphere(arch::Distributed, time_indices=Colon(); kw...) = - JRA55PrescribedAtmosphere(child_architecture(arch), time_indices; kw...) +JRA55PrescribedAtmosphere(arch::Distributed; kw...) = + JRA55PrescribedAtmosphere(child_architecture(arch); kw...) # TODO: allow the user to pass dates """ - JRA55PrescribedAtmosphere(architecture::AA, time_indices=Colon(); + JRA55PrescribedAtmosphere(architecture::AA; + version = JRA55RepeatYear(), + dates = all_dates(version), backend = nothing, time_indexing = Cyclical(), - dates = all_dates(metadata), reference_height = 10, # meters include_rivers_and_icebergs = false, other_kw...) Return a `PrescribedAtmosphere` representing JRA55 reanalysis data. """ -function JRA55PrescribedAtmosphere(metadata::JRA55Metadata, arch_or_grid = CPU(); - backend = nothing, +function JRA55PrescribedAtmosphere(architecture; + version = JRA55RepeatYear(), + dates = all_dates(version), + backend = JRA55NetCDFBackend(10), time_indexing = Cyclical(), reference_height = 10, # meters include_rivers_and_icebergs = false, other_kw...) - time_indices = JRA55_time_indices(metadata.version, metadata.dates) - - if isnothing(backend) # apply a default - Ni = try - length(time_indices) - catch - Inf - end - - # Manufacture a default for the number of fields to keep InMemory - Nf = min(24, Ni) - backend = JRA55NetCDFBackend(Nf) - end - - kw = (; time_indices, time_indexing, backend, architecture) + kw = (; time_indexing, backend, dates, version) kw = merge(kw, other_kw) - ua = JRA55FieldTimeSeries(:eastward_velocity; kw...) - va = JRA55FieldTimeSeries(:northward_velocity; kw...) - Ta = JRA55FieldTimeSeries(:temperature; kw...) - qa = JRA55FieldTimeSeries(:specific_humidity; kw...) - pa = JRA55FieldTimeSeries(:sea_level_pressure; kw...) - Fra = JRA55FieldTimeSeries(:rain_freshwater_flux; kw...) - Fsn = JRA55FieldTimeSeries(:snow_freshwater_flux; kw...) - Ql = JRA55FieldTimeSeries(:downwelling_longwave_radiation; kw...) - Qs = JRA55FieldTimeSeries(:downwelling_shortwave_radiation; kw...) + ua = JRA55FieldTimeSeries(:eastward_velocity, architecture; kw...) + va = JRA55FieldTimeSeries(:northward_velocity, architecture; kw...) + Ta = JRA55FieldTimeSeries(:temperature, architecture; kw...) + qa = JRA55FieldTimeSeries(:specific_humidity, architecture; kw...) + pa = JRA55FieldTimeSeries(:sea_level_pressure, architecture; kw...) + Fra = JRA55FieldTimeSeries(:rain_freshwater_flux, architecture; kw...) + Fsn = JRA55FieldTimeSeries(:snow_freshwater_flux, architecture; kw...) + Ql = JRA55FieldTimeSeries(:downwelling_longwave_radiation, architecture; kw...) + Qs = JRA55FieldTimeSeries(:downwelling_shortwave_radiation, architecture; kw...) freshwater_flux = (rain = Fra, snow = Fsn) @@ -60,14 +45,15 @@ function JRA55PrescribedAtmosphere(metadata::JRA55Metadata, arch_or_grid = CPU() # a different frequency than the rest of the JRA55 data. We use `PrescribedAtmospheres` # "auxiliary_freshwater_flux" feature to represent them. if include_rivers_and_icebergs - Fri = JRA55FieldTimeSeries(:river_freshwater_flux; kw...) - Fic = JRA55FieldTimeSeries(:iceberg_freshwater_flux; kw...) + Fri = JRA55FieldTimeSeries(:river_freshwater_flux, architecture; kw...) + Fic = JRA55FieldTimeSeries(:iceberg_freshwater_flux, architecture; kw...) auxiliary_freshwater_flux = (rivers = Fri, icebergs = Fic) else auxiliary_freshwater_flux = nothing end times = ua.times + grid = ua.grid velocities = (u = ua, v = va) @@ -82,7 +68,7 @@ function JRA55PrescribedAtmosphere(metadata::JRA55Metadata, arch_or_grid = CPU() FT = eltype(ua) reference_height = convert(FT, reference_height) - atmosphere = PrescribedAtmosphere(times, FT; + atmosphere = PrescribedAtmosphere(grid, times; velocities, freshwater_flux, auxiliary_freshwater_flux, diff --git a/src/DataWrangling/JRA55/jra55_field_time_series.jl b/src/DataWrangling/JRA55/jra55_field_time_series.jl index bb24bce6..04876a79 100644 --- a/src/DataWrangling/JRA55/jra55_field_time_series.jl +++ b/src/DataWrangling/JRA55/jra55_field_time_series.jl @@ -292,7 +292,7 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture = CPU(); push!(φn, 90) push!(λn, λn[1] + 360) - i₁, i₂, j₁, j₂, TX = compute_bounding_indices(longitude, latitude, grid, Center, Center, λc, φc) + i₁, i₂, j₁, j₂, TX = compute_bounding_indices(longitude, latitude, nothing, Center, Center, λc, φc) data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] λr = λn[i₁:i₂+1] diff --git a/src/OceanSeaIceModels/PrescribedAtmospheres.jl b/src/OceanSeaIceModels/PrescribedAtmospheres.jl index 79a8fafd..e485da20 100644 --- a/src/OceanSeaIceModels/PrescribedAtmospheres.jl +++ b/src/OceanSeaIceModels/PrescribedAtmospheres.jl @@ -287,9 +287,8 @@ const PATP = PrescribedAtmosphereThermodynamicsParameters ##### Prescribed atmosphere (as opposed to dynamically evolving / prognostic) ##### -struct PrescribedAtmosphere{FT, M, G, U, P, C, F, I, R, TP, TI} +struct PrescribedAtmosphere{FT, G, U, P, C, F, I, R, TP, TI} grid :: G - metadata :: M velocities :: U pressure :: P tracers :: C @@ -349,22 +348,20 @@ end """ PrescribedAtmosphere(grid, times; - metadata = nothing, reference_height = 10, # meters boundary_layer_height = 600 # meters, thermodynamics_parameters = PrescribedAtmosphereThermodynamicsParameters(FT), - auxiliary_freshwater_flux = nothing, - velocities = default_atmosphere_velocities(grid, times), - tracers = default_atmosphere_tracers(grid, times), - pressure = default_atmosphere_pressure(grid, times), - freshwater_flux = default_freshwater_flux(grid, times), - downwelling_radiation = default_downwelling_radiation(grid, times)) + auxiliary_freshwater_flux = nothing, + velocities = default_atmosphere_velocities(grid, times), + tracers = default_atmosphere_tracers(grid, times), + pressure = default_atmosphere_pressure(grid, times), + freshwater_flux = default_freshwater_flux(grid, times), + downwelling_radiation = default_downwelling_radiation(grid, times)) Return a representation of a prescribed time-evolving atmospheric state with data given at `times`. """ function PrescribedAtmosphere(grid, times; - metadata = nothing, reference_height = convert(eltype(grid), 10), boundary_layer_height = convert(eltype(grid), 600), thermodynamics_parameters = nothing, @@ -381,7 +378,6 @@ function PrescribedAtmosphere(grid, times; end return PrescribedAtmosphere(grid, - metadata, velocities, pressure, tracers, From e81bdfb3ab3f84c650583b8685b99361438ba0b9 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 12 Feb 2025 14:23:40 +0100 Subject: [PATCH 021/104] works --- src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl index 0c16ce5b..1fe097cc 100644 --- a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl +++ b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl @@ -3,7 +3,6 @@ const AA = Oceananigans.Architectures.AbstractArchitecture JRA55PrescribedAtmosphere(arch::Distributed; kw...) = JRA55PrescribedAtmosphere(child_architecture(arch); kw...) -# TODO: allow the user to pass dates """ JRA55PrescribedAtmosphere(architecture::AA; version = JRA55RepeatYear(), From afbd1ccadb9e64e8e2496995258dc4d7d21b3299 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 12 Feb 2025 14:26:38 +0100 Subject: [PATCH 022/104] improve show method --- src/DataWrangling/ECCO/ECCO_metadata.jl | 7 ------- src/DataWrangling/metadata.jl | 10 +++++----- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/DataWrangling/ECCO/ECCO_metadata.jl b/src/DataWrangling/ECCO/ECCO_metadata.jl index 11e73fe0..0899d027 100644 --- a/src/DataWrangling/ECCO/ECCO_metadata.jl +++ b/src/DataWrangling/ECCO/ECCO_metadata.jl @@ -16,13 +16,6 @@ struct ECCO4Monthly end const ECCOMetadata{D, V} = Metadata{D, V} where {D, V<:Union{<:ECCO2Monthly, <:ECCO2Daily, <:ECCO4Monthly}} -Base.show(io::IO, metadata::ECCOMetadata) = - print(io, "ECCOMetadata:", '\n', - "├── name: $(metadata.name)", '\n', - "├── dates: $(metadata.dates)", '\n', - "├── version: $(metadata.version)", '\n', - "└── dir: $(metadata.dir)") - Base.summary(md::ECCOMetadata{<:Any, <:ECCO2Daily}) = "ECCO2Daily $(md.name) metadata ($(first(md.dates))--$(last(md.dates)))" Base.summary(md::ECCOMetadata{<:Any, <:ECCO2Monthly}) = "ECCO2Monthly $(md.name) metadata ($(first(md.dates))--$(last(md.dates)))" Base.summary(md::ECCOMetadata{<:Any, <:ECCO4Monthly}) = "ECCO4Monthly $(md.name) metadata ($(first(md.dates))--$(last(md.dates)))" diff --git a/src/DataWrangling/metadata.jl b/src/DataWrangling/metadata.jl index 79423463..5241eeed 100644 --- a/src/DataWrangling/metadata.jl +++ b/src/DataWrangling/metadata.jl @@ -18,12 +18,12 @@ struct Metadata{D, V} dir :: String end -Base.show(io::IO, metadata::Metadata) = - print(io, "Metadata:", '\n', - "├── field: $(metadata.name)", '\n', +Base.show(io::IO, metadata::EMetadata) = + print(io, "ECCOMetadata:", '\n', + "├── name: $(metadata.name)", '\n', "├── dates: $(metadata.dates)", '\n', - "└── data version: $(metadata.version)") - + "├── version: $(metadata.version)", '\n', + "└── data directory: $(metadata.dir)") # Treat Metadata as an array to allow iteration over the dates. Base.length(metadata::Metadata) = length(metadata.dates) From 8599314412c5e5cb5ce4e0ff2a7d43bb1d7b6945 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 12 Feb 2025 14:28:22 +0100 Subject: [PATCH 023/104] change names --- .../{jra55_field_time_series.jl => JRA55_field_time_series.jl} | 0 src/DataWrangling/JRA55/{jra55_metadata.jl => JRA55_metadata.jl} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/DataWrangling/JRA55/{jra55_field_time_series.jl => JRA55_field_time_series.jl} (100%) rename src/DataWrangling/JRA55/{jra55_metadata.jl => JRA55_metadata.jl} (100%) diff --git a/src/DataWrangling/JRA55/jra55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl similarity index 100% rename from src/DataWrangling/JRA55/jra55_field_time_series.jl rename to src/DataWrangling/JRA55/JRA55_field_time_series.jl diff --git a/src/DataWrangling/JRA55/jra55_metadata.jl b/src/DataWrangling/JRA55/JRA55_metadata.jl similarity index 100% rename from src/DataWrangling/JRA55/jra55_metadata.jl rename to src/DataWrangling/JRA55/JRA55_metadata.jl From bb246333003dff9ffe181ee3eb1ffe9f7fe0d176 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 12 Feb 2025 14:29:39 +0100 Subject: [PATCH 024/104] go ahead --- src/DataWrangling/JRA55/JRA55_metadata.jl | 34 ++++++----------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_metadata.jl b/src/DataWrangling/JRA55/JRA55_metadata.jl index 977afb50..659cbe48 100644 --- a/src/DataWrangling/JRA55/JRA55_metadata.jl +++ b/src/DataWrangling/JRA55/JRA55_metadata.jl @@ -43,19 +43,18 @@ function metadata_filename(metadata::Metadata{<:Any, <:JRA55RepeatYear}) # No di return "RYF." * shortname * ".1990_1991.nc" end -# TODO: Implement this function for JRA55MultipleYears -# function metadata_filename(metadata::Metadata{<:AbstractCFDateTime, <:JRA55MultipleYears}) -# # fix the filename -# shortname = short_name(metadata) -# year = Dates.year(metadata.dates) -# suffix = "_input4MIPs_atmosphericState_OMIP_MRI-JRA55-do-1-5-0_gr_" -# dates = "($year)01010130-($year)12312330.nc" -# return shortname * suffix * dates * ".nc" -# end +function metadata_filename(metadata::Metadata{<:AbstractCFDateTime, <:JRA55MultipleYears}) + # fix the filename + shortname = short_name(metadata) + year = Dates.year(metadata.dates) + suffix = "_input4MIPs_atmosphericState_OMIP_MRI-JRA55-do-1-5-0_gr_" + dates = "($year)01010130-($year)12312330.nc" + return shortname * suffix * dates * ".nc" +end # Convenience functions short_name(data::JRA55Metadata) = JRA55_short_names[data.name] -location(data::JRA55Metadata) = (Center, Center, Center) +location(::JRA55Metadata) = (Center, Center, Center) # A list of all variables provided in the JRA55 dataset: JRA55_variable_names = (:river_freshwater_flux, @@ -86,21 +85,6 @@ JRA55_short_names = Dict( :northward_velocity => "vas", # Northward near-surface wind ) -field_time_series_short_names = Dict( - :river_freshwater_flux => "Fri", # Freshwater fluxes from rivers - :rain_freshwater_flux => "Fra", # Freshwater flux from rainfall - :snow_freshwater_flux => "Fsn", # Freshwater flux from snowfall - :iceberg_freshwater_flux => "Fic", # Freshwater flux from calving icebergs - :specific_humidity => "qa", # Surface specific humidity - :sea_level_pressure => "pa", # Sea level pressure - :relative_humidity => "rh", # Surface relative humidity - :downwelling_longwave_radiation => "Ql", # Downwelling longwave radiation - :downwelling_shortwave_radiation => "Qs", # Downwelling shortwave radiation - :temperature => "Ta", # Near-surface air temperature - :eastward_velocity => "ua", # Eastward near-surface wind - :northward_velocity => "va", # Northward near-surface wind -) - JRA55_repeat_year_urls = Dict( :shortwave_radiation => "https://www.dropbox.com/scl/fi/z6fkvmd9oe3ycmaxta131/" * "RYF.rsds.1990_1991.nc?rlkey=r7q6zcbj6a4fxsq0f8th7c4tc&dl=0", From 4f05dbbdfbc804ea39350e236d4c271d6ac8ef05 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 12 Feb 2025 14:34:40 +0100 Subject: [PATCH 025/104] fix it --- src/DataWrangling/JRA55/JRA55_metadata.jl | 6 ++++-- src/DataWrangling/metadata.jl | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_metadata.jl b/src/DataWrangling/JRA55/JRA55_metadata.jl index 659cbe48..199ef59c 100644 --- a/src/DataWrangling/JRA55/JRA55_metadata.jl +++ b/src/DataWrangling/JRA55/JRA55_metadata.jl @@ -129,8 +129,10 @@ JRA55_repeat_year_urls = Dict( variable_is_three_dimensional(data::JRA55Metadata) = false urls(metadata::Metadata{<:Any, <:JRA55RepeatYear}) = JRA55_repeat_year_urls[metadata.name] -# TODO: -# urls(metadata::Metadata{<:Any, <:JRA55MultipleYears}) = ... +function urls(metadata::Metadata{<:Any, <:JRA55MultipleYears}) + shotname = short_name(metadata) + return "https://esgf-data2.llnl.gov/thredds/fileServer/user_pub_work/input4MIPs/CMIP6/OMIP/MRI/MRI-JRA55-do-1-5-0/atmos/$(shortname)/prra/gr/v20200916/" +end metadata_url(prefix, m::Metadata{<:Any, <:JRA55RepeatYear}) = prefix # No specific name for this url diff --git a/src/DataWrangling/metadata.jl b/src/DataWrangling/metadata.jl index 5241eeed..e61dec10 100644 --- a/src/DataWrangling/metadata.jl +++ b/src/DataWrangling/metadata.jl @@ -18,7 +18,7 @@ struct Metadata{D, V} dir :: String end -Base.show(io::IO, metadata::EMetadata) = +Base.show(io::IO, metadata::Metadata) = print(io, "ECCOMetadata:", '\n', "├── name: $(metadata.name)", '\n', "├── dates: $(metadata.dates)", '\n', From fe6dc72f724896cf7f244d73eb028d20f1e70b29 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 12 Feb 2025 15:14:54 +0100 Subject: [PATCH 026/104] this should work --- .../JRA55/JRA55_field_time_series.jl | 92 ++++++++++--------- src/DataWrangling/JRA55/JRA55_metadata.jl | 17 ++-- 2 files changed, 61 insertions(+), 48 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index 04876a79..b1e0db4a 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -73,17 +73,20 @@ function compute_bounding_indices(longitude, latitude, grid, LX, LY, λc, φc) return i₁, i₂, j₁, j₂, TX end -struct JRA55NetCDFBackend <: AbstractInMemoryBackend{Int} +struct JRA55NetCDFBackend{M} <: AbstractInMemoryBackend{Int} start :: Int length :: Int + metadata :: M end +Adapt.adapt_structure(to, b::JRA55NetCDFBackend) = JRA55NetCDFBackend(b.start, b.length, nothing) + """ JRA55NetCDFBackend(length) Represents a JRA55 FieldTimeSeries backed by JRA55 native .nc files. """ -JRA55NetCDFBackend(length) = JRA55NetCDFBackend(1, length) +JRA55NetCDFBackend(length, metadata) = JRA55NetCDFBackend(1, length, metadata) Base.length(backend::JRA55NetCDFBackend) = backend.length Base.summary(backend::JRA55NetCDFBackend) = string("JRA55NetCDFBackend(", backend.start, ", ", backend.length, ")") @@ -91,52 +94,59 @@ Base.summary(backend::JRA55NetCDFBackend) = string("JRA55NetCDFBackend(", backen const JRA55NetCDFFTS = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend} # TODO: This will need to change when we add a method for JRA55MultipleYears -function set!(fts::JRA55NetCDFFTS, path::String=fts.path, name::String=fts.name) +function set!(fts::JRA55NetCDFFTS) - ds = Dataset(path) + backend = fts.backend + metadata = backend.metadata - # Note that each file should have the variables - # - ds["time"]: time coordinate - # - ds["lon"]: longitude at the location of the variable - # - ds["lat"]: latitude at the location of the variable - # - ds["lon_bnds"]: bounding longitudes between which variables are averaged - # - ds["lat_bnds"]: bounding latitudes between which variables are averaged - # - ds[shortname]: the variable data - - # Nodes at the variable location - λc = ds["lon"][:] - φc = ds["lat"][:] - LX, LY, LZ = location(fts) - i₁, i₂, j₁, j₂, TX = compute_bounding_indices(nothing, nothing, fts.grid, LX, LY, λc, φc) - - nn = time_indices(fts) - nn = collect(nn) + filepath = metadata_path(metadata) + + for path in filepath + ds = Dataset(path) + + # Note that each file should have the variables + # - ds["time"]: time coordinate + # - ds["lon"]: longitude at the location of the variable + # - ds["lat"]: latitude at the location of the variable + # - ds["lon_bnds"]: bounding longitudes between which variables are averaged + # - ds["lat_bnds"]: bounding latitudes between which variables are averaged + # - ds[shortname]: the variable data + + # Nodes at the variable location + λc = ds["lon"][:] + φc = ds["lat"][:] + LX, LY, LZ = location(fts) + i₁, i₂, j₁, j₂, TX = compute_bounding_indices(nothing, nothing, fts.grid, LX, LY, λc, φc) + + nn = time_indices(fts) + nn = collect(nn) + + if issorted(nn) + data = ds[name][i₁:i₂, j₁:j₂, nn] + else + # The time indices may be cycling past 1; eg ti = [6, 7, 8, 1]. + # However, DiskArrays does not seem to support loading data with unsorted + # indices. So to handle this, we load the data in chunks, where each chunk's + # indices are sorted, and then glue the data together. + m = findfirst(n -> n == 1, nn) + n1 = nn[1:m-1] + n2 = nn[m:end] + + data1 = ds[name][i₁:i₂, j₁:j₂, n1] + data2 = ds[name][i₁:i₂, j₁:j₂, n2] + data = cat(data1, data2, dims=3) + end + + close(ds) - if issorted(nn) - data = ds[name][i₁:i₂, j₁:j₂, nn] - else - # The time indices may be cycling past 1; eg ti = [6, 7, 8, 1]. - # However, DiskArrays does not seem to support loading data with unsorted - # indices. So to handle this, we load the data in chunks, where each chunk's - # indices are sorted, and then glue the data together. - m = findfirst(n -> n == 1, nn) - n1 = nn[1:m-1] - n2 = nn[m:end] - - data1 = ds[name][i₁:i₂, j₁:j₂, n1] - data2 = ds[name][i₁:i₂, j₁:j₂, n2] - data = cat(data1, data2, dims=3) + copyto!(interior(fts, :, :, 1, :), data) + fill_halo_regions!(fts) end - - close(ds) - - copyto!(interior(fts, :, :, 1, :), data) - fill_halo_regions!(fts) - + return nothing end -new_backend(::JRA55NetCDFBackend, start, length) = JRA55NetCDFBackend(start, length) +new_backend(b::JRA55NetCDFBackend, start, length) = JRA55NetCDFBackend(start, length, b.metadata) """ JRA55FieldTimeSeries(variable_name [, arch_or_grid=CPU() ]; diff --git a/src/DataWrangling/JRA55/JRA55_metadata.jl b/src/DataWrangling/JRA55/JRA55_metadata.jl index 199ef59c..f88bf660 100644 --- a/src/DataWrangling/JRA55/JRA55_metadata.jl +++ b/src/DataWrangling/JRA55/JRA55_metadata.jl @@ -48,7 +48,12 @@ function metadata_filename(metadata::Metadata{<:AbstractCFDateTime, <:JRA55Multi shortname = short_name(metadata) year = Dates.year(metadata.dates) suffix = "_input4MIPs_atmosphericState_OMIP_MRI-JRA55-do-1-5-0_gr_" - dates = "($year)01010130-($year)12312330.nc" + + if shortname == "tas" + dates = "$(year)01010000-$(year)12312100" + else + dates = "$(year)01010130-$(year)12312330" + end return shortname * suffix * dates * ".nc" end @@ -129,17 +134,15 @@ JRA55_repeat_year_urls = Dict( variable_is_three_dimensional(data::JRA55Metadata) = false urls(metadata::Metadata{<:Any, <:JRA55RepeatYear}) = JRA55_repeat_year_urls[metadata.name] -function urls(metadata::Metadata{<:Any, <:JRA55MultipleYears}) - shotname = short_name(metadata) - return "https://esgf-data2.llnl.gov/thredds/fileServer/user_pub_work/input4MIPs/CMIP6/OMIP/MRI/MRI-JRA55-do-1-5-0/atmos/$(shortname)/prra/gr/v20200916/" -end +urls(metadata::Metadata{<:Any, <:JRA55MultipleYears}) = "https://esgf-data2.llnl.gov/thredds/fileServer/user_pub_work/input4MIPs/CMIP6/OMIP/MRI/MRI-JRA55-do-1-5-0/atmos/3hrPt" -metadata_url(prefix, m::Metadata{<:Any, <:JRA55RepeatYear}) = prefix # No specific name for this url +metadata_url(prefix, m::Metadata{<:Any, <:JRA55RepeatYear}) = prefix # No specific name for this url +metadata_url(prefix, m::Metadata{<:Any, <:JRA55MultipleYears}) = prefix * "/" * short_name(m) * "/gr/v20200916/" * metadata_filename(m) # TODO: This will need to change when we add a method for JRA55MultipleYears function download_dataset!(metadata::JRA55Metadata; url = urls(metadata)) - asyncmap(metadata, ntasks=10) do metadatum # Distribute the download among tasks + for metadatum in metadata # Distribute the download among tasks fileurl = metadata_url(url, metadatum) filepath = metadata_path(metadatum) From 787ebc91ba47150c312d9ee6c3bcf37709de9cdd Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 11 Mar 2025 11:18:17 +0100 Subject: [PATCH 027/104] add a new constructor --- src/DataWrangling/metadata.jl | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/DataWrangling/metadata.jl b/src/DataWrangling/metadata.jl index 546b4140..9f4e10a3 100644 --- a/src/DataWrangling/metadata.jl +++ b/src/DataWrangling/metadata.jl @@ -31,7 +31,7 @@ Keyword Arguments """ function Metadata(variable_name; dataset, - dates=all_dates(dataset, variable_name)[1], + dates=all_dates(dataset, variable_name)[1:1], dir=default_download_folder(dataset)) return Metadata(variable_name, dates, dataset, dir) @@ -40,6 +40,15 @@ end const AnyDateTime = Union{AbstractCFDateTime, Dates.AbstractDateTime} const Metadatum = Metadata{<:AnyDateTime} +# A constructor for a single date +function Metadatum(variable_name; + dataset, + date=first_date(dataset, variable_name), + dir=default_download_folder(dataset)) + + return Metadata(variable_name, date, dataset, dir) +end + default_download_folder(dataset) = "./" Base.show(io::IO, metadata::Metadata) = @@ -104,6 +113,10 @@ function native_times(metadata; start_time=first(metadata).dates) return times end +#### +#### Some utilities +#### + """ all_dates(metadata) @@ -112,7 +125,25 @@ Needs to be extended by any new dataset dataset. """ all_dates(metadata) = all_dates(metadata.dataset, metadata.name) +""" + first_date(dataset, variable_name) + +Extracts the first date of the given dataset and variable name formatted using the `DateTime` type. +""" +first_date(dataset, variable_name) = first(all_dates(dataset, variable_name)) + +""" + end_date(dataset, variable_name) + +Extracts the last date of the given dataset and variable name formatted using the `DateTime` type. +""" +end_date(dataset, variable_name) = last(all_dates(dataset, variable_name)) + +""" + metadata_filename(metadata) + # File names of metadata containing multiple dates +""" metadata_filename(metadata) = [metadata_filename(metadatum) for metadatum in metadata] """ From 7c519a1ac328b06900719e3eeb67e5322fc7f36f Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 11 Mar 2025 11:19:48 +0100 Subject: [PATCH 028/104] set with ECCOMetadatum --- src/DataWrangling/ECCO/ECCO.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataWrangling/ECCO/ECCO.jl b/src/DataWrangling/ECCO/ECCO.jl index ec625bcc..0d51c547 100644 --- a/src/DataWrangling/ECCO/ECCO.jl +++ b/src/DataWrangling/ECCO/ECCO.jl @@ -256,7 +256,7 @@ end inpainted_metadata_path(metadata::ECCOMetadata) = joinpath(metadata.dir, inpainted_metadata_filename(metadata)) -function set!(field::Field, ECCO_metadata::ECCOMetadata; kw...) +function set!(field::Field, ECCO_metadata::ECCOMetadatum; kw...) # Fields initialized from ECCO grid = field.grid From 78a53cd2e0ac2f230f84aaab19a8926d6f21b765 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 11 Mar 2025 11:20:54 +0100 Subject: [PATCH 029/104] some changes --- src/DataWrangling/ECCO/ECCO.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataWrangling/ECCO/ECCO.jl b/src/DataWrangling/ECCO/ECCO.jl index 0d51c547..141cb246 100644 --- a/src/DataWrangling/ECCO/ECCO.jl +++ b/src/DataWrangling/ECCO/ECCO.jl @@ -88,7 +88,7 @@ const ECCO_z = [ 0.0, ] -empty_ECCO_field(variable_name::Symbol; kw...) = empty_ECCO_field(ECCOMetadata(variable_name); kw...) +empty_ECCO_field(variable_name::Symbol; kw...) = empty_ECCO_field(Metadatum(variable_name, dataset=ECCO4Monthly()); kw...) function empty_ECCO_field(metadata::ECCOMetadata; architecture = CPU(), From 1e71d6719dfb3760aceff8fab3065cca98abb960 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 11 Mar 2025 11:30:01 +0100 Subject: [PATCH 030/104] improvement to UI --- src/DataWrangling/DataWrangling.jl | 2 +- src/DataWrangling/ECCO/ECCO_restoring.jl | 15 ++++++-- .../JRA55/JRA55_field_time_series.jl | 12 ++++--- .../JRA55/JRA55_prescribed_atmosphere.jl | 36 ++++++++----------- src/DataWrangling/metadata.jl | 16 ++++++--- 5 files changed, 48 insertions(+), 33 deletions(-) diff --git a/src/DataWrangling/DataWrangling.jl b/src/DataWrangling/DataWrangling.jl index 3514c673..71b43ec0 100644 --- a/src/DataWrangling/DataWrangling.jl +++ b/src/DataWrangling/DataWrangling.jl @@ -1,6 +1,6 @@ module DataWrangling -export Metadata, all_dates +export Metadata, all_dates, first_date, end_date using Oceananigans using Downloads diff --git a/src/DataWrangling/ECCO/ECCO_restoring.jl b/src/DataWrangling/ECCO/ECCO_restoring.jl index 23483d03..ea20fe5f 100644 --- a/src/DataWrangling/ECCO/ECCO_restoring.jl +++ b/src/DataWrangling/ECCO/ECCO_restoring.jl @@ -141,10 +141,13 @@ end function ECCOFieldTimeSeries(variable_name::Symbol; dataset = ECCO4Monthly(), architecture = CPU(), - dates = all_dates(dataset, variable_name), + start_date = first_date(dataset, variable_name), + end_date = first_date(dataset, variable_name), dir = download_ECCO_cache, kw...) + native_dates = all_dates(dataset, variable_name) + dates = compute_native_date_range(native_dates, start_date, end_date) metadata = Metadata(variable_name, dates, dataset, dir) return ECCOFieldTimeSeries(metadata, architecture; kw...) end @@ -266,7 +269,9 @@ Keyword Arguments - `dataset`: The dataset of the ECCO dataset. Default: `ECCO4Monthly()`. -- `dates`: The dates to use for the ECCO dataset. Default: `all_dates(dataset, variable_name)`. +- `start_date`: The starting date to use for the ECCO dataset. Default: `first_date(dataset, variable_name)`. + +- `end_date`: The ending date to use for the ECCO dataset. Default: `end_date(dataset, variable_name)`. - `time_indices_in_memory`: The number of time indices to keep in memory. The number is chosen based on a trade-off between increased performance (more indices in memory) and reduced @@ -289,11 +294,15 @@ Keyword Arguments function ECCORestoring(variable_name::Symbol, arch_or_grid = CPU(); dataset = ECCO4Monthly(), - dates = all_dates(dataset, variable_name), + start_date = first_date(dataset, variable_name), + end_date = last_date(dataset, variable_name), dir = download_ECCO_cache, kw...) + native_dates = all_dates(dataset, variable_name) + dates = compute_native_date_range(native_dates, start_date, end_date) metadata = Metadata(variable_name, dates, dataset, dir) + return ECCORestoring(metadata, arch_or_grid; kw...) end diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index 73386836..62eed3f4 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -180,9 +180,9 @@ Keyword arguments - `architecture`: Architecture for the `FieldTimeSeries`. Default: CPU() -- `dates`: The date(s) of the metadata. Note this can either be a single date, - representing a snapshot, or a range of dates, representing a time-series. - Default: `all_dates(dataset, name)` (see `all_dates`). +- `start_date`: The starting date to use for the ECCO dataset. Default: `first_date(dataset, variable_name)`. + +- `end_date`: The ending date to use for the ECCO dataset. Default: `end_date(dataset, variable_name)`. - `dataset`: The data dataset. The only supported datasets is `JRA55RepeatYear()` @@ -203,10 +203,14 @@ Keyword arguments """ function JRA55FieldTimeSeries(variable_name::Symbol, architecture = CPU(), FT=Float32; dataset = JRA55RepeatYear(), - dates = all_dates(dataset, variable_name), + start_date = first_date(dataset, variable_name), + end_date = last_date(dataset, variable_name), dir = download_JRA55_cache, kw...) + native_dates = all_dates(dataset, variable_name) + dates = compute_native_date_range(native_dates, start_date, end_date) + metadata = Metadata(variable_name, dates, dataset, dir) return JRA55FieldTimeSeries(metadata, architecture, FT; kw...) diff --git a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl index 9ea6b4d9..97ca8db3 100644 --- a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl +++ b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl @@ -17,29 +17,26 @@ Return a `PrescribedAtmosphere` representing JRA55 reanalysis data. """ function JRA55PrescribedAtmosphere(architecture = CPU(), FT = Float32; dataset = JRA55RepeatYear(), - start_date = first(all_dates(dataset, :temperature)), - end_date = last(all_dates(dataset, :temperature)), + start_date = first_date(dataset, :temperature), + end_date = last_date(dataset, :temperature), backend = JRA55NetCDFBackend(10), time_indexing = Cyclical(), surface_layer_height = 10, # meters include_rivers_and_icebergs = false, other_kw...) - native_dates = all_dates(dataset, :temperature) - dates = compute_native_date_range(native_dates, start_date, end_date) - - kw = (; time_indexing, dates, backend, dataset) + kw = (; time_indexing, dates, backend, start_date, end_date, dataset) kw = merge(kw, other_kw) - ua = JRA55FieldTimeSeries(:eastward_velocity, architecture, FT; dates, kw...) - va = JRA55FieldTimeSeries(:northward_velocity, architecture, FT; dates, kw...) - Ta = JRA55FieldTimeSeries(:temperature, architecture, FT; dates, kw...) - qa = JRA55FieldTimeSeries(:specific_humidity, architecture, FT; dates, kw...) - pa = JRA55FieldTimeSeries(:sea_level_pressure, architecture, FT; dates, kw...) - Fra = JRA55FieldTimeSeries(:rain_freshwater_flux, architecture, FT; dates, kw...) - Fsn = JRA55FieldTimeSeries(:snow_freshwater_flux, architecture, FT; dates, kw...) - Ql = JRA55FieldTimeSeries(:downwelling_longwave_radiation, architecture, FT; dates, kw...) - Qs = JRA55FieldTimeSeries(:downwelling_shortwave_radiation, architecture, FT; dates, kw...) + ua = JRA55FieldTimeSeries(:eastward_velocity, architecture, FT; kw...) + va = JRA55FieldTimeSeries(:northward_velocity, architecture, FT; kw...) + Ta = JRA55FieldTimeSeries(:temperature, architecture, FT; kw...) + qa = JRA55FieldTimeSeries(:specific_humidity, architecture, FT; kw...) + pa = JRA55FieldTimeSeries(:sea_level_pressure, architecture, FT; kw...) + Fra = JRA55FieldTimeSeries(:rain_freshwater_flux, architecture, FT; kw...) + Fsn = JRA55FieldTimeSeries(:snow_freshwater_flux, architecture, FT; kw...) + Ql = JRA55FieldTimeSeries(:downwelling_longwave_radiation, architecture, FT; kw...) + Qs = JRA55FieldTimeSeries(:downwelling_shortwave_radiation, architecture, FT; kw...) freshwater_flux = (rain = Fra, snow = Fsn) @@ -47,12 +44,9 @@ function JRA55PrescribedAtmosphere(architecture = CPU(), FT = Float32; # Remember that rivers and icebergs are on a different grid and have # a different frequency than the rest of the JRA55 data. We use `PrescribedAtmospheres` # "auxiliary_freshwater_flux" feature to represent them. - if include_rivers_and_icebergs - native_dates = all_dates(dataset, :river_freshwater_flux) - dates = compute_native_date_range(native_dates, start_date, end_date) - - Fri = JRA55FieldTimeSeries(:river_freshwater_flux, architecture; dates, kw...) - Fic = JRA55FieldTimeSeries(:iceberg_freshwater_flux, architecture; dates, kw...) + if include_rivers_and_icebergs + Fri = JRA55FieldTimeSeries(:river_freshwater_flux, architecture; kw...) + Fic = JRA55FieldTimeSeries(:iceberg_freshwater_flux, architecture; kw...) auxiliary_freshwater_flux = (rivers = Fri, icebergs = Fic) else auxiliary_freshwater_flux = nothing diff --git a/src/DataWrangling/metadata.jl b/src/DataWrangling/metadata.jl index 9f4e10a3..9380b94b 100644 --- a/src/DataWrangling/metadata.jl +++ b/src/DataWrangling/metadata.jl @@ -23,8 +23,8 @@ Arguments Keyword Arguments ================= -- `dates`: The dates of the dataset, in a `AbstractCFDateTime` format.. Note this can either be a single date, - representing a snapshot, or a range of dates, representing a time-series. +- `dates`: The dates of the dataset, in a `AbstractCFDateTime` format.. Note this can either be a range or a vector of dates, + representing a time-series. For a single date, use the [`Metadatum`](@ref) constructor . - `dataset`: The dataset of the dataset. Supported datasets are `ECCO2Monthly()`, `ECCO2Daily()`, `ECCO4Monthly()`, `JRA55RepeatYear()`, or `JRA55MultipleYears()`. - `dir`: The directory where the dataset is stored. @@ -40,7 +40,14 @@ end const AnyDateTime = Union{AbstractCFDateTime, Dates.AbstractDateTime} const Metadatum = Metadata{<:AnyDateTime} -# A constructor for a single date +""" + Metadatum(variable_name; + dataset, + date=first_date(dataset, variable_name), + dir=default_download_folder(dataset)) + +Specific constructor for a `Metadata` object with a single date, representative of a snapshot in time. +""" function Metadatum(variable_name; dataset, date=first_date(dataset, variable_name), @@ -142,7 +149,8 @@ end_date(dataset, variable_name) = last(all_dates(dataset, variable_name)) """ metadata_filename(metadata) -# File names of metadata containing multiple dates +File names of metadata containing multiple dates. The specific version for a `Metadatum` object is +extended in the data specific modules. """ metadata_filename(metadata) = [metadata_filename(metadatum) for metadatum in metadata] From 4f7fdb24def5e24e6ee1ba5e7d62f4b086a15156 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 11 Mar 2025 11:35:40 +0100 Subject: [PATCH 031/104] change examples --- examples/ecco_inspect_temperature_salinity.jl | 9 ++++----- examples/ecco_mixed_layer_depth.jl | 12 ++++-------- examples/generate_surface_fluxes.jl | 4 ++-- ...editerranean_simulation_with_ecco_restoring.jl | 15 ++++++--------- examples/near_global_ocean_simulation.jl | 4 ++-- examples/one_degree_simulation.jl | 3 +-- examples/single_column_os_papa_simulation.jl | 4 ++-- 7 files changed, 21 insertions(+), 30 deletions(-) diff --git a/examples/ecco_inspect_temperature_salinity.jl b/examples/ecco_inspect_temperature_salinity.jl index a1b4f489..da1bc0b8 100644 --- a/examples/ecco_inspect_temperature_salinity.jl +++ b/examples/ecco_inspect_temperature_salinity.jl @@ -39,15 +39,14 @@ sb = SeawaterBuoyancy(; equation_of_state) tracers = (T=T, S=S) b = Field(buoyancy(sb, grid, tracers)) -start = DateTime(1993, 1, 1) -stop = DateTime(1999, 1, 1) -dates = range(start; stop, step=Month(1)) +start_date = DateTime(1993, 1, 1) +end_date = DateTime(1999, 1, 1) Tmeta = Metadata(:temperature; dates, dataset=ECCO4Monthly()) Smeta = Metadata(:salinity; dates, dataset=ECCO4Monthly()) -Tt = ECCOFieldTimeSeries(Tmeta, grid; time_indices_in_memory=length(dates)) -St = ECCOFieldTimeSeries(Smeta, grid; time_indices_in_memory=length(dates)) +Tt = ECCOFieldTimeSeries(:temperature, grid; start_date, end_date, time_indices_in_memory=length(dates)) +St = ECCOFieldTimeSeries(:salinity, grid; start_date, end_date, time_indices_in_memory=length(dates)) fig = Figure(size=(900, 1050)) diff --git a/examples/ecco_mixed_layer_depth.jl b/examples/ecco_mixed_layer_depth.jl index a444e734..e712eaa6 100644 --- a/examples/ecco_mixed_layer_depth.jl +++ b/examples/ecco_mixed_layer_depth.jl @@ -30,15 +30,11 @@ bottom_height = regrid_bathymetry(grid; grid = ImmersedBoundaryGrid(grid, GridFittedBottom(bottom_height)) -start = DateTimeProlepticGregorian(1993, 1, 1) -stop = DateTimeProlepticGregorian(2003, 1, 1) -dates = range(start; stop, step=Month(1)) +start_date = DateTimeProlepticGregorian(1993, 1, 1) +end_date = DateTimeProlepticGregorian(2003, 1, 1) -Tmeta = Metadata(:temperature; dates, dataset=ECCO4Monthly()) -Smeta = Metadata(:salinity; dates, dataset=ECCO4Monthly()) - -Tt = ECCOFieldTimeSeries(Tmeta, grid; time_indices_in_memory=2) -St = ECCOFieldTimeSeries(Smeta, grid; time_indices_in_memory=2) +Tt = ECCOFieldTimeSeries(Tmeta, grid; start_date, end_date, time_indices_in_memory=2) +St = ECCOFieldTimeSeries(Smeta, grid; start_date, end_date, time_indices_in_memory=2) ht = FieldTimeSeries{Center, Center, Nothing}(grid, Tt.times) equation_of_state = TEOS10EquationOfState() diff --git a/examples/generate_surface_fluxes.jl b/examples/generate_surface_fluxes.jl index c762d9d2..7ef0087c 100644 --- a/examples/generate_surface_fluxes.jl +++ b/examples/generate_surface_fluxes.jl @@ -52,8 +52,8 @@ ocean = ocean_simulation(grid, closure=nothing) # Now that we have an atmosphere and ocean, we `set!` the ocean temperature and salinity # to the ECCO2 data by first creating T, S metadata objects, -T_metadata = Metadata(:temperature; dataset=ECCO4Monthly()) -S_metadata = Metadata(:salinity; dataset=ECCO4Monthly()) +T_metadata = Metadatum(:temperature; date=DateTime(1993, 1, 1), dataset=ECCO4Monthly()) +S_metadata = Metadatum(:salinity; date=DateTime(1993, 1, 1), dataset=ECCO4Monthly()) # Note that if a date is not provided to `Metadata`, then the default Jan 1st, 1992 is used. # To copy the ECCO state into `ocean.model`, we use `set!`, diff --git a/examples/mediterranean_simulation_with_ecco_restoring.jl b/examples/mediterranean_simulation_with_ecco_restoring.jl index d9bc528d..66567540 100644 --- a/examples/mediterranean_simulation_with_ecco_restoring.jl +++ b/examples/mediterranean_simulation_with_ecco_restoring.jl @@ -23,8 +23,6 @@ using ClimaOcean.ECCO using ClimaOcean.ECCO: ECCO4Monthly using Oceananigans.Units using Printf - -using CFTime using Dates # ## Grid Configuration for the Mediterranean Sea @@ -73,13 +71,11 @@ grid = ImmersedBoundaryGrid(grid, GridFittedBottom(bottom_height)) # using the function `ECCO_restoring_forcing` to apply restoring forcings for these tracers. # This allows us to nudge the model towards realistic temperature and salinity profiles. -dates = DateTimeProlepticGregorian(1993, 1, 1) : Month(1) : DateTimeProlepticGregorian(1993, 12, 1) - -temperature = Metadata(:temperature; dates, dataset=ECCO4Monthly()) -salinity = Metadata(:salinity; dates, dataset=ECCO4Monthly()) +start_date = DateTime(1993, 1, 1) +end_date = DateTime(1993, 12, 1) -FT = ECCO_restoring_forcing(temperature; architecture = GPU(), timescale = 2days) -FS = ECCO_restoring_forcing(salinity; architecture = GPU(), timescale = 2days) +FT = ECCO_restoring_forcing(:temperature; start_date, end_date, architecture = GPU(), timescale = 2days) +FS = ECCO_restoring_forcing(:salinity; start_date, end_date, architecture = GPU(), timescale = 2days) # Constructing the Simulation # @@ -94,7 +90,8 @@ ocean = ocean_simulation(grid; forcing = (T = FT, S = FS)) # In this case, our ECCO dataset has access to a temperature and a salinity # field, so we initialize temperature T and salinity S from ECCO. -set!(ocean.model, T = temperature[1], S = salinity[1]) +set!(ocean.model, T = Metadatum(:temperature; date=start_date), + S = Metadatum(:salinity; date=start_date)) fig = Figure() ax = Axis(fig[1, 1]) diff --git a/examples/near_global_ocean_simulation.jl b/examples/near_global_ocean_simulation.jl index 1f005b7b..ec45a314 100644 --- a/examples/near_global_ocean_simulation.jl +++ b/examples/near_global_ocean_simulation.jl @@ -84,8 +84,8 @@ ocean.model # We initialize the ocean model with ECCO2 temperature and salinity for January 1, 1993. date = DateTimeProlepticGregorian(1993, 1, 1) -set!(ocean.model, T=Metadata(:temperature; dates=date, dataset=ECCO4Monthly()), - S=Metadata(:salinity; dates=date, dataset=ECCO4Monthly())) +set!(ocean.model, T=Metadatum(:temperature; date, dataset=ECCO4Monthly()), + S=Metadatum(:salinity; date, dataset=ECCO4Monthly())) # ### Prescribed atmosphere and radiation # diff --git a/examples/one_degree_simulation.jl b/examples/one_degree_simulation.jl index d1c2f6ec..23deab86 100644 --- a/examples/one_degree_simulation.jl +++ b/examples/one_degree_simulation.jl @@ -100,8 +100,7 @@ ocean = ocean_simulation(grid; # We initialize the ocean from the ECCO state estimate. -set!(ocean.model, T=Metadata(:temperature; dates=first(dates), dataset=ECCO4Monthly()), - S=Metadata(:salinity; dates=first(dates), dataset=ECCO4Monthly())) +set!(ocean.model, T=temperature[1], S=salinity[1]) # ### Atmospheric forcing diff --git a/examples/single_column_os_papa_simulation.jl b/examples/single_column_os_papa_simulation.jl index dfed57db..dc19b52f 100644 --- a/examples/single_column_os_papa_simulation.jl +++ b/examples/single_column_os_papa_simulation.jl @@ -51,8 +51,8 @@ ocean.model # We set initial conditions from ECCO: -set!(ocean.model, T=Metadata(:temperature, dataset=ECCO4Monthly()), - S=Metadata(:salinity, dataset=ECCO4Monthly())) +set!(ocean.model, T=Metadatum(:temperature), + S=Metadatum(:salinity)) # # A prescribed atmosphere based on JRA55 re-analysis # From 58662d25ea7ec9c90898d4effe217b12512a4fe6 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 11 Mar 2025 11:40:14 +0100 Subject: [PATCH 032/104] we don't need this --- examples/ecco_inspect_temperature_salinity.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/ecco_inspect_temperature_salinity.jl b/examples/ecco_inspect_temperature_salinity.jl index da1bc0b8..d3a77ce6 100644 --- a/examples/ecco_inspect_temperature_salinity.jl +++ b/examples/ecco_inspect_temperature_salinity.jl @@ -42,9 +42,6 @@ b = Field(buoyancy(sb, grid, tracers)) start_date = DateTime(1993, 1, 1) end_date = DateTime(1999, 1, 1) -Tmeta = Metadata(:temperature; dates, dataset=ECCO4Monthly()) -Smeta = Metadata(:salinity; dates, dataset=ECCO4Monthly()) - Tt = ECCOFieldTimeSeries(:temperature, grid; start_date, end_date, time_indices_in_memory=length(dates)) St = ECCOFieldTimeSeries(:salinity, grid; start_date, end_date, time_indices_in_memory=length(dates)) From 22c8d08e215fe667b39787c061648a34d5940ae1 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 11 Mar 2025 11:41:01 +0100 Subject: [PATCH 033/104] chnages --- examples/ecco_mixed_layer_depth.jl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/ecco_mixed_layer_depth.jl b/examples/ecco_mixed_layer_depth.jl index e712eaa6..16b505d4 100644 --- a/examples/ecco_mixed_layer_depth.jl +++ b/examples/ecco_mixed_layer_depth.jl @@ -4,7 +4,6 @@ using ClimaOcean.DataWrangling.ECCO: ECCO_field, ECCOFieldTimeSeries, ECCO4Month using Oceananigans using CairoMakie using Printf -using CFTime using Dates using SeawaterPolynomials: TEOS10EquationOfState @@ -30,11 +29,11 @@ bottom_height = regrid_bathymetry(grid; grid = ImmersedBoundaryGrid(grid, GridFittedBottom(bottom_height)) -start_date = DateTimeProlepticGregorian(1993, 1, 1) -end_date = DateTimeProlepticGregorian(2003, 1, 1) +start_date = DateTime(1993, 1, 1) +end_date = DateTime(2003, 1, 1) -Tt = ECCOFieldTimeSeries(Tmeta, grid; start_date, end_date, time_indices_in_memory=2) -St = ECCOFieldTimeSeries(Smeta, grid; start_date, end_date, time_indices_in_memory=2) +Tt = ECCOFieldTimeSeries(:temprature, grid; start_date, end_date, time_indices_in_memory=2) +St = ECCOFieldTimeSeries(:salinity, grid; start_date, end_date, time_indices_in_memory=2) ht = FieldTimeSeries{Center, Center, Nothing}(grid, Tt.times) equation_of_state = TEOS10EquationOfState() From 79f37ebb117935945d08586bab7fb6a4610760c2 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 11 Mar 2025 11:47:19 +0100 Subject: [PATCH 034/104] export ui functions --- src/ClimaOcean.jl | 3 +++ src/DataWrangling/DataWrangling.jl | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ClimaOcean.jl b/src/ClimaOcean.jl index 670149b2..d37389ac 100644 --- a/src/ClimaOcean.jl +++ b/src/ClimaOcean.jl @@ -25,6 +25,9 @@ export PowerLawStretching, LinearStretching, exponential_z_faces, Metadata, + Metadatum, + first_date, + last_date, all_dates, JRA55FieldTimeSeries, ECCO_field, diff --git a/src/DataWrangling/DataWrangling.jl b/src/DataWrangling/DataWrangling.jl index 71b43ec0..e342d194 100644 --- a/src/DataWrangling/DataWrangling.jl +++ b/src/DataWrangling/DataWrangling.jl @@ -1,6 +1,6 @@ module DataWrangling -export Metadata, all_dates, first_date, end_date +export Metadata, Metadatum, all_dates, first_date, end_date using Oceananigans using Downloads From a98669f6d662166236738478d5d3b436f0199f8f Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 11 Mar 2025 11:56:51 +0100 Subject: [PATCH 035/104] last date --- src/DataWrangling/DataWrangling.jl | 2 +- src/DataWrangling/metadata.jl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DataWrangling/DataWrangling.jl b/src/DataWrangling/DataWrangling.jl index e342d194..5ce954d2 100644 --- a/src/DataWrangling/DataWrangling.jl +++ b/src/DataWrangling/DataWrangling.jl @@ -1,6 +1,6 @@ module DataWrangling -export Metadata, Metadatum, all_dates, first_date, end_date +export Metadata, Metadatum, all_dates, first_date, last_date using Oceananigans using Downloads diff --git a/src/DataWrangling/metadata.jl b/src/DataWrangling/metadata.jl index 9380b94b..0de805f3 100644 --- a/src/DataWrangling/metadata.jl +++ b/src/DataWrangling/metadata.jl @@ -140,11 +140,11 @@ Extracts the first date of the given dataset and variable name formatted using t first_date(dataset, variable_name) = first(all_dates(dataset, variable_name)) """ - end_date(dataset, variable_name) + last_date(dataset, variable_name) Extracts the last date of the given dataset and variable name formatted using the `DateTime` type. """ -end_date(dataset, variable_name) = last(all_dates(dataset, variable_name)) +last_date(dataset, variable_name) = last(all_dates(dataset, variable_name)) """ metadata_filename(metadata) From 5405ef374a2e11c77f2bd25935d7b903aa8b04c9 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 11 Mar 2025 13:13:38 +0100 Subject: [PATCH 036/104] vestigial dates --- src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl index 97ca8db3..2f3c6ac1 100644 --- a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl +++ b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl @@ -25,7 +25,7 @@ function JRA55PrescribedAtmosphere(architecture = CPU(), FT = Float32; include_rivers_and_icebergs = false, other_kw...) - kw = (; time_indexing, dates, backend, start_date, end_date, dataset) + kw = (; time_indexing, backend, start_date, end_date, dataset) kw = merge(kw, other_kw) ua = JRA55FieldTimeSeries(:eastward_velocity, architecture, FT; kw...) From b2ff520b256c1a2b8d740b842090d2e30e9a5b97 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 11 Mar 2025 13:19:41 +0100 Subject: [PATCH 037/104] change docstrings --- .../JRA55/JRA55_field_time_series.jl | 8 +++----- .../JRA55/JRA55_prescribed_atmosphere.jl | 20 +++++++++++++++---- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index 62eed3f4..8c928774 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -147,12 +147,8 @@ new_backend(::JRA55NetCDFBackend, start, length) = JRA55NetCDFBackend(start, len latitude = nothing, longitude = nothing, dir = download_JRA55_cache, - filename = nothing, - shortname = nothing, backend = InMemory(), - time_indexing = Cyclical(), - preprocess_chunk_size = 10, - preprocess_architecture = CPU()) + time_indexing = Cyclical()) Return a `FieldTimeSeries` containing atmospheric reanalysis data for `variable_name`, which describes one of the variables in the "repeat year forcing" dataset derived @@ -188,6 +184,8 @@ Keyword arguments - `dir`: The directory of the data file. Default: `ClimaOcean.JRA55.download_JRA55_cache`. +- `time_indexing`: The time indexing scheme for the field time series. Default: `Cyclical()`. + - `latitude`: Guiding latitude bounds for the resulting grid. Used to slice the data when loading into memory. Default: nothing, which retains the latitude range of the native grid. diff --git a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl index 2f3c6ac1..5a445b0f 100644 --- a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl +++ b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl @@ -1,19 +1,31 @@ const AA = Oceananigans.Architectures.AbstractArchitecture -JRA55PrescribedAtmosphere(arch::Distributed; kw...) = +JRA55PrescribedAtmosphere(arch::Distributed, FT = Float32; kw...) = JRA55PrescribedAtmosphere(child_architecture(arch); kw...) + """ - JRA55PrescribedAtmosphere([architecture = CPU()]; + JRA55PrescribedAtmosphere([architecture = CPU(), FT = Float32]; dataset = JRA55RepeatYear(), - dates = all_dates(dataset), + start_date = first_date(dataset, :temperature), + end_date = last_date(dataset, :temperature), backend = JRA55NetCDFBackend(10), time_indexing = Cyclical(), surface_layer_height = 10, # meters include_rivers_and_icebergs = false, other_kw...) -Return a `PrescribedAtmosphere` representing JRA55 reanalysis data. +Return a [`PrescribedAtmosphere`](@ref) representing JRA55 reanalysis data. +The atmospheric data will be held in `JRA55FieldTimeSeries` objects containing: + +- velocities (`ua`, `va`) +- tracers (`Ta`, `qa`) +- pressure (`pa`) +- freshwater fluxes (`Fra`, `Fsn`) +- downwelling radiation (`Ql`, `Qs`) +- auxiliary freshwater fluxes (`Fri`, `Fic`) if `include_rivers_and_icebergs` is `true` + +For a detailed description of the keyword arguments, see the [`JRA55FieldTimeSeries`](@ref) constructor. """ function JRA55PrescribedAtmosphere(architecture = CPU(), FT = Float32; dataset = JRA55RepeatYear(), From 992b4d7930e299425641a88c7750c2b6525a90c8 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 11 Mar 2025 13:20:56 +0100 Subject: [PATCH 038/104] better like this --- src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl index 5a445b0f..eb763832 100644 --- a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl +++ b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl @@ -16,15 +16,7 @@ JRA55PrescribedAtmosphere(arch::Distributed, FT = Float32; kw...) = other_kw...) Return a [`PrescribedAtmosphere`](@ref) representing JRA55 reanalysis data. -The atmospheric data will be held in `JRA55FieldTimeSeries` objects containing: - -- velocities (`ua`, `va`) -- tracers (`Ta`, `qa`) -- pressure (`pa`) -- freshwater fluxes (`Fra`, `Fsn`) -- downwelling radiation (`Ql`, `Qs`) -- auxiliary freshwater fluxes (`Fri`, `Fic`) if `include_rivers_and_icebergs` is `true` - +The atmospheric data will be held in `JRA55FieldTimeSeries` objects containing. For a detailed description of the keyword arguments, see the [`JRA55FieldTimeSeries`](@ref) constructor. """ function JRA55PrescribedAtmosphere(architecture = CPU(), FT = Float32; From e65b0ea3c384dadb44717e5b5f2e175cbcede474 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 12 Mar 2025 10:45:21 +0100 Subject: [PATCH 039/104] add native date range --- src/DataWrangling/ECCO/ECCO.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataWrangling/ECCO/ECCO.jl b/src/DataWrangling/ECCO/ECCO.jl index 141cb246..56a96a43 100644 --- a/src/DataWrangling/ECCO/ECCO.jl +++ b/src/DataWrangling/ECCO/ECCO.jl @@ -7,7 +7,7 @@ export ECCOFieldTimeSeries, ECCORestoring, LinearlyTaperedPolarMask using ClimaOcean using ClimaOcean.DistributedUtils using ClimaOcean.DataWrangling -using ClimaOcean.DataWrangling: inpaint_mask!, NearestNeighborInpainting, download_progress +using ClimaOcean.DataWrangling: inpaint_mask!, NearestNeighborInpainting, download_progress, compute_native_date_range using ClimaOcean.InitialConditions: three_dimensional_regrid!, interpolate! using Oceananigans From 40a9f2465d47b9924bef4a079ab06fada1d81c66 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 13 Mar 2025 09:53:54 +0100 Subject: [PATCH 040/104] change the url --- src/DataWrangling/ECCO/ECCO_metadata.jl | 25 +++++++++++------------ src/DataWrangling/JRA55/JRA55_metadata.jl | 11 ++++------ 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/DataWrangling/ECCO/ECCO_metadata.jl b/src/DataWrangling/ECCO/ECCO_metadata.jl index b4b4f583..3e3583b7 100644 --- a/src/DataWrangling/ECCO/ECCO_metadata.jl +++ b/src/DataWrangling/ECCO/ECCO_metadata.jl @@ -72,14 +72,6 @@ short_name(data::Metadata{<:Any, <:ECCO2Daily}) = ECCO2_short_names[data.name] short_name(data::Metadata{<:Any, <:ECCO2Monthly}) = ECCO2_short_names[data.name] short_name(data::Metadata{<:Any, <:ECCO4Monthly}) = ECCO4_short_names[data.name] -metadata_url(prefix, m::Metadata{<:Any, <:ECCO2Daily}) = prefix * "/" * short_name(m) * "/" * metadata_filename(m) -metadata_url(prefix, m::Metadata{<:Any, <:ECCO2Monthly}) = prefix * "/" * short_name(m) * "/" * metadata_filename(m) - -function metadata_url(prefix, m::Metadata{<:Any, <:ECCO4Monthly}) - year = string(Dates.year(m.dates)) - return prefix * "/" * short_name(m) * "/" * year * "/" * metadata_filename(m) -end - location(data::ECCOMetadata) = ECCO_location[data.name] variable_is_three_dimensional(data::ECCOMetadata) = @@ -121,10 +113,17 @@ ECCO_location = Dict( :v_velocity => (Center, Face, Center), ) +const ECCO2_url = "https://ecco.jpl.nasa.gov/drive/files/ECCO2/cube92_latlon_quart_90S90N/" + # URLs for the ECCO datasets specific to each dataset -urls(::Metadata{<:Any, <:ECCO2Monthly}) = "https://ecco.jpl.nasa.gov/drive/files/ECCO2/cube92_latlon_quart_90S90N/monthly" -urls(::Metadata{<:Any, <:ECCO2Daily}) = "https://ecco.jpl.nasa.gov/drive/files/ECCO2/cube92_latlon_quart_90S90N/daily" -urls(::Metadata{<:Any, <:ECCO4Monthly}) = "https://ecco.jpl.nasa.gov/drive/files/Version4/Release4/interp_monthly" +metadata_url(m::Metadata{<:Any, <:ECCO2Daily}) = ECCO2_url * "monthly/" * short_name(m) * "/" * metadata_filename(m) +metadata_url(m::Metadata{<:Any, <:ECCO2Monthly}) = ECCO2_url * "daily/" * short_name(m) * "/" * metadata_filename(m) + +function metadata_url(m::Metadata{<:Any, <:ECCO4Monthly}) + year = string(Dates.year(m.dates)) + prefix = "https://ecco.jpl.nasa.gov/drive/files/Version4/Release4/interp_monthly/" + return prefix * short_name(m) * "/" * year * "/" * metadata_filename(m) +end """ download_dataset(metadata::ECCOMetadata; url = urls(metadata)) @@ -156,7 +155,7 @@ Arguments ========= - `metadata::ECCOMetadata`: The metadata specifying the dataset to be downloaded. """ -function download_dataset(metadata::ECCOMetadata; url = urls(metadata)) +function download_dataset(metadata::ECCOMetadata) username = get(ENV, "ECCO_USERNAME", nothing) password = get(ENV, "ECCO_PASSWORD", nothing) dir = metadata.dir @@ -173,7 +172,7 @@ function download_dataset(metadata::ECCOMetadata; url = urls(metadata)) asyncmap(metadata; ntasks) do metadatum # Distribute the download among tasks - fileurl = metadata_url(url, metadatum) + fileurl = metadata_url(metadatum) filepath = metadata_path(metadatum) if !isfile(filepath) diff --git a/src/DataWrangling/JRA55/JRA55_metadata.jl b/src/DataWrangling/JRA55/JRA55_metadata.jl index 23315ab2..038d4e0d 100644 --- a/src/DataWrangling/JRA55/JRA55_metadata.jl +++ b/src/DataWrangling/JRA55/JRA55_metadata.jl @@ -150,18 +150,15 @@ JRA55_repeat_year_urls = Dict( variable_is_three_dimensional(data::JRA55Metadata) = false -urls(metadata::Metadata{<:Any, <:JRA55RepeatYear}) = JRA55_repeat_year_urls[metadata.name] +metadata_url(metadata::Metadata{<:Any, <:JRA55RepeatYear}) = JRA55_repeat_year_urls[metadata.name] # TODO: -# urls(metadata::Metadata{<:Any, <:JRA55MultipleYears}) = ... +# metadata_url(metadata::Metadata{<:Any, <:JRA55MultipleYears}) = ... -metadata_url(prefix, ::Metadata{<:Any, <:JRA55RepeatYear}) = prefix # No specific name for this url - -# TODO: This will need to change when we add a method for JRA55MultipleYears -function download_dataset(metadata::JRA55Metadata; url = urls(metadata)) +function download_dataset(metadata::JRA55Metadata) @root for metadatum in metadata - fileurl = metadata_url(url, metadatum) + fileurl = metadata_url(metadatum) filepath = metadata_path(metadatum) if !isfile(filepath) From 58d5c1102ffb504bfb806b251d2f308b229e8ebb Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 13 Mar 2025 09:59:44 +0100 Subject: [PATCH 041/104] fix tests --- test/test_ecco.jl | 30 ++++++++++++------------------ test/test_jra55.jl | 8 ++++---- test/test_surface_fluxes.jl | 12 ++++++------ 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/test/test_ecco.jl b/test/test_ecco.jl index ed658756..d81aaba4 100644 --- a/test/test_ecco.jl +++ b/test/test_ecco.jl @@ -60,7 +60,7 @@ end @testset "Inpainting algorithm" begin for arch in test_architectures - T_metadata = Metadata(:temperature; dates=dates[1], dataset=ECCO4Monthly()) + T_metadatum = Metadatum(:temperature; date=start_date, dataset=ECCO4Monthly()) grid = LatitudeLongitudeGrid(arch, size = (100, 100, 10), @@ -72,8 +72,8 @@ end fully_inpainted_field = CenterField(grid) partially_inpainted_field = CenterField(grid) - set!(fully_inpainted_field, T_metadata; inpainting = NearestNeighborInpainting(Inf)) - set!(partially_inpainted_field, T_metadata; inpainting = NearestNeighborInpainting(1)) + set!(fully_inpainted_field, T_metadatum; inpainting = NearestNeighborInpainting(Inf)) + set!(partially_inpainted_field, T_metadatum; inpainting = NearestNeighborInpainting(1)) fully_inpainted_interior = on_architecture(CPU(), interior(fully_inpainted_field)) partially_inpainted_interior = on_architecture(CPU(), interior(partially_inpainted_field)) @@ -102,9 +102,7 @@ end southern = (φ₁, φ₂), z = (z₁, 0)) - T_restoring = ECCORestoring(:temperature, arch; - dates, mask, inpainting, - rate=1/1000) + T_restoring = ECCORestoring(:temperature, arch; start_date, end_date, mask, inpainting, rate=1/1000) fill!(T_restoring.field_time_series[1], 1.0) fill!(T_restoring.field_time_series[2], 1.0) @@ -134,8 +132,8 @@ end field = CenterField(grid) @test begin - set!(field, Metadata(:temperature, dates=start_date, dataset=ECCO4Monthly())) - set!(field, Metadata(:salinity, dates=start_date, dataset=ECCO4Monthly())) + set!(field, Metadatum(:temperature, date=start_date, dataset=ECCO4Monthly())) + set!(field, Metadatum(:salinity, date=start_date, dataset=ECCO4Monthly())) true end end @@ -154,13 +152,12 @@ end field = CenterField(grid) @test begin - set!(field, Metadata(:temperature, dates=start_date, dataset=ECCO4Monthly())) - set!(field, Metadata(:salinity, dates=start_date, dataset=ECCO4Monthly())) + set!(field, Metadatum(:temperature, date=start_date, dataset=ECCO4Monthly())) + set!(field, Metadatum(:salinity, date=start_date, dataset=ECCO4Monthly())) true end - forcing_T = ECCORestoring(:temperature, arch; dates, inpainting, - rate = 1/1000) + forcing_T = ECCORestoring(:temperature, arch; start_date, end_date, inpainting, rate=1/1000) ocean = ocean_simulation(grid; forcing = (; T = forcing_T), verbose=false) @@ -183,8 +180,8 @@ end ocean = ocean_simulation(grid) date = DateTimeProlepticGregorian(1993, 1, 1) - set!(ocean.model, T=Metadata(:temperature; dates=start_date, dataset=ECCO4Monthly()), - S=Metadata(:salinity; dates=start_date, dataset=ECCO4Monthly())) + set!(ocean.model, T=Metadatum(:temperature; date=start_date, dataset=ECCO4Monthly()), + S=Metadatum(:salinity; date=start_date, dataset=ECCO4Monthly())) end end @@ -201,10 +198,7 @@ end end_date = DateTimeProlepticGregorian(1993, 5, 1) dates = start_date : Month(1) : end_date - T_restoring = ECCORestoring(:temperature, arch; - dates, - rate = 1 / 1000.0, - inpainting) + T_restoring = ECCORestoring(:temperature, arch; start_date, end_date, inpainting, rate=1/1000) times = native_times(T_restoring.field_time_series.backend.metadata) ocean = ocean_simulation(grid, forcing = (; T = T_restoring)) diff --git a/test/test_jra55.jl b/test/test_jra55.jl index 7222e2e7..4b160f76 100644 --- a/test/test_jra55.jl +++ b/test/test_jra55.jl @@ -11,9 +11,9 @@ using ClimaOcean.OceanSeaIceModels: PrescribedAtmosphere # This should download files called "RYF.rsds.1990_1991.nc" and "RYF.tas.1990_1991.nc" for test_name in (:downwelling_shortwave_radiation, :temperature) dates = ClimaOcean.DataWrangling.all_dates(JRA55.JRA55RepeatYear(), test_name) - dates = dates[1:3] + end_date = dates[3] - JRA55_fts = JRA55FieldTimeSeries(test_name, arch; dates) + JRA55_fts = JRA55FieldTimeSeries(test_name, arch; end_date) test_filename = joinpath(download_JRA55_cache, "RYF.rsds.1990_1991.nc") @test JRA55_fts isa FieldTimeSeries @@ -60,8 +60,8 @@ using ClimaOcean.OceanSeaIceModels: PrescribedAtmosphere name = :downwelling_shortwave_radiation dates = ClimaOcean.DataWrangling.all_dates(JRA55.JRA55RepeatYear(), name) - dates = dates[1:3] - JRA55_fts = JRA55FieldTimeSeries(name, arch; dates) + end_date = dates[3] + JRA55_fts = JRA55FieldTimeSeries(name, arch; end_date) # Make target grid and field resolution = 1 # degree, eg 1/4 diff --git a/test/test_surface_fluxes.jl b/test/test_surface_fluxes.jl index e7ea1f06..f8ba2a86 100644 --- a/test/test_surface_fluxes.jl +++ b/test/test_surface_fluxes.jl @@ -187,8 +187,8 @@ end closure = nothing, bottom_drag_coefficient = 0.0) - dates = all_dates(JRA55RepeatYear(), :temperature)[1:2] - atmosphere = JRA55PrescribedAtmosphere(arch; dates, backend = InMemory()) + dates = all_dates(JRA55RepeatYear(), :temperature) + atmosphere = JRA55PrescribedAtmosphere(arch; end_date=dates[2], backend = InMemory()) fill!(ocean.model.tracers.T, -2.0) @@ -230,13 +230,13 @@ end ocean = ocean_simulation(grid; momentum_advection, tracer_advection, closure, tracers, coriolis) - T_metadata = Metadata(:temperature, dates=DateTimeProlepticGregorian(1993, 1, 1), dataset=ECCO4Monthly()) - S_metadata = Metadata(:salinity, dates=DateTimeProlepticGregorian(1993, 1, 1), dataset=ECCO4Monthly()) + T_metadata = Metadatum(:temperature, date=DateTimeProlepticGregorian(1993, 1, 1), dataset=ECCO4Monthly()) + S_metadata = Metadatum(:salinity, date=DateTimeProlepticGregorian(1993, 1, 1), dataset=ECCO4Monthly()) set!(ocean.model; T=T_metadata, S=S_metadata) - dates = all_dates(JRA55RepeatYear(), :temperature)[1:10] - atmosphere = JRA55PrescribedAtmosphere(arch; dates, backend = InMemory()) + end_date = all_dates(JRA55RepeatYear(), :temperature)[10] + atmosphere = JRA55PrescribedAtmosphere(arch; end_date, backend = InMemory()) radiation = Radiation(ocean_albedo=0.1, ocean_emissivity=1.0) sea_ice = nothing From 4fc23948b8fff0f04477003b0404db334675f9b7 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 13 Mar 2025 10:57:47 +0100 Subject: [PATCH 042/104] fixing the tests --- test/test_surface_fluxes.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test_surface_fluxes.jl b/test/test_surface_fluxes.jl index f8ba2a86..c2710dce 100644 --- a/test/test_surface_fluxes.jl +++ b/test/test_surface_fluxes.jl @@ -18,6 +18,9 @@ using Oceananigans.TimeSteppers: update_state! using Oceananigans.Units: hours, days using ClimaOcean.DataWrangling: all_dates +using ClimaSeaIce.SeaIceMomentumEquations +using ClimaSeaIce.Rheologies + import ClimaOcean.OceanSeaIceModels.InterfaceComputations: saturation_specific_humidity using Statistics: mean, std From 385a44ad61cf2fd3f52410a21fcd59b52994b15b Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 13 Mar 2025 14:40:03 +0100 Subject: [PATCH 043/104] fix tests --- test/test_ecco.jl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/test_ecco.jl b/test/test_ecco.jl index d81aaba4..c91bfef7 100644 --- a/test/test_ecco.jl +++ b/test/test_ecco.jl @@ -1,6 +1,5 @@ include("runtests_setup.jl") -using CFTime using Dates using ClimaOcean @@ -15,8 +14,8 @@ using Oceananigans.Units using CUDA: @allowscalar -start_date = DateTimeProlepticGregorian(1993, 1, 1) -end_date = DateTimeProlepticGregorian(1993, 2, 1) +start_date = DateTime(1993, 1, 1) +end_date = DateTime(1993, 2, 1) dates = start_date : Month(1) : end_date # Inpaint only the first two cells inside the missing mask @@ -179,7 +178,7 @@ end halo = (7, 7, 7)) ocean = ocean_simulation(grid) - date = DateTimeProlepticGregorian(1993, 1, 1) + date = DateTime(1993, 1, 1) set!(ocean.model, T=Metadatum(:temperature; date=start_date, dataset=ECCO4Monthly()), S=Metadatum(:salinity; date=start_date, dataset=ECCO4Monthly())) end @@ -194,8 +193,8 @@ end z = (-200, 0), halo = (7, 7, 7)) - start_date = DateTimeProlepticGregorian(1993, 1, 1) - end_date = DateTimeProlepticGregorian(1993, 5, 1) + start_date = DateTime(1993, 1, 1) + end_date = DateTime(1993, 5, 1) dates = start_date : Month(1) : end_date T_restoring = ECCORestoring(:temperature, arch; start_date, end_date, inpainting, rate=1/1000) From 54e583829267c8a16f153530577d097c9cd6bada Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Fri, 14 Mar 2025 12:38:53 +0100 Subject: [PATCH 044/104] it's six dates not 5 --- test/test_ecco.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_ecco.jl b/test/test_ecco.jl index c91bfef7..6ff76364 100644 --- a/test/test_ecco.jl +++ b/test/test_ecco.jl @@ -221,7 +221,7 @@ end end # The backend has cycled to the end - @test time_indices(T_restoring.field_time_series) == (5, 1) + @test time_indices(T_restoring.field_time_series) == (6, 1) end end From fb35d7816c72a7d31c7ec59b85a74ee64add031d Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Sat, 15 Mar 2025 10:57:30 +0100 Subject: [PATCH 045/104] this works --- .../JRA55/JRA55_field_time_series.jl | 2 + src/DataWrangling/JRA55/JRA55_metadata.jl | 47 ++++++++++++------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index 0f503e88..4698b3c8 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -3,6 +3,8 @@ using ClimaOcean.DataWrangling: compute_native_date_range using Oceananigans.Grids: AbstractGrid using Oceananigans.OutputReaders: PartlyInMemory +using Adapt + download_JRA55_cache::String = "" function __init__() diff --git a/src/DataWrangling/JRA55/JRA55_metadata.jl b/src/DataWrangling/JRA55/JRA55_metadata.jl index 93f39d88..44187881 100644 --- a/src/DataWrangling/JRA55/JRA55_metadata.jl +++ b/src/DataWrangling/JRA55/JRA55_metadata.jl @@ -25,6 +25,9 @@ default_download_folder(::Union{<:JRA55MultipleYears, <:JRA55RepeatYear}) = down Base.size(data::JRA55Metadata) = (640, 320, length(data.dates)) Base.size(::JRA55Metadatum) = (640, 320, 1) +# JRA55 is a spatially 2D dataset +variable_is_three_dimensional(data::JRA55Metadata) = false + # The whole range of dates in the different dataset datasets # NOTE! rivers and icebergs have a different frequency! (typical JRA55 data is three-hourly while rivers and icebergs are daily) function all_dates(::JRA55RepeatYear, name) @@ -59,22 +62,30 @@ function JRA55_time_indices(dataset, dates, name) end # File name generation specific to each Dataset dataset -function metadata_filename(metadata::Metadata{<:Any, <:JRA55RepeatYear}) # No difference +function metadata_filename(metadata::Metadatum{<:Any, <:JRA55RepeatYear}) # No difference shortname = short_name(metadata) return "RYF." * shortname * ".1990_1991.nc" end -function metadata_filename(metadata::Metadata{<:AbstractCFDateTime, <:JRA55MultipleYears}) +multiple_year_time_displaced_variables = [:rain_freshwater_flux, + :snow_freshwater_flux, + :downwelling_shortwave_radiation, + :downwelling_longwave_radiation] + +function metadata_filename(metadata::Metadatum{<:Any, <:JRA55MultipleYears}) # fix the filename shortname = short_name(metadata) year = Dates.year(metadata.dates) suffix = "_input4MIPs_atmosphericState_OMIP_MRI-JRA55-do-1-5-0_gr_" - if shortname == "tas" - dates = "$(year)01010000-$(year)12312100" + if metadata.name ∈ [:river_freshwater_flux, :iceberg_freshwater_flux] + dates = "$(year)0101-$(year)1231" + elseif metadata.name ∈ multiple_year_time_displaced_variables + dates = "$(year)01010130-$(year)12312230" else - dates = "$(year)01010130-$(year)12312330" + dates = "$(year)01010000-$(year)12312100" end + return shortname * suffix * dates * ".nc" end @@ -89,7 +100,6 @@ JRA55_variable_names = (:river_freshwater_flux, :iceberg_freshwater_flux, :specific_humidity, :sea_level_pressure, - :relative_humidity, :downwelling_longwave_radiation, :downwelling_shortwave_radiation, :temperature, @@ -103,7 +113,6 @@ JRA55_short_names = Dict( :iceberg_freshwater_flux => "licalvf", # Freshwater flux from calving icebergs :specific_humidity => "huss", # Surface specific humidity :sea_level_pressure => "psl", # Sea level pressure - :relative_humidity => "rhuss", # Surface relative humidity :downwelling_longwave_radiation => "rlds", # Downwelling longwave radiation :downwelling_shortwave_radiation => "rsds", # Downwelling shortwave radiation :temperature => "tas", # Near-surface air temperature @@ -133,9 +142,6 @@ JRA55_repeat_year_urls = Dict( :sea_level_pressure => "https://www.dropbox.com/scl/fi/0fk332027oru1iiseykgp/" * "RYF.psl.1990_1991.nc?rlkey=4xpr9uah741483aukok6d7ctt&dl=0", - :relative_humidity => "https://www.dropbox.com/scl/fi/1agwsp0lzvntuyf8bm9la/" * - "RYF.rhuss.1990_1991.nc?rlkey=8cd0vs7iy1rw58b9pc9t68gtz&dl=0", - :downwelling_longwave_radiation => "https://www.dropbox.com/scl/fi/y6r62szkirrivua5nqq61/" * "RYF.rlds.1990_1991.nc?rlkey=wt9yq3cyrvs2rbowoirf4nkum&dl=0", @@ -152,19 +158,24 @@ JRA55_repeat_year_urls = Dict( "RYF.vas.1990_1991.nc?rlkey=f9y3e57kx8xrb40gbstarf0x6&dl=0", ) -variable_is_three_dimensional(data::JRA55Metadata) = false - metadata_url(metadata::Metadata{<:Any, <:JRA55RepeatYear}) = JRA55_repeat_year_urls[metadata.name] -function url(metadata::Metadata{<:Any, <:JRA55MultipleYears}) - if metadata.name isa String - return "https://esgf-data2.llnl.gov/thredds/fileServer/user_pub_work/input4MIPs/CMIP6/OMIP/MRI/MRI-JRA55-do-1-5-0/atmos/3hrPt" +const JRA55_multiple_year_url = "https://esgf-data2.llnl.gov/thredds/fileServer/user_pub_work/input4MIPs/CMIP6/OMIP/MRI/MRI-JRA55-do-1-5-0/" + +function metadata_url(m::Metadata{<:Any, <:JRA55MultipleYears}) + + if m.name == :iceberg_freshwater_flux + url = JRA55_multiple_year_url * "landIce/day" + elseif m.name == :river_freshwater_flux + url = JRA55_multiple_year_url * "land/day" + elseif m.name ∈ multiple_year_time_displaced_variables + url = JRA55_multiple_year_url * "atmos/3hr" else - return "https://esgf-data2.llnl.gov/thredds/fileServer/user_pub_work/input4MIPs/CMIP6/OMIP/MRI/MRI-JRA55-do-1-5-0/atmos/3hrPt" + url = JRA55_multiple_year_url * "atmos/3hrPt" end -end -metadata_url(prefix, m::Metadata{<:Any, <:JRA55MultipleYears}) = url(metadata) * "/" * short_name(m) * "/gr/v20200916/" * metadata_filename(m) + return url * "/" * short_name(m) * "/gr/v20200916/" * metadata_filename(m) +end function download_dataset(metadata::JRA55Metadata) From 65ca9ba519aa75436f45fdc830f040fc339af04a Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Sat, 15 Mar 2025 11:04:40 +0100 Subject: [PATCH 046/104] this works --- src/DataWrangling/JRA55/JRA55_metadata.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_metadata.jl b/src/DataWrangling/JRA55/JRA55_metadata.jl index 44187881..7a861f05 100644 --- a/src/DataWrangling/JRA55/JRA55_metadata.jl +++ b/src/DataWrangling/JRA55/JRA55_metadata.jl @@ -40,9 +40,11 @@ end function all_dates(::JRA55MultipleYears, name) if name == :river_freshwater_flux || name == :iceberg_freshwater_flux - return DateTime(1958, 1, 1) : Day(1) : DateTime(2021, 1, 1) + return DateTime(1958, 1, 1) : Day(1) : DateTime(2021, 12, 31) + elseif name ∈ multiple_year_time_displaced_variables + return DateTime(1958, 1, 1, 1, 30) : Hour(3) : DateTime(2021, 12, 31, 22, 30) else - return DateTime(1958, 1, 1) : Hour(3) : DateTime(2021, 1, 1) + return DateTime(1958, 1, 1) : Hour(3) : DateTime(2021, 12, 31, 21) end end From 18504c514e6044135bed55daf08b59fd8c7ab4b7 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Sat, 15 Mar 2025 12:03:11 +0100 Subject: [PATCH 047/104] this should work --- .../JRA55/JRA55_field_time_series.jl | 22 +++++++++---------- src/DataWrangling/metadata.jl | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index 4698b3c8..d93e6f98 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -103,9 +103,12 @@ function set!(fts::JRA55NetCDFFTS) backend = fts.backend metadata = backend.metadata - filepath = metadata_path(metadata) - - for path in filepath + filename = metadata_filename(metadata) + filename = unique(filename) + + for name in filename + + path = joinpath(metadata.dir, name) ds = Dataset(path) # Note that each file should have the variables @@ -347,16 +350,13 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(), FT=Fl path = filepath, name = shortname) - # Fill the data in a GPU-friendly manner - copyto!(interior(fts, :, :, 1, :), data) - fill_halo_regions!(fts) - + set!(fts) return fts else - native_fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; - time_indexing, - backend, - boundary_conditions) + fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; + time_indexing, + backend, + boundary_conditions) # Fill the data in a GPU-friendly manner copyto!(interior(native_fts, :, :, 1, :), data) diff --git a/src/DataWrangling/metadata.jl b/src/DataWrangling/metadata.jl index 0de805f3..3a60b4df 100644 --- a/src/DataWrangling/metadata.jl +++ b/src/DataWrangling/metadata.jl @@ -169,10 +169,10 @@ function compute_native_date_range(native_dates, start_date, end_date) end start_idx = findfirst(x -> x ≥ start_date, native_dates) - end_idx = findfirst(x -> x ≥ end_date, native_dates) + end_idx = findfirst(x -> x ≥ end_date, native_dates) start_idx = start_idx > 1 ? start_idx - 1 : start_idx - end_idx = isnothing(end_idx) ? length(native_dates) : end_idx + end_idx = isnothing(end_idx) ? length(native_dates) : end_idx return native_dates[start_idx:end_idx] end From f35345c01ae1bf680980cbef258b74c8d8962ae2 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Sat, 15 Mar 2025 12:05:07 +0100 Subject: [PATCH 048/104] done --- src/DataWrangling/JRA55/JRA55_field_time_series.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index d93e6f98..69174aea 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -95,10 +95,12 @@ JRA55NetCDFBackend(length, metadata) = JRA55NetCDFBackend(1, length, metadata) Base.length(backend::JRA55NetCDFBackend) = backend.length Base.summary(backend::JRA55NetCDFBackend) = string("JRA55NetCDFBackend(", backend.start, ", ", backend.length, ")") -const JRA55NetCDFFTS = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend} +const JRA55NetCDFFTS = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend} +const JRA55NetCDFFTSRepeatYear = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend{<:JRA55RepeatYear}} +const JRA55NetCDFFTSMultipleYears = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend{<:JRA55NetCDFFTSMultipleYears}} # TODO: This will need to change when we add a method for JRA55MultipleYears -function set!(fts::JRA55NetCDFFTS) +function set!(fts::JRA55NetCDFFTSMultipleYears) backend = fts.backend metadata = backend.metadata From 182cbfb6eae1504803dc67009efe28a065afdf3e Mon Sep 17 00:00:00 2001 From: "Navid C. Constantinou" Date: Sun, 16 Mar 2025 13:41:57 +1100 Subject: [PATCH 049/104] use directory over folder; fix some docstrings --- src/DataWrangling/ECCO/ECCO_metadata.jl | 22 +++++----- src/DataWrangling/JRA55/JRA55_metadata.jl | 20 ++++----- src/DataWrangling/metadata.jl | 51 +++++++++++------------ 3 files changed, 46 insertions(+), 47 deletions(-) diff --git a/src/DataWrangling/ECCO/ECCO_metadata.jl b/src/DataWrangling/ECCO/ECCO_metadata.jl index f7d22dbd..127e7239 100644 --- a/src/DataWrangling/ECCO/ECCO_metadata.jl +++ b/src/DataWrangling/ECCO/ECCO_metadata.jl @@ -9,7 +9,7 @@ using Downloads import Oceananigans.Fields: set!, location import Base -import ClimaOcean.DataWrangling: all_dates, metadata_filename, default_download_folder +import ClimaOcean.DataWrangling: all_dates, metadata_filename, default_download_directory struct ECCO2Monthly end struct ECCO2Daily end @@ -18,7 +18,7 @@ struct ECCO4Monthly end const ECCOMetadata{D} = Metadata{D, <:Union{<:ECCO2Monthly, <:ECCO2Daily, <:ECCO4Monthly}} where {D} const ECCOMetadatum = ECCOMetadata{<:AnyDateTime} -default_download_folder(::Union{<:ECCO2Monthly, <:ECCO2Daily, <:ECCO4Monthly}) = download_ECCO_cache +default_download_directory(::Union{<:ECCO2Monthly, <:ECCO2Daily, <:ECCO4Monthly}) = download_ECCO_cache datasetstr(md::ECCOMetadata) = string(md.dataset) @@ -41,7 +41,7 @@ all_dates(::ECCO4Monthly, name) = DateTime(1992, 1, 1) : Month(1) : DateTime(202 all_dates(::ECCO2Monthly, name) = DateTime(1992, 1, 1) : Month(1) : DateTime(2023, 12, 1) all_dates(::ECCO2Daily, name) = DateTime(1992, 1, 4) : Day(1) : DateTime(2023, 12, 31) -# Fallback, actually, we do not really need the name for ECCO since all +# Fallback, actually, we do not really need the name for ECCO since all # variables have the same frequency and the same time-range, differently from JRA55 all_dates(dataset::Union{<:ECCO4Monthly, <:ECCO2Monthly, <:ECCO2Daily}) = all_dates(dataset, :temperature) @@ -59,7 +59,7 @@ function metadata_filename(metadata::Metadata{<:AnyDateTime, <:Union{ECCO2Daily, monthstr = string(Dates.month(metadata.dates), pad=2) postfix = variable_is_three_dimensional(metadata) ? ".1440x720x50." : ".1440x720." - if metadata.dataset isa ECCO2Monthly + if metadata.dataset isa ECCO2Monthly return shortname * postfix * yearstr * monthstr * ".nc" elseif metadata.dataset isa ECCO2Daily daystr = string(Dates.day(metadata.dates), pad=2) @@ -75,8 +75,8 @@ short_name(data::Metadata{<:Any, <:ECCO4Monthly}) = ECCO4_short_names[data.name] location(data::ECCOMetadata) = ECCO_location[data.name] variable_is_three_dimensional(data::ECCOMetadata) = - data.name == :temperature || - data.name == :salinity || + data.name == :temperature || + data.name == :salinity || data.name == :u_velocity || data.name == :v_velocity @@ -128,13 +128,13 @@ end """ download_dataset(metadata::ECCOMetadata; url = urls(metadata)) -Download the dataset specified by `ECCOMetadata`. If `ECCOMetadata.dates` is a single date, -the dataset is downloaded directly. If `ECCOMetadata.dates` is a vector of dates, each date +Download the dataset specified by the `metadata::ECCOMetadata`. If `metadata.dates` is a single date, +the dataset is downloaded directly. If `metadata.dates` is a vector of dates, each date is downloaded individually. The data download requires a username and password to be provided in the `ECCO_USERNAME` and -`ECCO_PASSWORD` environment variables. This can be done by exporting the environment variables -in the shell before running the script, or by launching julia with +`ECCO_PASSWORD` environment variables respectively. This can be done by exporting the +environment variables in the shell before running the script, or by launching julia with ``` ECCO_USERNAME=myusername ECCO_PASSWORD=mypassword julia @@ -170,7 +170,7 @@ function download_dataset(metadata::ECCOMetadata) asyncmap(metadata; ntasks) do metadatum # Distribute the download among tasks - fileurl = metadata_url(metadatum) + fileurl = metadata_url(metadatum) filepath = metadata_path(metadatum) if !isfile(filepath) diff --git a/src/DataWrangling/JRA55/JRA55_metadata.jl b/src/DataWrangling/JRA55/JRA55_metadata.jl index 038d4e0d..ab17c094 100644 --- a/src/DataWrangling/JRA55/JRA55_metadata.jl +++ b/src/DataWrangling/JRA55/JRA55_metadata.jl @@ -12,7 +12,7 @@ import Oceananigans.Fields: set! import Base import Oceananigans.Fields: set!, location -import ClimaOcean.DataWrangling: all_dates, metadata_filename, default_download_folder +import ClimaOcean.DataWrangling: all_dates, metadata_filename, default_download_directory struct JRA55MultipleYears end struct JRA55RepeatYear end @@ -20,14 +20,14 @@ struct JRA55RepeatYear end const JRA55Metadata{D} = Metadata{D, <:Union{<:JRA55MultipleYears, <:JRA55RepeatYear}} where {D} const JRA55Metadatum = JRA55Metadata{<:AnyDateTime} -default_download_folder(::Union{<:JRA55MultipleYears, <:JRA55RepeatYear}) = download_JRA55_cache +default_download_directory(::Union{<:JRA55MultipleYears, <:JRA55RepeatYear}) = download_JRA55_cache Base.size(data::JRA55Metadata) = (640, 320, length(data.dates)) Base.size(::JRA55Metadatum) = (640, 320, 1) # The whole range of dates in the different dataset datasets # NOTE! rivers and icebergs have a different frequency! (typical JRA55 data is three-hourly while rivers and icebergs are daily) -function all_dates(::JRA55RepeatYear, name) +function all_dates(::JRA55RepeatYear, name) if name == :river_freshwater_flux || name == :iceberg_freshwater_flux return DateTime(1990, 1, 1) : Day(1) : DateTime(1990, 12, 31) else @@ -43,13 +43,13 @@ function all_dates(::JRA55MultipleYears, name) end end -# Fallback, if we not provide the name, take the highest frequency +# Fallback, if we not provide the name, take the highest frequency all_dates(dataset::Union{<:JRA55MultipleYears, <:JRA55RepeatYear}) = all_dates(dataset, :temperature) function JRA55_time_indices(dataset, dates, name) all_JRA55_dates = all_dates(dataset, name) indices = Int[] - + for date in dates index = findfirst(x -> x == date, all_JRA55_dates) !isnothing(index) && push!(indices, index) @@ -59,7 +59,7 @@ function JRA55_time_indices(dataset, dates, name) end # File name generation specific to each Dataset dataset -function metadata_filename(metadata::Metadata{<:Any, <:JRA55RepeatYear}) # No difference +function metadata_filename(metadata::Metadata{<:Any, <:JRA55RepeatYear}) # No difference shortname = short_name(metadata) return "RYF." * shortname * ".1990_1991.nc" end @@ -111,7 +111,7 @@ JRA55_repeat_year_urls = Dict( :shortwave_radiation => "https://www.dropbox.com/scl/fi/z6fkvmd9oe3ycmaxta131/" * "RYF.rsds.1990_1991.nc?rlkey=r7q6zcbj6a4fxsq0f8th7c4tc&dl=0", - :river_freshwater_flux => "https://www.dropbox.com/scl/fi/21ggl4p74k4zvbf04nb67/" * + :river_freshwater_flux => "https://www.dropbox.com/scl/fi/21ggl4p74k4zvbf04nb67/" * "RYF.friver.1990_1991.nc?rlkey=ny2qcjkk1cfijmwyqxsfm68fz&dl=0", :rain_freshwater_flux => "https://www.dropbox.com/scl/fi/5icl1gbd7f5hvyn656kjq/" * @@ -150,15 +150,15 @@ JRA55_repeat_year_urls = Dict( variable_is_three_dimensional(data::JRA55Metadata) = false -metadata_url(metadata::Metadata{<:Any, <:JRA55RepeatYear}) = JRA55_repeat_year_urls[metadata.name] -# TODO: +metadata_url(metadata::Metadata{<:Any, <:JRA55RepeatYear}) = JRA55_repeat_year_urls[metadata.name] +# TODO: # metadata_url(metadata::Metadata{<:Any, <:JRA55MultipleYears}) = ... function download_dataset(metadata::JRA55Metadata) @root for metadatum in metadata - fileurl = metadata_url(metadatum) + fileurl = metadata_url(metadatum) filepath = metadata_path(metadatum) if !isfile(filepath) diff --git a/src/DataWrangling/metadata.jl b/src/DataWrangling/metadata.jl index 0de805f3..60837f4a 100644 --- a/src/DataWrangling/metadata.jl +++ b/src/DataWrangling/metadata.jl @@ -2,7 +2,7 @@ using CFTime using Dates using Base: @propagate_inbounds -struct Metadata{D, V} +struct Metadata{D, V} name :: Symbol dates :: D dataset :: V @@ -13,26 +13,27 @@ end Metadata(variable_name; dataset, dates = all_dates(dataset, variable_name), - dir = default_download_folder(dataset)) + dir = default_download_directory(dataset)) Metadata holding a specific dataset information. Arguments ========= -- `variable_name`: a symbol representing the name of the variable (for example :temperature, :salinity, :u_velocity, etc...) +- `variable_name`: a symbol representing the name of the variable (for example, `:temperature`, + `:salinity`, `:u_velocity`, etc) Keyword Arguments ================= -- `dates`: The dates of the dataset, in a `AbstractCFDateTime` format.. Note this can either be a range or a vector of dates, - representing a time-series. For a single date, use the [`Metadatum`](@ref) constructor . -- `dataset`: The dataset of the dataset. Supported datasets are `ECCO2Monthly()`, `ECCO2Daily()`, `ECCO4Monthly()`, - `JRA55RepeatYear()`, or `JRA55MultipleYears()`. +- `dataset`: The dataset of the dataset. Supported datasets are `ECCO2Monthly()`, `ECCO2Daily()`, + `ECCO4Monthly()`, `JRA55RepeatYear()`, or `JRA55MultipleYears()`. +- `dates`: The dates of the dataset, in a `AbstractCFDateTime` format. Note this can either be a range + or a vector of dates, representing a time-series. For a single date, use [`Metadatum`](@ref). - `dir`: The directory where the dataset is stored. """ function Metadata(variable_name; dataset, dates=all_dates(dataset, variable_name)[1:1], - dir=default_download_folder(dataset)) + dir=default_download_directory(dataset)) return Metadata(variable_name, dates, dataset, dir) end @@ -42,23 +43,25 @@ const Metadatum = Metadata{<:AnyDateTime} """ Metadatum(variable_name; - dataset, - date=first_date(dataset, variable_name), - dir=default_download_folder(dataset)) + dataset, + date=first_date(dataset, variable_name), + dir=default_download_directory(dataset)) -Specific constructor for a `Metadata` object with a single date, representative of a snapshot in time. +A specialized constructor for a [`Metadata`](@ref) object with a single date, representative of a snapshot in time. """ function Metadatum(variable_name; dataset, date=first_date(dataset, variable_name), - dir=default_download_folder(dataset)) + dir=default_download_directory(dataset)) + + # TODO: validate that `date` is actually a single date? return Metadata(variable_name, date, dataset, dir) end -default_download_folder(dataset) = "./" +default_download_directory(dataset) = "./" -Base.show(io::IO, metadata::Metadata) = +Base.show(io::IO, metadata::Metadata) = print(io, "ECCOMetadata:", '\n', "├── name: $(metadata.name)", '\n', "├── dates: $(metadata.dates)", '\n', @@ -94,19 +97,15 @@ Base.iterate(::Metadatum, ::Any) = nothing metadata_path(metadata) = joinpath(metadata.dir, metadata_filename(metadata)) """ - native_times(metadata; start_time = first(metadata).dates) + native_times(metadata; start_time=first(metadata).dates) -Extract the time values from the given metadata and calculates the time difference -from the start time. +Extract the time values from the given `metadata` and calculate the time difference +from the `start_time` and return as an array of time differences in seconds. Arguments ========= - `metadata`: The metadata containing the date information. - `start_time`: The start time for calculating the time difference. Defaults to the first date in the metadata. - -Returns -======= -An array of time differences in seconds. """ function native_times(metadata; start_time=first(metadata).dates) times = zeros(length(metadata)) @@ -127,8 +126,8 @@ end """ all_dates(metadata) -Extracts all the dates of the given metadata formatted using the `DateTime` type. -Needs to be extended by any new dataset dataset. +Extract all the dates of the given `metadata` formatted using the `DateTime` type. +Note: This methods needs to be extended for any new dataset. """ all_dates(metadata) = all_dates(metadata.dataset, metadata.name) @@ -149,7 +148,7 @@ last_date(dataset, variable_name) = last(all_dates(dataset, variable_name)) """ metadata_filename(metadata) -File names of metadata containing multiple dates. The specific version for a `Metadatum` object is +File names of metadata containing multiple dates. The specific version for a `Metadatum` object is extended in the data specific modules. """ metadata_filename(metadata) = [metadata_filename(metadatum) for metadatum in metadata] @@ -170,7 +169,7 @@ function compute_native_date_range(native_dates, start_date, end_date) start_idx = findfirst(x -> x ≥ start_date, native_dates) end_idx = findfirst(x -> x ≥ end_date, native_dates) - + start_idx = start_idx > 1 ? start_idx - 1 : start_idx end_idx = isnothing(end_idx) ? length(native_dates) : end_idx From f42a18780f4e91534ca8bf651fe9289f065d69a1 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Mon, 17 Mar 2025 22:17:28 +0100 Subject: [PATCH 050/104] add an ECCOMetadatum --- examples/generate_surface_fluxes.jl | 4 ++-- examples/mediterranean_simulation_with_ecco_restoring.jl | 4 ++-- examples/near_global_ocean_simulation.jl | 7 +++---- examples/single_column_os_papa_simulation.jl | 4 ++-- src/ClimaOcean.jl | 1 + src/DataWrangling/DataWrangling.jl | 2 +- src/DataWrangling/ECCO/ECCO.jl | 2 +- src/DataWrangling/ECCO/ECCO_metadata.jl | 8 ++++++++ 8 files changed, 20 insertions(+), 12 deletions(-) diff --git a/examples/generate_surface_fluxes.jl b/examples/generate_surface_fluxes.jl index 7ef0087c..9b6d8b96 100644 --- a/examples/generate_surface_fluxes.jl +++ b/examples/generate_surface_fluxes.jl @@ -52,8 +52,8 @@ ocean = ocean_simulation(grid, closure=nothing) # Now that we have an atmosphere and ocean, we `set!` the ocean temperature and salinity # to the ECCO2 data by first creating T, S metadata objects, -T_metadata = Metadatum(:temperature; date=DateTime(1993, 1, 1), dataset=ECCO4Monthly()) -S_metadata = Metadatum(:salinity; date=DateTime(1993, 1, 1), dataset=ECCO4Monthly()) +T_metadata = ECCOMetadatum(:temperature; date=DateTime(1993, 1, 1)) +S_metadata = ECCOMetadatum(:salinity; date=DateTime(1993, 1, 1)) # Note that if a date is not provided to `Metadata`, then the default Jan 1st, 1992 is used. # To copy the ECCO state into `ocean.model`, we use `set!`, diff --git a/examples/mediterranean_simulation_with_ecco_restoring.jl b/examples/mediterranean_simulation_with_ecco_restoring.jl index 66567540..c82209b4 100644 --- a/examples/mediterranean_simulation_with_ecco_restoring.jl +++ b/examples/mediterranean_simulation_with_ecco_restoring.jl @@ -90,8 +90,8 @@ ocean = ocean_simulation(grid; forcing = (T = FT, S = FS)) # In this case, our ECCO dataset has access to a temperature and a salinity # field, so we initialize temperature T and salinity S from ECCO. -set!(ocean.model, T = Metadatum(:temperature; date=start_date), - S = Metadatum(:salinity; date=start_date)) +set!(ocean.model, T = ECCOMetadatum(:temperature; date=start_date), + S = ECCOMetadatum(:salinity; date=start_date)) fig = Figure() ax = Axis(fig[1, 1]) diff --git a/examples/near_global_ocean_simulation.jl b/examples/near_global_ocean_simulation.jl index ec45a314..3a3e8144 100644 --- a/examples/near_global_ocean_simulation.jl +++ b/examples/near_global_ocean_simulation.jl @@ -81,11 +81,10 @@ ocean = ocean_simulation(grid) ocean.model -# We initialize the ocean model with ECCO2 temperature and salinity for January 1, 1993. +# We initialize the ocean model with ECCO4 temperature and salinity for January 1, 1992. -date = DateTimeProlepticGregorian(1993, 1, 1) -set!(ocean.model, T=Metadatum(:temperature; date, dataset=ECCO4Monthly()), - S=Metadatum(:salinity; date, dataset=ECCO4Monthly())) +set!(ocean.model, T=ECCOMetadatum(:temperature), + S=ECCOMetadatum(:salinity)) # ### Prescribed atmosphere and radiation # diff --git a/examples/single_column_os_papa_simulation.jl b/examples/single_column_os_papa_simulation.jl index dc19b52f..70b02900 100644 --- a/examples/single_column_os_papa_simulation.jl +++ b/examples/single_column_os_papa_simulation.jl @@ -51,8 +51,8 @@ ocean.model # We set initial conditions from ECCO: -set!(ocean.model, T=Metadatum(:temperature), - S=Metadatum(:salinity)) +set!(ocean.model, T=ECCOMetadatum(:temperature), + S=ECCOMetadatum(:salinity)) # # A prescribed atmosphere based on JRA55 re-analysis # diff --git a/src/ClimaOcean.jl b/src/ClimaOcean.jl index 7de4a473..a86f1b09 100644 --- a/src/ClimaOcean.jl +++ b/src/ClimaOcean.jl @@ -26,6 +26,7 @@ export exponential_z_faces, Metadata, Metadatum, + ECCOMetadatum, first_date, last_date, all_dates, diff --git a/src/DataWrangling/DataWrangling.jl b/src/DataWrangling/DataWrangling.jl index d7024ae4..dd66b287 100644 --- a/src/DataWrangling/DataWrangling.jl +++ b/src/DataWrangling/DataWrangling.jl @@ -1,6 +1,6 @@ module DataWrangling -export Metadata, Metadatum, all_dates, first_date, last_date +export Metadata, Metadatum, ECCOMetadatum, all_dates, first_date, last_date using Oceananigans using Downloads diff --git a/src/DataWrangling/ECCO/ECCO.jl b/src/DataWrangling/ECCO/ECCO.jl index 9437a54e..2e15720a 100644 --- a/src/DataWrangling/ECCO/ECCO.jl +++ b/src/DataWrangling/ECCO/ECCO.jl @@ -1,6 +1,6 @@ module ECCO -export ECCOMetadata, ECCO_field, ECCO_mask, ECCO_immersed_grid, adjusted_ECCO_tracers, initialize! +export ECCOMetadatum, ECCO_field, ECCO_mask, ECCO_immersed_grid, adjusted_ECCO_tracers, initialize! export ECCO2Monthly, ECCO4Monthly, ECCO2Daily export ECCOFieldTimeSeries, ECCORestoring, LinearlyTaperedPolarMask diff --git a/src/DataWrangling/ECCO/ECCO_metadata.jl b/src/DataWrangling/ECCO/ECCO_metadata.jl index 3e3583b7..a59b2df6 100644 --- a/src/DataWrangling/ECCO/ECCO_metadata.jl +++ b/src/DataWrangling/ECCO/ECCO_metadata.jl @@ -18,6 +18,14 @@ struct ECCO4Monthly end const ECCOMetadata{D} = Metadata{D, <:Union{<:ECCO2Monthly, <:ECCO2Daily, <:ECCO4Monthly}} where {D} const ECCOMetadatum = ECCOMetadata{<:AnyDateTime} +# Alias for ECCOMetadatum +function ECCOMetadatum(name; + date=first_date(ECCO4Monthly(), variable_name), + dir=download_ECCO_cache) + + return ECCOMetadatum(name, dates, dir, ECCO2Monthly()) +end + default_download_folder(::Union{<:ECCO2Monthly, <:ECCO2Daily, <:ECCO4Monthly}) = download_ECCO_cache datasetstr(md::ECCOMetadata) = string(md.dataset) From 790b5e69488ae1a3761dc3bc30a51e7e0ac21a08 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Mon, 17 Mar 2025 22:18:30 +0100 Subject: [PATCH 051/104] spacing --- src/DataWrangling/metadata.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/DataWrangling/metadata.jl b/src/DataWrangling/metadata.jl index 60837f4a..f8b7f716 100644 --- a/src/DataWrangling/metadata.jl +++ b/src/DataWrangling/metadata.jl @@ -55,7 +55,6 @@ function Metadatum(variable_name; dir=default_download_directory(dataset)) # TODO: validate that `date` is actually a single date? - return Metadata(variable_name, date, dataset, dir) end From 16493538f4c70d2f5540c8b26d6e79dc5cb87a03 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Mon, 17 Mar 2025 22:19:42 +0100 Subject: [PATCH 052/104] add a top level constant --- src/DataWrangling/ECCO/ECCO_metadata.jl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/DataWrangling/ECCO/ECCO_metadata.jl b/src/DataWrangling/ECCO/ECCO_metadata.jl index 127e7239..c61a4203 100644 --- a/src/DataWrangling/ECCO/ECCO_metadata.jl +++ b/src/DataWrangling/ECCO/ECCO_metadata.jl @@ -18,6 +18,9 @@ struct ECCO4Monthly end const ECCOMetadata{D} = Metadata{D, <:Union{<:ECCO2Monthly, <:ECCO2Daily, <:ECCO4Monthly}} where {D} const ECCOMetadatum = ECCOMetadata{<:AnyDateTime} +const ECCO2_url = "https://ecco.jpl.nasa.gov/drive/files/ECCO2/cube92_latlon_quart_90S90N/" +const ECCO4_url = "https://ecco.jpl.nasa.gov/drive/files/Version4/Release4/interp_monthly/" + default_download_directory(::Union{<:ECCO2Monthly, <:ECCO2Daily, <:ECCO4Monthly}) = download_ECCO_cache datasetstr(md::ECCOMetadata) = string(md.dataset) @@ -113,16 +116,13 @@ ECCO_location = Dict( :v_velocity => (Center, Face, Center), ) -const ECCO2_url = "https://ecco.jpl.nasa.gov/drive/files/ECCO2/cube92_latlon_quart_90S90N/" - # URLs for the ECCO datasets specific to each dataset metadata_url(m::Metadata{<:Any, <:ECCO2Daily}) = ECCO2_url * "monthly/" * short_name(m) * "/" * metadata_filename(m) metadata_url(m::Metadata{<:Any, <:ECCO2Monthly}) = ECCO2_url * "daily/" * short_name(m) * "/" * metadata_filename(m) function metadata_url(m::Metadata{<:Any, <:ECCO4Monthly}) year = string(Dates.year(m.dates)) - prefix = "https://ecco.jpl.nasa.gov/drive/files/Version4/Release4/interp_monthly/" - return prefix * short_name(m) * "/" * year * "/" * metadata_filename(m) + return ECCO4_url * short_name(m) * "/" * year * "/" * metadata_filename(m) end """ @@ -174,7 +174,6 @@ function download_dataset(metadata::ECCOMetadata) filepath = metadata_path(metadatum) if !isfile(filepath) - missing_files = true instructions_msg = "\n See ClimaOcean.jl/src/DataWrangling/ECCO/README.md for instructions." if isnothing(username) msg = "Could not find the ECCO_PASSWORD environment variable. \ From 739bc46a6d0d59ac1fbcb9899509e7c89e08c1a6 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Mon, 17 Mar 2025 22:24:07 +0100 Subject: [PATCH 053/104] correct directory --- src/DataWrangling/ECCO/ECCO_metadata.jl | 2 +- src/DataWrangling/JRA55/JRA55.jl | 6 ++++++ src/DataWrangling/JRA55/JRA55_field_time_series.jl | 6 ------ src/DataWrangling/JRA55/JRA55_metadata.jl | 2 +- src/DataWrangling/metadata.jl | 3 ++- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/DataWrangling/ECCO/ECCO_metadata.jl b/src/DataWrangling/ECCO/ECCO_metadata.jl index c61a4203..e7479ae3 100644 --- a/src/DataWrangling/ECCO/ECCO_metadata.jl +++ b/src/DataWrangling/ECCO/ECCO_metadata.jl @@ -9,7 +9,7 @@ using Downloads import Oceananigans.Fields: set!, location import Base -import ClimaOcean.DataWrangling: all_dates, metadata_filename, default_download_directory +import ClimaOcean.DataWrangling: all_dates, metadata_filename, download_dataset, default_download_directory struct ECCO2Monthly end struct ECCO2Daily end diff --git a/src/DataWrangling/JRA55/JRA55.jl b/src/DataWrangling/JRA55/JRA55.jl index b88d8e0b..48a3d365 100644 --- a/src/DataWrangling/JRA55/JRA55.jl +++ b/src/DataWrangling/JRA55/JRA55.jl @@ -30,6 +30,12 @@ import Oceananigans.Fields: set! import Oceananigans.OutputReaders: new_backend, update_field_time_series! using Downloads: download +download_JRA55_cache::String = "" + +function __init__() + global download_JRA55_cache = @get_scratch!("JRA55") +end + include("JRA55_metadata.jl") include("JRA55_field_time_series.jl") include("JRA55_prescribed_atmosphere.jl") diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index 8c928774..e1b4654b 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -3,12 +3,6 @@ using ClimaOcean.DataWrangling: compute_native_date_range using Oceananigans.Grids: AbstractGrid using Oceananigans.OutputReaders: PartlyInMemory -download_JRA55_cache::String = "" - -function __init__() - global download_JRA55_cache = @get_scratch!("JRA55") -end - compute_bounding_nodes(::Nothing, ::Nothing, LH, hnodes) = nothing compute_bounding_nodes(bounds, ::Nothing, LH, hnodes) = bounds diff --git a/src/DataWrangling/JRA55/JRA55_metadata.jl b/src/DataWrangling/JRA55/JRA55_metadata.jl index ab17c094..f381bc94 100644 --- a/src/DataWrangling/JRA55/JRA55_metadata.jl +++ b/src/DataWrangling/JRA55/JRA55_metadata.jl @@ -12,7 +12,7 @@ import Oceananigans.Fields: set! import Base import Oceananigans.Fields: set!, location -import ClimaOcean.DataWrangling: all_dates, metadata_filename, default_download_directory +import ClimaOcean.DataWrangling: all_dates, metadata_filename, download_dataset, default_download_directory struct JRA55MultipleYears end struct JRA55RepeatYear end diff --git a/src/DataWrangling/metadata.jl b/src/DataWrangling/metadata.jl index f8b7f716..fc2298f0 100644 --- a/src/DataWrangling/metadata.jl +++ b/src/DataWrangling/metadata.jl @@ -58,7 +58,8 @@ function Metadatum(variable_name; return Metadata(variable_name, date, dataset, dir) end -default_download_directory(dataset) = "./" +# Just the current directory +default_download_directory(dataset) = pwd() Base.show(io::IO, metadata::Metadata) = print(io, "ECCOMetadata:", '\n', From 6ef9d7c58e3721dae6bc9ef71b1782c93ac34e39 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Mon, 17 Mar 2025 22:25:18 +0100 Subject: [PATCH 054/104] add a default dowbload --- src/DataWrangling/metadata.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/DataWrangling/metadata.jl b/src/DataWrangling/metadata.jl index fc2298f0..8be319dd 100644 --- a/src/DataWrangling/metadata.jl +++ b/src/DataWrangling/metadata.jl @@ -61,6 +61,9 @@ end # Just the current directory default_download_directory(dataset) = pwd() +# Default download function for a metadata object, to be extended by each dataset +download_dataset(metadata) = nothing + Base.show(io::IO, metadata::Metadata) = print(io, "ECCOMetadata:", '\n', "├── name: $(metadata.name)", '\n', From 4e53f825514903bf12a255ea3ee1ea1e61da6d37 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Mon, 17 Mar 2025 22:38:21 +0100 Subject: [PATCH 055/104] almost there --- src/DataWrangling/JRA55/JRA55_metadata.jl | 66 ++++++++++++++--------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_metadata.jl b/src/DataWrangling/JRA55/JRA55_metadata.jl index 194dc841..3c5a368b 100644 --- a/src/DataWrangling/JRA55/JRA55_metadata.jl +++ b/src/DataWrangling/JRA55/JRA55_metadata.jl @@ -38,19 +38,12 @@ function all_dates(::JRA55RepeatYear, name) end end -function all_dates(::JRA55MultipleYears, name) - if name == :river_freshwater_flux || name == :iceberg_freshwater_flux - return DateTime(1958, 1, 1) : Day(1) : DateTime(2021, 12, 31) - elseif name ∈ multiple_year_time_displaced_variables - return DateTime(1958, 1, 1, 1, 30) : Hour(3) : DateTime(2021, 12, 31, 22, 30) - else - return DateTime(1958, 1, 1) : Hour(3) : DateTime(2021, 12, 31, 21) - end -end +all_dates(::JRA55MultipleYears, name) = JRA55_multiple_years_dates[name] # Fallback, if we not provide the name, take the highest frequency all_dates(dataset::Union{<:JRA55MultipleYears, <:JRA55RepeatYear}) = all_dates(dataset, :temperature) +# Valid for all JRA55 datasets function JRA55_time_indices(dataset, dates, name) all_JRA55_dates = all_dates(dataset, name) indices = Int[] @@ -79,10 +72,13 @@ function metadata_filename(metadata::Metadatum{<:Any, <:JRA55MultipleYears}) shortname = short_name(metadata) year = Dates.year(metadata.dates) suffix = "_input4MIPs_atmosphericState_OMIP_MRI-JRA55-do-1-5-0_gr_" - + if metadata.name ∈ [:river_freshwater_flux, :iceberg_freshwater_flux] dates = "$(year)0101-$(year)1231" - elseif metadata.name ∈ multiple_year_time_displaced_variables + elseif metadata.name ∈ [:rain_freshwater_flux, + :snow_freshwater_flux, + :downwelling_shortwave_radiation, + :downwelling_longwave_radiation] dates = "$(year)01010130-$(year)12312230" else dates = "$(year)01010000-$(year)12312100" @@ -93,7 +89,7 @@ end # Convenience functions short_name(data::JRA55Metadata) = JRA55_short_names[data.name] -location(::JRA55Metadata) = (Center, Center, Center) +location(::JRA55Metadata) = (Center, Center, Center) # A list of all variables provided in the JRA55 dataset: JRA55_variable_names = (:river_freshwater_flux, @@ -122,6 +118,36 @@ JRA55_short_names = Dict( :northward_velocity => "vas", # Northward near-surface wind ) +JRA55_multiple_year_url = "https://esgf-data2.llnl.gov/thredds/fileServer/user_pub_work/input4MIPs/CMIP6/OMIP/MRI/MRI-JRA55-do-1-5-0/" + +JRA55_multiple_year_prefix = Dict( + :river_freshwater_flux => "land/day", + :rain_freshwater_flux => "atmos/3hr", + :snow_freshwater_flux => "atmos/3hr", + :iceberg_freshwater_flux => "landIce/day", + :specific_humidity => "atmos/3hrPt", + :sea_level_pressure => "atmos/3hrPt", + :downwelling_longwave_radiation => "atmos/3hr", + :downwelling_shortwave_radiation => "atmos/3hr", + :temperature => "atmos/3hrPt", + :eastward_velocity => "atmos/3hrPt", + :northward_velocity => "atmos/3hrPt", +) + +JRA55_multiple_year_dates = Dict( + :river_freshwater_flux => DateTime(1958, 1, 1) : Day(1) : DateTime(2021, 12, 31), + :rain_freshwater_flux => DateTime(1958, 1, 1, 1, 30) : Hour(3) : DateTime(2021, 12, 31, 22, 30) + :snow_freshwater_flux => DateTime(1958, 1, 1, 1, 30) : Hour(3) : DateTime(2021, 12, 31, 22, 30) + :iceberg_freshwater_flux => DateTime(1958, 1, 1) : Day(1) : DateTime(2021, 12, 31), + :specific_humidity => DateTime(1958, 1, 1) : Hour(3) : DateTime(2021, 12, 31, 21), + :sea_level_pressure => DateTime(1958, 1, 1) : Hour(3) : DateTime(2021, 12, 31, 21), + :downwelling_longwave_radiation => DateTime(1958, 1, 1, 1, 30) : Hour(3) : DateTime(2021, 12, 31, 22, 30), + :downwelling_shortwave_radiation => DateTime(1958, 1, 1, 1, 30) : Hour(3) : DateTime(2021, 12, 31, 22, 30), + :temperature => DateTime(1958, 1, 1) : Hour(3) : DateTime(2021, 12, 31, 21), + :eastward_velocity => DateTime(1958, 1, 1) : Hour(3) : DateTime(2021, 12, 31, 21), + :northward_velocity => DateTime(1958, 1, 1) : Hour(3) : DateTime(2021, 12, 31, 21) +) + JRA55_repeat_year_urls = Dict( :shortwave_radiation => "https://www.dropbox.com/scl/fi/z6fkvmd9oe3ycmaxta131/" * "RYF.rsds.1990_1991.nc?rlkey=r7q6zcbj6a4fxsq0f8th7c4tc&dl=0", @@ -162,21 +188,9 @@ JRA55_repeat_year_urls = Dict( metadata_url(metadata::Metadata{<:Any, <:JRA55RepeatYear}) = JRA55_repeat_year_urls[metadata.name] -const JRA55_multiple_year_url = "https://esgf-data2.llnl.gov/thredds/fileServer/user_pub_work/input4MIPs/CMIP6/OMIP/MRI/MRI-JRA55-do-1-5-0/" - function metadata_url(m::Metadata{<:Any, <:JRA55MultipleYears}) - - if m.name == :iceberg_freshwater_flux - url = JRA55_multiple_year_url * "landIce/day" - elseif m.name == :river_freshwater_flux - url = JRA55_multiple_year_url * "land/day" - elseif m.name ∈ multiple_year_time_displaced_variables - url = JRA55_multiple_year_url * "atmos/3hr" - else - url = JRA55_multiple_year_url * "atmos/3hrPt" - end - - return url * "/" * short_name(m) * "/gr/v20200916/" * metadata_filename(m) + prefix = JRA55_multiple_year_prefix[m.name] + return url * prefix * "/" * short_name(m) * "/gr/v20200916/" * metadata_filename(m) end function download_dataset(metadata::JRA55Metadata) From 418fa995782c4b1926c4529df6fc97db53b04631 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Fri, 21 Mar 2025 18:30:35 +0100 Subject: [PATCH 056/104] revert test ecco --- test/test_ecco.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/test_ecco.jl b/test/test_ecco.jl index 6ff76364..6096cbc2 100644 --- a/test/test_ecco.jl +++ b/test/test_ecco.jl @@ -59,7 +59,7 @@ end @testset "Inpainting algorithm" begin for arch in test_architectures - T_metadatum = Metadatum(:temperature; date=start_date, dataset=ECCO4Monthly()) + T_metadatum = ECCOMetadatum(:temperature; date=start_date) grid = LatitudeLongitudeGrid(arch, size = (100, 100, 10), @@ -131,8 +131,8 @@ end field = CenterField(grid) @test begin - set!(field, Metadatum(:temperature, date=start_date, dataset=ECCO4Monthly())) - set!(field, Metadatum(:salinity, date=start_date, dataset=ECCO4Monthly())) + set!(field, ECCOMetadatum(:temperature, date=start_date)) + set!(field, ECCOMetadatum(:salinity, date=start_date)) true end end @@ -151,8 +151,8 @@ end field = CenterField(grid) @test begin - set!(field, Metadatum(:temperature, date=start_date, dataset=ECCO4Monthly())) - set!(field, Metadatum(:salinity, date=start_date, dataset=ECCO4Monthly())) + set!(field, ECCOMetadatum(:temperature, date=start_date)) + set!(field, ECCOMetadatum(:salinity, date=start_date)) true end @@ -179,8 +179,8 @@ end ocean = ocean_simulation(grid) date = DateTime(1993, 1, 1) - set!(ocean.model, T=Metadatum(:temperature; date=start_date, dataset=ECCO4Monthly()), - S=Metadatum(:salinity; date=start_date, dataset=ECCO4Monthly())) + set!(ocean.model, T=ECCOMetadatum(:temperature; date=start_date), + S=ECCOMetadatum(:salinity; date=start_date)) end end From 93c6b6f4b1b8ff86eec8af03b4b55e1b5ab17eb3 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Fri, 21 Mar 2025 19:24:23 +0100 Subject: [PATCH 057/104] bugfix --- src/DataWrangling/JRA55/JRA55_metadata.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_metadata.jl b/src/DataWrangling/JRA55/JRA55_metadata.jl index 3c5a368b..f13a7010 100644 --- a/src/DataWrangling/JRA55/JRA55_metadata.jl +++ b/src/DataWrangling/JRA55/JRA55_metadata.jl @@ -136,8 +136,8 @@ JRA55_multiple_year_prefix = Dict( JRA55_multiple_year_dates = Dict( :river_freshwater_flux => DateTime(1958, 1, 1) : Day(1) : DateTime(2021, 12, 31), - :rain_freshwater_flux => DateTime(1958, 1, 1, 1, 30) : Hour(3) : DateTime(2021, 12, 31, 22, 30) - :snow_freshwater_flux => DateTime(1958, 1, 1, 1, 30) : Hour(3) : DateTime(2021, 12, 31, 22, 30) + :rain_freshwater_flux => DateTime(1958, 1, 1, 1, 30) : Hour(3) : DateTime(2021, 12, 31, 22, 30), + :snow_freshwater_flux => DateTime(1958, 1, 1, 1, 30) : Hour(3) : DateTime(2021, 12, 31, 22, 30), :iceberg_freshwater_flux => DateTime(1958, 1, 1) : Day(1) : DateTime(2021, 12, 31), :specific_humidity => DateTime(1958, 1, 1) : Hour(3) : DateTime(2021, 12, 31, 21), :sea_level_pressure => DateTime(1958, 1, 1) : Hour(3) : DateTime(2021, 12, 31, 21), From ce7cbd32d95115e2b609ebc7662463a63493d1bc Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Mon, 31 Mar 2025 14:09:10 +0200 Subject: [PATCH 058/104] start changes --- .../JRA55/JRA55_field_time_series.jl | 67 ++++++++++++++++--- 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index 277fb160..9bb1df74 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -92,7 +92,64 @@ const JRA55NetCDFFTS = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JR const JRA55NetCDFFTSRepeatYear = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend{<:JRA55RepeatYear}} const JRA55NetCDFFTSMultipleYears = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend{<:JRA55NetCDFFTSMultipleYears}} -# TODO: This will need to change when we add a method for JRA55MultipleYears +# Note that each file should have the variables +# - ds["time"]: time coordinate +# - ds["lon"]: longitude at the location of the variable +# - ds["lat"]: latitude at the location of the variable +# - ds["lon_bnds"]: bounding longitudes between which variables are averaged +# - ds["lat_bnds"]: bounding latitudes between which variables are averaged +# - ds[shortname]: the variable data +@inline function load_JRA55_data(ds, fts) + λc = ds["lon"][:] + φc = ds["lat"][:] + LX, LY, LZ = location(fts) + i₁, i₂, j₁, j₂, TX = compute_bounding_indices(nothing, nothing, fts.grid, LX, LY, λc, φc) + + nn = time_indices(fts) + nn = collect(nn) + + if issorted(nn) + data = ds[name][i₁:i₂, j₁:j₂, nn] + else + # The time indices may be cycling past 1; eg ti = [6, 7, 8, 1]. + # However, DiskArrays does not seem to support loading data with unsorted + # indices. So to handle this, we load the data in chunks, where each chunk's + # indices are sorted, and then glue the data together. + m = findfirst(n -> n == 1, nn) + n1 = nn[1:m-1] + n2 = nn[m:end] + + data1 = ds[name][i₁:i₂, j₁:j₂, n1] + data2 = ds[name][i₁:i₂, j₁:j₂, n2] + data = cat(data1, data2, dims=3) + end + + return data +end + +# Simple case, only one file per variable, no need to deal with multiple files +function set!(fts::JRA55NetCDFFTSRepeatYear) + + backend = fts.backend + metadata = backend.metadata + + filename = metadata_filename(metadata) + path = joinpath(metadata.dir, filename) + ds = Dataset(path) + + # Nodes at the variable location + data = load_JRA55_data(ds, fts) + + close(ds) + + copyto!(interior(fts, :, :, 1, :), data) + fill_halo_regions!(fts) + + return nothing +end + +# Tricky case: multiple files per variable -- one file per year -- +# we need to infer the file name from the metadata and split the data loading function set!(fts::JRA55NetCDFFTSMultipleYears) backend = fts.backend @@ -106,14 +163,6 @@ function set!(fts::JRA55NetCDFFTSMultipleYears) path = joinpath(metadata.dir, name) ds = Dataset(path) - # Note that each file should have the variables - # - ds["time"]: time coordinate - # - ds["lon"]: longitude at the location of the variable - # - ds["lat"]: latitude at the location of the variable - # - ds["lon_bnds"]: bounding longitudes between which variables are averaged - # - ds["lat_bnds"]: bounding latitudes between which variables are averaged - # - ds[shortname]: the variable data - # Nodes at the variable location λc = ds["lon"][:] φc = ds["lat"][:] From fbfe5cee8cd97af127a21298c2fcde5cd56aff2a Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Mon, 31 Mar 2025 14:19:39 +0200 Subject: [PATCH 059/104] this should work --- .../JRA55/JRA55_field_time_series.jl | 75 +++++++++++-------- 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index 9bb1df74..24f8626d 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -99,14 +99,27 @@ const JRA55NetCDFFTSMultipleYears = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JR # - ds["lon_bnds"]: bounding longitudes between which variables are averaged # - ds["lat_bnds"]: bounding latitudes between which variables are averaged # - ds[shortname]: the variable data -@inline function load_JRA55_data(ds, fts) + +# Simple case, only one file per variable, no need to deal with multiple files +function set!(fts::JRA55NetCDFFTSRepeatYear) + + backend = fts.backend + metadata = backend.metadata + + filename = metadata_filename(metadata) + path = joinpath(metadata.dir, filename) + ds = Dataset(path) + + # Nodes at the variable location + λc = ds["lon"][:] φc = ds["lat"][:] LX, LY, LZ = location(fts) i₁, i₂, j₁, j₂, TX = compute_bounding_indices(nothing, nothing, fts.grid, LX, LY, λc, φc) - nn = time_indices(fts) - nn = collect(nn) + nn = time_indices(fts) + nn = collect(nn) + name = short_name(fts.backend.metadata) if issorted(nn) data = ds[name][i₁:i₂, j₁:j₂, nn] @@ -124,22 +137,6 @@ const JRA55NetCDFFTSMultipleYears = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JR data = cat(data1, data2, dims=3) end - return data -end - -# Simple case, only one file per variable, no need to deal with multiple files -function set!(fts::JRA55NetCDFFTSRepeatYear) - - backend = fts.backend - metadata = backend.metadata - - filename = metadata_filename(metadata) - path = joinpath(metadata.dir, filename) - ds = Dataset(path) - - # Nodes at the variable location - data = load_JRA55_data(ds, fts) - close(ds) copyto!(interior(fts, :, :, 1, :), data) @@ -155,23 +152,38 @@ function set!(fts::JRA55NetCDFFTSMultipleYears) backend = fts.backend metadata = backend.metadata - filename = metadata_filename(metadata) - filename = unique(filename) + filename = metadata_filename(metadata) + filename = unique(filename) + name = short_name(metadata) + start_date = first_date(metadata, name) - for name in filename + for file in filename - path = joinpath(metadata.dir, name) + path = joinpath(metadata.dir, file) ds = Dataset(path) + # This can be simplified once we start supporting a + # datetime `Clock` in Oceananigans + file_dates = ds["times"][:] + file_times = zeros(length(file_dates)) + for (t, date) in enumerate(file_dates) + delta = date - start_date + delta = Second(delta).value + file_times[t] = delta + end + + ftsn = time_indices(fts) + ftsn = collect(ftsn) + + # Intersect the time indices with the file times + nn = findall(n -> fts.times[n] ∈ file_times, ftsn) + # Nodes at the variable location λc = ds["lon"][:] φc = ds["lat"][:] LX, LY, LZ = location(fts) i₁, i₂, j₁, j₂, TX = compute_bounding_indices(nothing, nothing, fts.grid, LX, LY, λc, φc) - nn = time_indices(fts) - nn = collect(nn) - if issorted(nn) data = ds[name][i₁:i₂, j₁:j₂, nn] else @@ -189,11 +201,14 @@ function set!(fts::JRA55NetCDFFTSMultipleYears) end close(ds) - - copyto!(interior(fts, :, :, 1, :), data) - fill_halo_regions!(fts) + for n in 1:length(ftsn) + # We need to set the time index for each file + copyto!(interior(fts, :, :, 1, n), data[:, :, n]) + end end - + + fill_halo_regions!(fts) + return nothing end From 46940e7b9a70be0450baf77d6741f79e961ca6ee Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Mon, 31 Mar 2025 14:30:37 +0200 Subject: [PATCH 060/104] bugfix --- src/DataWrangling/JRA55/JRA55_field_time_series.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index 24f8626d..4650b8da 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -90,7 +90,7 @@ Base.summary(backend::JRA55NetCDFBackend) = string("JRA55NetCDFBackend(", backen const JRA55NetCDFFTS = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend} const JRA55NetCDFFTSRepeatYear = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend{<:JRA55RepeatYear}} -const JRA55NetCDFFTSMultipleYears = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend{<:JRA55NetCDFFTSMultipleYears}} +const JRA55NetCDFFTSMultipleYears = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend{<:JRA55MultipleYears}} # Note that each file should have the variables # - ds["time"]: time coordinate From 473dbd7bb065e42e42c9f2cfae9c9c64c3255ebb Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Mon, 31 Mar 2025 14:36:10 +0200 Subject: [PATCH 061/104] some bugfixing --- src/DataWrangling/JRA55/JRA55_field_time_series.jl | 3 ++- src/DataWrangling/JRA55/JRA55_metadata.jl | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index 4650b8da..4b175f90 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -201,8 +201,9 @@ function set!(fts::JRA55NetCDFFTSMultipleYears) end close(ds) + + # We need to set the time index for each file for n in 1:length(ftsn) - # We need to set the time index for each file copyto!(interior(fts, :, :, 1, n), data[:, :, n]) end end diff --git a/src/DataWrangling/JRA55/JRA55_metadata.jl b/src/DataWrangling/JRA55/JRA55_metadata.jl index f13a7010..17746109 100644 --- a/src/DataWrangling/JRA55/JRA55_metadata.jl +++ b/src/DataWrangling/JRA55/JRA55_metadata.jl @@ -38,7 +38,7 @@ function all_dates(::JRA55RepeatYear, name) end end -all_dates(::JRA55MultipleYears, name) = JRA55_multiple_years_dates[name] +all_dates(::JRA55MultipleYears, name) = JRA55_multiple_year_dates[name] # Fallback, if we not provide the name, take the highest frequency all_dates(dataset::Union{<:JRA55MultipleYears, <:JRA55RepeatYear}) = all_dates(dataset, :temperature) @@ -190,7 +190,7 @@ metadata_url(metadata::Metadata{<:Any, <:JRA55RepeatYear}) = JRA55_repeat_year_u function metadata_url(m::Metadata{<:Any, <:JRA55MultipleYears}) prefix = JRA55_multiple_year_prefix[m.name] - return url * prefix * "/" * short_name(m) * "/gr/v20200916/" * metadata_filename(m) + return JRA55_multiple_year_url * prefix * "/" * short_name(m) * "/gr/v20200916/" * metadata_filename(m) end function download_dataset(metadata::JRA55Metadata) From 3ccc9f7ce07f7da3175da624c407c1ea80298a86 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Mon, 31 Mar 2025 15:29:44 +0200 Subject: [PATCH 062/104] this works --- src/DataWrangling/ECCO/ECCO_metadata.jl | 30 +++---- .../JRA55/JRA55_field_time_series.jl | 82 ++++++++++--------- src/DataWrangling/JRA55/JRA55_metadata.jl | 10 +-- src/DataWrangling/metadata.jl | 7 +- 4 files changed, 67 insertions(+), 62 deletions(-) diff --git a/src/DataWrangling/ECCO/ECCO_metadata.jl b/src/DataWrangling/ECCO/ECCO_metadata.jl index 0edae311..1bc63b56 100644 --- a/src/DataWrangling/ECCO/ECCO_metadata.jl +++ b/src/DataWrangling/ECCO/ECCO_metadata.jl @@ -15,7 +15,7 @@ struct ECCO2Monthly end struct ECCO2Daily end struct ECCO4Monthly end -const ECCOMetadata{D} = Metadata{D, <:Union{<:ECCO2Monthly, <:ECCO2Daily, <:ECCO4Monthly}} where {D} +const ECCOMetadata{D} = Metadata{<:Union{<:ECCO2Monthly, <:ECCO2Daily, <:ECCO4Monthly}, D} where {D} const ECCOMetadatum = ECCOMetadata{<:AnyDateTime} const ECCO2_url = "https://ecco.jpl.nasa.gov/drive/files/ECCO2/cube92_latlon_quart_90S90N/" @@ -45,13 +45,13 @@ datestr(md::ECCOMetadatum) = string(md.dates) Base.summary(md::ECCOMetadata) = string("ECCOMetadata{", datasetstr(md), "} of ", md.name, " for ", datestr(md)) -Base.size(data::Metadata{<:Any, <:ECCO2Daily}) = (1440, 720, 50, length(data.dates)) -Base.size(data::Metadata{<:Any, <:ECCO2Monthly}) = (1440, 720, 50, length(data.dates)) -Base.size(data::Metadata{<:Any, <:ECCO4Monthly}) = (720, 360, 50, length(data.dates)) +Base.size(data::Metadata{<:ECCO2Daily}) = (1440, 720, 50, length(data.dates)) +Base.size(data::Metadata{<:ECCO2Monthly}) = (1440, 720, 50, length(data.dates)) +Base.size(data::Metadata{<:ECCO4Monthly}) = (720, 360, 50, length(data.dates)) -Base.size(::Metadata{<:AnyDateTime, <:ECCO2Daily}) = (1440, 720, 50, 1) -Base.size(::Metadata{<:AnyDateTime, <:ECCO2Monthly}) = (1440, 720, 50, 1) -Base.size(::Metadata{<:AnyDateTime, <:ECCO4Monthly}) = (720, 360, 50, 1) +Base.size(::Metadatum{<:ECCO2Daily}) = (1440, 720, 50, 1) +Base.size(::Metadatum{<:ECCO2Monthly}) = (1440, 720, 50, 1) +Base.size(::Metadatum{<:ECCO4Monthly}) = (720, 360, 50, 1) # The whole range of dates in the different dataset datasets all_dates(::ECCO4Monthly, name) = DateTime(1992, 1, 1) : Month(1) : DateTime(2023, 12, 1) @@ -63,14 +63,14 @@ all_dates(::ECCO2Daily, name) = DateTime(1992, 1, 4) : Day(1) : DateTime(202 all_dates(dataset::Union{<:ECCO4Monthly, <:ECCO2Monthly, <:ECCO2Daily}) = all_dates(dataset, :temperature) # File name generation specific to each Dataset dataset -function metadata_filename(metadata::Metadata{<:AnyDateTime, <:ECCO4Monthly}) +function metadata_filename(metadata::Metadatum{<:ECCO4Monthly}) shortname = short_name(metadata) yearstr = string(Dates.year(metadata.dates)) monthstr = string(Dates.month(metadata.dates), pad=2) return shortname * "_" * yearstr * "_" * monthstr * ".nc" end -function metadata_filename(metadata::Metadata{<:AnyDateTime, <:Union{ECCO2Daily, ECCO2Monthly}}) +function metadata_filename(metadata::Metadatum{<:Union{ECCO2Daily, ECCO2Monthly}}) shortname = short_name(metadata) yearstr = string(Dates.year(metadata.dates)) monthstr = string(Dates.month(metadata.dates), pad=2) @@ -85,9 +85,9 @@ function metadata_filename(metadata::Metadata{<:AnyDateTime, <:Union{ECCO2Daily, end # Convenience functions -short_name(data::Metadata{<:Any, <:ECCO2Daily}) = ECCO2_short_names[data.name] -short_name(data::Metadata{<:Any, <:ECCO2Monthly}) = ECCO2_short_names[data.name] -short_name(data::Metadata{<:Any, <:ECCO4Monthly}) = ECCO4_short_names[data.name] +short_name(data::Metadata{<:ECCO2Daily}) = ECCO2_short_names[data.name] +short_name(data::Metadata{<:ECCO2Monthly}) = ECCO2_short_names[data.name] +short_name(data::Metadata{<:ECCO4Monthly}) = ECCO4_short_names[data.name] location(data::ECCOMetadata) = ECCO_location[data.name] @@ -131,10 +131,10 @@ ECCO_location = Dict( ) # URLs for the ECCO datasets specific to each dataset -metadata_url(m::Metadata{<:Any, <:ECCO2Daily}) = ECCO2_url * "monthly/" * short_name(m) * "/" * metadata_filename(m) -metadata_url(m::Metadata{<:Any, <:ECCO2Monthly}) = ECCO2_url * "daily/" * short_name(m) * "/" * metadata_filename(m) +metadata_url(m::Metadata{<:ECCO2Daily}) = ECCO2_url * "monthly/" * short_name(m) * "/" * metadata_filename(m) +metadata_url(m::Metadata{<:ECCO2Monthly}) = ECCO2_url * "daily/" * short_name(m) * "/" * metadata_filename(m) -function metadata_url(m::Metadata{<:Any, <:ECCO4Monthly}) +function metadata_url(m::Metadata{<:ECCO4Monthly}) year = string(Dates.year(m.dates)) return ECCO4_url * short_name(m) * "/" * year * "/" * metadata_filename(m) end diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index 4b175f90..5667cf77 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -73,7 +73,7 @@ end struct JRA55NetCDFBackend{M} <: AbstractInMemoryBackend{Int} start :: Int length :: Int - metadata :: M + metadata :: Metadata{M} end Adapt.adapt_structure(to, b::JRA55NetCDFBackend) = JRA55NetCDFBackend(b.start, b.length, nothing) @@ -155,7 +155,7 @@ function set!(fts::JRA55NetCDFFTSMultipleYears) filename = metadata_filename(metadata) filename = unique(filename) name = short_name(metadata) - start_date = first_date(metadata, name) + start_date = first_date(metadata.dataset, metadata.name) for file in filename @@ -164,7 +164,8 @@ function set!(fts::JRA55NetCDFFTSMultipleYears) # This can be simplified once we start supporting a # datetime `Clock` in Oceananigans - file_dates = ds["times"][:] + file_dates = ds["time"][:] + file_indices = 1:length(file_dates) file_times = zeros(length(file_dates)) for (t, date) in enumerate(file_dates) delta = date - start_date @@ -176,35 +177,37 @@ function set!(fts::JRA55NetCDFFTSMultipleYears) ftsn = collect(ftsn) # Intersect the time indices with the file times - nn = findall(n -> fts.times[n] ∈ file_times, ftsn) - - # Nodes at the variable location - λc = ds["lon"][:] - φc = ds["lat"][:] - LX, LY, LZ = location(fts) - i₁, i₂, j₁, j₂, TX = compute_bounding_indices(nothing, nothing, fts.grid, LX, LY, λc, φc) - - if issorted(nn) - data = ds[name][i₁:i₂, j₁:j₂, nn] - else - # The time indices may be cycling past 1; eg ti = [6, 7, 8, 1]. - # However, DiskArrays does not seem to support loading data with unsorted - # indices. So to handle this, we load the data in chunks, where each chunk's - # indices are sorted, and then glue the data together. - m = findfirst(n -> n == 1, nn) - n1 = nn[1:m-1] - n2 = nn[m:end] - - data1 = ds[name][i₁:i₂, j₁:j₂, n1] - data2 = ds[name][i₁:i₂, j₁:j₂, n2] - data = cat(data1, data2, dims=3) - end - - close(ds) - - # We need to set the time index for each file - for n in 1:length(ftsn) - copyto!(interior(fts, :, :, 1, n), data[:, :, n]) + nn = findall(n -> file_times[n] ∈ fts.times[ftsn], file_indices) + + if !isempty(nn) + # Nodes at the variable location + λc = ds["lon"][:] + φc = ds["lat"][:] + LX, LY, LZ = location(fts) + i₁, i₂, j₁, j₂, TX = compute_bounding_indices(nothing, nothing, fts.grid, LX, LY, λc, φc) + + if issorted(nn) + data = ds[name][i₁:i₂, j₁:j₂, nn] + else + # The time indices may be cycling past 1; eg ti = [6, 7, 8, 1]. + # However, DiskArrays does not seem to support loading data with unsorted + # indices. So to handle this, we load the data in chunks, where each chunk's + # indices are sorted, and then glue the data together. + m = findfirst(n -> n == 1, nn) + n1 = nn[1:m-1] + n2 = nn[m:end] + + data1 = ds[name][i₁:i₂, j₁:j₂, n1] + data2 = ds[name][i₁:i₂, j₁:j₂, n2] + data = cat(data1, data2, dims=3) + end + + close(ds) + + # We need to set the time index for each file + for n in 1:length(ftsn) + copyto!(interior(fts, :, :, 1, n), data[:, :, n]) + end end end @@ -292,7 +295,7 @@ end function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(), FT=Float32; latitude = nothing, longitude = nothing, - backend = InMemory(), + backend = JRA55NetCDFBackend(10, metadata), time_indexing = Cyclical()) # First thing: we download the dataset! @@ -369,20 +372,20 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(), FT=Fl φc = ds["lat"][:] # Interfaces for the "native" JRA55 grid - λn = ds["lon_bnds"][1, :] - φn = ds["lat_bnds"][1, :] + λn = Array(ds["lon_bnds"][1, :]) + φn = Array(ds["lat_bnds"][1, :]) # The .nc coordinates lon_bnds and lat_bnds do not include # the last interface, so we push them here. - push!(φn, 90) + push!(φn, 90.0) push!(λn, λn[1] + 360) i₁, i₂, j₁, j₂, TX = compute_bounding_indices(longitude, latitude, nothing, Center, Center, λc, φc) - data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] λr = λn[i₁:i₂+1] φr = φn[j₁:j₂+1] - Nrx, Nry, Nt = size(data) + Nrx = length(λr) - 1 + Nry = length(φr) - 1 close(ds) N = (Nrx, Nry) @@ -396,7 +399,8 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(), FT=Fl topology = (TX, Bounded, Flat)) boundary_conditions = FieldBoundaryConditions(JRA55_native_grid, (Center, Center, Nothing)) - times = native_times(metadata) + start_time = first_date(metadata.dataset, metadata.name) + times = native_times(metadata; start_time) if backend isa JRA55NetCDFBackend fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; diff --git a/src/DataWrangling/JRA55/JRA55_metadata.jl b/src/DataWrangling/JRA55/JRA55_metadata.jl index 17746109..54d366d6 100644 --- a/src/DataWrangling/JRA55/JRA55_metadata.jl +++ b/src/DataWrangling/JRA55/JRA55_metadata.jl @@ -17,7 +17,7 @@ import ClimaOcean.DataWrangling: all_dates, metadata_filename, download_dataset, struct JRA55MultipleYears end struct JRA55RepeatYear end -const JRA55Metadata{D} = Metadata{D, <:Union{<:JRA55MultipleYears, <:JRA55RepeatYear}} where {D} +const JRA55Metadata{D} = Metadata{<:Union{<:JRA55MultipleYears, <:JRA55RepeatYear}, D} where {D} const JRA55Metadatum = JRA55Metadata{<:AnyDateTime} default_download_directory(::Union{<:JRA55MultipleYears, <:JRA55RepeatYear}) = download_JRA55_cache @@ -57,7 +57,7 @@ function JRA55_time_indices(dataset, dates, name) end # File name generation specific to each Dataset dataset -function metadata_filename(metadata::Metadatum{<:Any, <:JRA55RepeatYear}) # No difference +function metadata_filename(metadata::Metadatum{<:JRA55RepeatYear}) # No difference shortname = short_name(metadata) return "RYF." * shortname * ".1990_1991.nc" end @@ -67,7 +67,7 @@ multiple_year_time_displaced_variables = [:rain_freshwater_flux, :downwelling_shortwave_radiation, :downwelling_longwave_radiation] -function metadata_filename(metadata::Metadatum{<:Any, <:JRA55MultipleYears}) +function metadata_filename(metadata::Metadatum{<:JRA55MultipleYears}) # fix the filename shortname = short_name(metadata) year = Dates.year(metadata.dates) @@ -186,9 +186,9 @@ JRA55_repeat_year_urls = Dict( "RYF.vas.1990_1991.nc?rlkey=f9y3e57kx8xrb40gbstarf0x6&dl=0", ) -metadata_url(metadata::Metadata{<:Any, <:JRA55RepeatYear}) = JRA55_repeat_year_urls[metadata.name] +metadata_url(metadata::Metadata{<:JRA55RepeatYear}) = JRA55_repeat_year_urls[metadata.name] -function metadata_url(m::Metadata{<:Any, <:JRA55MultipleYears}) +function metadata_url(m::Metadata{<:JRA55MultipleYears}) prefix = JRA55_multiple_year_prefix[m.name] return JRA55_multiple_year_url * prefix * "/" * short_name(m) * "/gr/v20200916/" * metadata_filename(m) end diff --git a/src/DataWrangling/metadata.jl b/src/DataWrangling/metadata.jl index 3d715b7f..c4554076 100644 --- a/src/DataWrangling/metadata.jl +++ b/src/DataWrangling/metadata.jl @@ -2,7 +2,7 @@ using CFTime using Dates using Base: @propagate_inbounds -struct Metadata{D, V} +struct Metadata{V, D} name :: Symbol dates :: D dataset :: V @@ -39,7 +39,7 @@ function Metadata(variable_name; end const AnyDateTime = Union{AbstractCFDateTime, Dates.AbstractDateTime} -const Metadatum = Metadata{<:AnyDateTime} +const Metadatum = Metadata{<:Any, <:AnyDateTime} """ Metadatum(variable_name; @@ -97,7 +97,8 @@ Base.last(metadata::Metadatum) = metadata Base.iterate(metadata::Metadatum) = (metadata, nothing) Base.iterate(::Metadatum, ::Any) = nothing -metadata_path(metadata) = joinpath(metadata.dir, metadata_filename(metadata)) +metadata_path(metadata::Metadatum) = joinpath(metadata.dir, metadata_filename(metadata)) +metadata_path(metadata) = [metadata_path(metadatum) for metadatum in metadata] """ native_times(metadata; start_time=first(metadata).dates) From a35bf048a3212985c19cd50bc9fb8596d7dea7f3 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Mon, 31 Mar 2025 16:00:17 +0200 Subject: [PATCH 063/104] this works! --- src/DataWrangling/JRA55/JRA55_field_time_series.jl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index 5667cf77..92ee550f 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -177,7 +177,8 @@ function set!(fts::JRA55NetCDFFTSMultipleYears) ftsn = collect(ftsn) # Intersect the time indices with the file times - nn = findall(n -> file_times[n] ∈ fts.times[ftsn], file_indices) + nn = findall(n -> file_times[n] ∈ fts.times[ftsn], file_indices) + ftsn = findall(n -> fts.times[n] ∈ file_times[nn], ftsn) if !isempty(nn) # Nodes at the variable location @@ -186,6 +187,7 @@ function set!(fts::JRA55NetCDFFTSMultipleYears) LX, LY, LZ = location(fts) i₁, i₂, j₁, j₂, TX = compute_bounding_indices(nothing, nothing, fts.grid, LX, LY, λc, φc) + if issorted(nn) data = ds[name][i₁:i₂, j₁:j₂, nn] else @@ -205,8 +207,9 @@ function set!(fts::JRA55NetCDFFTSMultipleYears) close(ds) # We need to set the time index for each file - for n in 1:length(ftsn) - copyto!(interior(fts, :, :, 1, n), data[:, :, n]) + # Find start index corresponding to the underlying data + for n in 1:length(nn) + copyto!(interior(fts, :, :, 1, ftsn[n]), data[:, :, n]) end end end From 68b3ff325bc8233943b1b4d912aec7b907a5868b Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Mon, 31 Mar 2025 16:04:51 +0200 Subject: [PATCH 064/104] add regularization --- src/DataWrangling/JRA55/JRA55_field_time_series.jl | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index 92ee550f..0a97e4a6 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -73,7 +73,7 @@ end struct JRA55NetCDFBackend{M} <: AbstractInMemoryBackend{Int} start :: Int length :: Int - metadata :: Metadata{M} + metadata :: M end Adapt.adapt_structure(to, b::JRA55NetCDFBackend) = JRA55NetCDFBackend(b.start, b.length, nothing) @@ -85,12 +85,15 @@ Represents a JRA55 FieldTimeSeries backed by JRA55 native .nc files. """ JRA55NetCDFBackend(length, metadata) = JRA55NetCDFBackend(1, length, metadata) +# Metadata - agnostic constructor +JRA55NetCDFBackend(length) = JRA55NetCDFBackend(length, nothing) + Base.length(backend::JRA55NetCDFBackend) = backend.length Base.summary(backend::JRA55NetCDFBackend) = string("JRA55NetCDFBackend(", backend.start, ", ", backend.length, ")") const JRA55NetCDFFTS = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend} -const JRA55NetCDFFTSRepeatYear = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend{<:JRA55RepeatYear}} -const JRA55NetCDFFTSMultipleYears = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend{<:JRA55MultipleYears}} +const JRA55NetCDFFTSRepeatYear = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend{<:Metadata{<:JRA55RepeatYear}}} +const JRA55NetCDFFTSMultipleYears = FlavorOfFTS{<:Any, <:Any, <:Any, <:Any, <:JRA55NetCDFBackend{<:Metadata{<:JRA55MultipleYears}}} # Note that each file should have the variables # - ds["time"]: time coordinate @@ -304,6 +307,11 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(), FT=Fl # First thing: we download the dataset! download_dataset(metadata) + # Regularize the backend in case of `JRA55NetCDFBackend` + if backend isa JRA55NetCDFBackend && backend.metadata isa Nothing + backend = JRA55NetCDFBackend(backend.length, metadata) + end + # Unpack metadata details dataset = metadata.dataset name = metadata.name From 5f532198c7ba361f37e1e93e931ec96f631877bd Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Mon, 31 Mar 2025 16:18:34 +0200 Subject: [PATCH 065/104] bugfix --- src/DataWrangling/JRA55/JRA55_metadata.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/DataWrangling/JRA55/JRA55_metadata.jl b/src/DataWrangling/JRA55/JRA55_metadata.jl index 54d366d6..51d88ec1 100644 --- a/src/DataWrangling/JRA55/JRA55_metadata.jl +++ b/src/DataWrangling/JRA55/JRA55_metadata.jl @@ -57,7 +57,9 @@ function JRA55_time_indices(dataset, dates, name) end # File name generation specific to each Dataset dataset -function metadata_filename(metadata::Metadatum{<:JRA55RepeatYear}) # No difference +# Note that `JRA55RepeatYear` has only one file associated, so we can define +# the filename directly for the whole `Metadata` object, independent of the `dates` +function metadata_filename(metadata::Metadata{<:JRA55RepeatYear}) # No difference shortname = short_name(metadata) return "RYF." * shortname * ".1990_1991.nc" end From 9209bbf561d0051f2ffbed5bb57a7fe60ab33033 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 1 Apr 2025 09:10:46 +0200 Subject: [PATCH 066/104] add sea-ice ocean stress --- .../assemble_net_fluxes.jl | 26 ++++---- .../component_interfaces.jl | 6 +- .../sea_ice_ocean_fluxes.jl | 59 ++++++++++++++----- 3 files changed, 63 insertions(+), 28 deletions(-) diff --git a/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl b/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl index 20d3c5d3..aa045fb5 100644 --- a/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl +++ b/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl @@ -76,8 +76,10 @@ end i, j = @index(Global, NTuple) kᴺ = size(grid, 3) time = Time(clock.time) - ρτx = atmos_ocean_fluxes.x_momentum # zonal momentum flux - ρτy = atmos_ocean_fluxes.y_momentum # meridional momentum flux + ρτxao = atmos_ocean_fluxes.x_momentum # atmosphere - ocean zonal momentum flux + ρτyao = atmos_ocean_fluxes.y_momentum # atmosphere - ocean meridional momentum flux + ρτxio = sea_ice_ocean_fluxes.x_momentum # sea_ice - ocean zonal momentum flux + ρτyio = sea_ice_ocean_fluxes.y_momentum # sea_ice - ocean meridional momentum flux @inbounds begin Sₒ = ocean_salinity[i, j, kᴺ] @@ -121,20 +123,22 @@ end ρₒ⁻¹ = 1 / ocean_properties.reference_density cₒ = ocean_properties.heat_capacity - τxao = ℑxᶠᵃᵃ(i, j, 1, grid, τᶜᶜᶜ, ρₒ⁻¹, ℵ, ρτx) - τyao = ℑyᵃᶠᵃ(i, j, 1, grid, τᶜᶜᶜ, ρₒ⁻¹, ℵ, ρτy) - Jᵀao = ΣQao * ρₒ⁻¹ / cₒ - Jˢao = - Sₒ * ΣFao - - ρₒ⁻¹ = 1 / ocean_properties.reference_density - cₒ = ocean_properties.heat_capacity - @inbounds begin ℵᵢ = ℵ[i, j, 1] Qio = sea_ice_ocean_fluxes.interface_heat[i, j, 1] - Jˢio = sea_ice_ocean_fluxes.salt[i, j, 1] + + Jᵀao = ΣQao * ρₒ⁻¹ / cₒ + Jˢao = - Sₒ * ΣFao Jᵀio = Qio * ρₒ⁻¹ / cₒ + Jˢio = sea_ice_ocean_fluxes.salt[i, j, 1] + + τxao = ℑxᶠᵃᵃ(i, j, 1, grid, τᶜᶜᶜ, ρₒ⁻¹, ℵ, ρτxao) + τyao = ℑyᵃᶠᵃ(i, j, 1, grid, τᶜᶜᶜ, ρₒ⁻¹, ℵ, ρτyao) + τxio = ρτxio[i, j, 1] * ρₒ⁻¹ * ℑxᶠᵃᵃ(i, j, 1, grid, ℵ) + τyio = ρτyio[i, j, 1] * ρₒ⁻¹ * ℑyᵃᶠᵃ(i, j, 1, grid, ℵ) + τx[i, j, 1] = τxao + τxio + τy[i, j, 1] = τyao + τyio τx[i, j, 1] = τxao τy[i, j, 1] = τyao Jᵀ[i, j, 1] = (1 - ℵᵢ) * Jᵀao + Jᵀio diff --git a/src/OceanSeaIceModels/InterfaceComputations/component_interfaces.jl b/src/OceanSeaIceModels/InterfaceComputations/component_interfaces.jl index ce4ae92f..e8097784 100644 --- a/src/OceanSeaIceModels/InterfaceComputations/component_interfaces.jl +++ b/src/OceanSeaIceModels/InterfaceComputations/component_interfaces.jl @@ -247,6 +247,8 @@ function sea_ice_ocean_interface(sea_ice::SeaIceSimulation, ocean; io_bottom_heat_flux = Field{Center, Center, Nothing}(ocean.model.grid) io_frazil_heat_flux = Field{Center, Center, Nothing}(ocean.model.grid) io_salt_flux = Field{Center, Center, Nothing}(ocean.model.grid) + x_momentum = Field{Face, Center, Nothing}(ocean.model.grid) + y_momentum = Field{Center, Face, Nothing}(ocean.model.grid) @assert io_frazil_heat_flux isa Field{Center, Center, Nothing} @assert io_bottom_heat_flux isa Field{Center, Center, Nothing} @@ -254,7 +256,9 @@ function sea_ice_ocean_interface(sea_ice::SeaIceSimulation, ocean; io_fluxes = (interface_heat=io_bottom_heat_flux, frazil_heat=io_frazil_heat_flux, - salt=io_salt_flux) + salt=io_salt_flux, + x_momentum=x_momentum, + y_momentum=y_momentum) io_properties = (; characteristic_melting_speed) diff --git a/src/OceanSeaIceModels/InterfaceComputations/sea_ice_ocean_fluxes.jl b/src/OceanSeaIceModels/InterfaceComputations/sea_ice_ocean_fluxes.jl index 0ef926c9..98d15607 100644 --- a/src/OceanSeaIceModels/InterfaceComputations/sea_ice_ocean_fluxes.jl +++ b/src/OceanSeaIceModels/InterfaceComputations/sea_ice_ocean_fluxes.jl @@ -1,5 +1,6 @@ using Oceananigans.Operators: Δzᶜᶜᶜ using ClimaSeaIce.SeaIceThermodynamics: melting_temperature +using ClimaSeaIce.SeaIceMomentumEquations: x_momentum_stress, y_momentum_stress function compute_sea_ice_ocean_fluxes!(coupled_model) ocean = coupled_model.ocean @@ -21,43 +22,61 @@ function compute_sea_ice_ocean_fluxes!(coupled_model) grid = ocean.model.grid arch = architecture(grid) + uᵢ, vᵢ = sea_ice.model.velocities + dynamics = sea_ice.model.dynamics + + τs = if isnothing(dynamics) + nothing + else + dynamics.external_momentum_stresses.bottom + end + # What about the latent heat removed from the ocean when ice forms? # Is it immediately removed from the ocean? Or is it stored in the ice? - launch!(arch, grid, :xy, _compute_sea_ice_ocean_latent_heat_flux!, - sea_ice_ocean_fluxes, grid, hᵢ, h⁻, ℵᵢ, Sᵢ, Tₒ, Sₒ, liquidus, ocean_properties, interface_properties, Δt) + launch!(arch, grid, :xy, _compute_sea_ice_ocean_fluxes!, + sea_ice_ocean_fluxes, grid, clock, hᵢ, h⁻, ℵᵢ, Sᵢ, Tₒ, Sₒ, uᵢ, vᵢ, + τs, liquidus, ocean_properties, interface_properties, Δt) return nothing end -@kernel function _compute_sea_ice_ocean_latent_heat_flux!(sea_ice_ocean_fluxes, - grid, - ice_thickness, - previous_ice_thickness, - ice_concentration, - ice_salinity, - ocean_temperature, - ocean_salinity, - liquidus, - ocean_properties, - interface_properties, - Δt) - +@kernel function _compute_sea_ice_ocean_fluxes!(sea_ice_ocean_fluxes, + grid, + clock, + ice_thickness, + previous_ice_thickness, + ice_concentration, + ice_salinity, + ocean_temperature, + ocean_salinity, + sea_ice_u_velocity, + sea_ice_v_velocity, + sea_ice_ocean_stresses, + liquidus, + ocean_properties, + interface_properties, + Δt) + i, j = @index(Global, NTuple) Nz = size(grid, 3) Qᶠₒ = sea_ice_ocean_fluxes.frazil_heat Qᵢₒ = sea_ice_ocean_fluxes.interface_heat Jˢ = sea_ice_ocean_fluxes.salt + τx = sea_ice_ocean_fluxes.x_momentum + τy = sea_ice_ocean_fluxes.y_momentum + uᵢ = sea_ice_u_velocity + vᵢ = sea_ice_v_velocity Tₒ = ocean_temperature Sₒ = ocean_salinity Sᵢ = ice_salinity hᵢ = ice_thickness + ℵᵢ = ice_concentration h⁻ = previous_ice_thickness ρₒ = ocean_properties.reference_density cₒ = ocean_properties.heat_capacity uₘ★ = interface_properties.characteristic_melting_speed - ℵ = @inbounds ice_concentration[i, j, 1] δQ_frazil = zero(grid) for k = Nz:-1:1 @@ -94,6 +113,7 @@ end @inbounds begin Tᴺ = Tₒ[i, j, Nz] Sᴺ = Sₒ[i, j, Nz] + ℵ = ℵᵢ[i, j, 1] end # Compute total heat associated with temperature adjustment @@ -111,6 +131,9 @@ end @inbounds Qᶠₒ[i, j, 1] = δQ_frazil @inbounds Qᵢₒ[i, j, 1] = δQ_melting * ℵ # Melting depends on concentration + sea_ice_fields = (; u = uᵢ, v = vᵢ, h = hᵢ, ℵ = ℵᵢ) + τₒᵢ = sea_ice_ocean_stresses + @inbounds begin # Change in thickness Δh = hᵢ[i, j, 1] - h⁻[i, j, 1] @@ -122,5 +145,9 @@ end # Update previous ice thickness h⁻[i, j, 1] = hᵢ[i, j, 1] + + # Momentum stresses + τx[i, j, 1] = x_momentum_stress(i, j, 1, grid, τₒᵢ, clock, sea_ice_fields) + τy[i, j, 1] = y_momentum_stress(i, j, 1, grid, τₒᵢ, clock, sea_ice_fields) end end \ No newline at end of file From 4d963eaa1a324d5c0ba7f2cddb854fbc3e7fd909 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 1 Apr 2025 13:49:24 +0200 Subject: [PATCH 067/104] fix tests --- src/DataWrangling/JRA55/JRA55_field_time_series.jl | 2 +- src/DataWrangling/metadata.jl | 2 +- test/test_jra55.jl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index 0a97e4a6..1cc56948 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -86,7 +86,7 @@ Represents a JRA55 FieldTimeSeries backed by JRA55 native .nc files. JRA55NetCDFBackend(length, metadata) = JRA55NetCDFBackend(1, length, metadata) # Metadata - agnostic constructor -JRA55NetCDFBackend(length) = JRA55NetCDFBackend(length, nothing) +JRA55NetCDFBackend(length) = JRA55NetCDFBackend(1, length, nothing) Base.length(backend::JRA55NetCDFBackend) = backend.length Base.summary(backend::JRA55NetCDFBackend) = string("JRA55NetCDFBackend(", backend.start, ", ", backend.length, ")") diff --git a/src/DataWrangling/metadata.jl b/src/DataWrangling/metadata.jl index c4554076..8789d4f8 100644 --- a/src/DataWrangling/metadata.jl +++ b/src/DataWrangling/metadata.jl @@ -65,7 +65,7 @@ default_download_directory(dataset) = pwd() download_dataset(metadata) = nothing Base.show(io::IO, metadata::Metadata) = - print(io, "ECCOMetadata:", '\n', + print(io, "Metadata:", '\n', "├── name: $(metadata.name)", '\n', "├── dates: $(metadata.dates)", '\n', "├── dataset: $(metadata.dataset)", '\n', diff --git a/test/test_jra55.jl b/test/test_jra55.jl index 4b160f76..b4ad7315 100644 --- a/test/test_jra55.jl +++ b/test/test_jra55.jl @@ -47,7 +47,7 @@ using ClimaOcean.OceanSeaIceModels: PrescribedAtmosphere f₁ = view(parent(netcdf_JRA55_fts), :, :, 1, 1) f₁ = Array(f₁) - netcdf_JRA55_fts.backend = JRA55NetCDFBackend(Nt-2, Nb) + netcdf_JRA55_fts.backend = Oceananigans.OutputReaders.new_backend(netcdf_JRA55_fts.backend, Nt-2, Nb) @test Oceananigans.OutputReaders.time_indices(netcdf_JRA55_fts) == (Nt-2, Nt-1, Nt, 1) set!(netcdf_JRA55_fts) From 1ee41afc95302fcb5226a189671e2e439744dbe7 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 1 Apr 2025 13:57:10 +0200 Subject: [PATCH 068/104] bugfix --- src/DataWrangling/JRA55/JRA55_field_time_series.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index bd217822..68486db0 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -430,9 +430,9 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(), FT=Fl boundary_conditions) # Fill the data in a GPU-friendly manner - copyto!(interior(native_fts, :, :, 1, :), data) - fill_halo_regions!(native_fts) + copyto!(interior(fts, :, :, 1, :), data) + fill_halo_regions!(fts) - return native_fts + return fts end end From 1d58772675b43606cdad9dc328e995e737582e8c Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 1 Apr 2025 14:05:20 +0200 Subject: [PATCH 069/104] Update JRA55_field_time_series.jl --- src/DataWrangling/JRA55/JRA55_field_time_series.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index 68486db0..a302cc79 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -83,7 +83,8 @@ Adapt.adapt_structure(to, b::JRA55NetCDFBackend) = JRA55NetCDFBackend(b.start, b Represents a JRA55 FieldTimeSeries backed by JRA55 native .nc files. """ -JRA55NetCDFBackend(length, metadata) = JRA55NetCDFBackend(1, length, metadata) +JRA55NetCDFBackend(length, metadata::Metadata) = JRA55NetCDFBackend(1, length, metadata) +JRA55NetCDFBackend(start::Integer, length::Integer) = JRA55NetCDFBackend(start, length, nothing) # Metadata - agnostic constructor JRA55NetCDFBackend(length) = JRA55NetCDFBackend(1, length, nothing) From 0c020d0601f5fc83fea4c0563c4510cdaf115225 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 1 Apr 2025 14:06:39 +0200 Subject: [PATCH 070/104] Update JRA55_field_time_series.jl --- src/DataWrangling/JRA55/JRA55_field_time_series.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index a302cc79..585568a5 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -389,7 +389,7 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(), FT=Fl # The .nc coordinates lon_bnds and lat_bnds do not include # the last interface, so we push them here. - push!(φn, 90.0) + push!(φn, 90) push!(λn, λn[1] + 360) i₁, i₂, j₁, j₂, TX = compute_bounding_indices(longitude, latitude, nothing, Center, Center, λc, φc) From bafe3c7e2fc758426dd41ccd55775201a6f981ca Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 1 Apr 2025 14:08:45 +0200 Subject: [PATCH 071/104] simplify --- .../JRA55/JRA55_field_time_series.jl | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index 585568a5..1b24caea 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -414,26 +414,14 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(), FT=Fl start_time = first_date(metadata.dataset, metadata.name) times = native_times(metadata; start_time) - if backend isa JRA55NetCDFBackend - fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; - backend, - time_indexing, - boundary_conditions, - path = filepath, - name = shortname) - - set!(fts) - return fts - else - fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; - time_indexing, - backend, - boundary_conditions) + fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; + backend, + time_indexing, + boundary_conditions, + path = filepath, + name = shortname) - # Fill the data in a GPU-friendly manner - copyto!(interior(fts, :, :, 1, :), data) - fill_halo_regions!(fts) + set!(fts) - return fts - end + return fts end From 350b6577b762b00149ecd1ea0a5101b38634f510 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Tue, 1 Apr 2025 14:11:39 +0200 Subject: [PATCH 072/104] add the inmemory stuff --- .../JRA55/JRA55_field_time_series.jl | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index 1b24caea..e36e946f 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -414,14 +414,30 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(), FT=Fl start_time = first_date(metadata.dataset, metadata.name) times = native_times(metadata; start_time) - fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; - backend, - time_indexing, - boundary_conditions, - path = filepath, - name = shortname) - - set!(fts) + if backend isa JRA55NetCDFBackend + fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; + backend, + time_indexing, + boundary_conditions, + path = filepath, + name = shortname) + + set!(fts) + return fts + else + fts = FieldTimeSeries{Center, Center, Nothing}(JRA55_native_grid, times; + time_indexing, + backend, + boundary_conditions) + + # Fill the data in a GPU-friendly manner + ds = Dataset(filepath) + data = ds[shortname][i₁:i₂, j₁:j₂, time_indices_in_memory] + close(ds) + + copyto!(interior(fts, :, :, 1, :), data) + fill_halo_regions!(fts) - return fts + return fts + end end From 4a49b40934525986cc26cb72db4e1bbe6bb4e51b Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 3 Apr 2025 09:44:59 +0200 Subject: [PATCH 073/104] start with this --- experiments/omip_prototype/download_data.jl | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 experiments/omip_prototype/download_data.jl diff --git a/experiments/omip_prototype/download_data.jl b/experiments/omip_prototype/download_data.jl new file mode 100644 index 00000000..b188c285 --- /dev/null +++ b/experiments/omip_prototype/download_data.jl @@ -0,0 +1,7 @@ +using ClimaOcean +using ClimaOcean.JRA55 +using ClimaOcean.DataWrangling: download_dataset + +dir = "forcing_data/" + +atmosphere = JRA55PrescribedAtmosphere(; dataset=JRA55MultipleYears(), dir, include_rivers_and_icebergs=true) \ No newline at end of file From fea185454d5d9cbd2f612a638f6d6ac7f748cf3d Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 3 Apr 2025 09:46:36 +0200 Subject: [PATCH 074/104] default to inmemory --- src/DataWrangling/JRA55/JRA55_field_time_series.jl | 2 +- src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index e36e946f..23baf51a 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -302,7 +302,7 @@ end function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(), FT=Float32; latitude = nothing, longitude = nothing, - backend = JRA55NetCDFBackend(10, metadata), + backend = InMemory(), time_indexing = Cyclical()) # First thing: we download the dataset! diff --git a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl index eb763832..714eace6 100644 --- a/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl +++ b/src/DataWrangling/JRA55/JRA55_prescribed_atmosphere.jl @@ -23,7 +23,7 @@ function JRA55PrescribedAtmosphere(architecture = CPU(), FT = Float32; dataset = JRA55RepeatYear(), start_date = first_date(dataset, :temperature), end_date = last_date(dataset, :temperature), - backend = JRA55NetCDFBackend(10), + backend = InMemory(), time_indexing = Cyclical(), surface_layer_height = 10, # meters include_rivers_and_icebergs = false, From 7c4aa61b2772bdad1b310cfc9f28bc6ad6cc203a Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 3 Apr 2025 09:48:08 +0200 Subject: [PATCH 075/104] go! --- experiments/omip_prototype/download_data.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/experiments/omip_prototype/download_data.jl b/experiments/omip_prototype/download_data.jl index b188c285..2881bcd7 100644 --- a/experiments/omip_prototype/download_data.jl +++ b/experiments/omip_prototype/download_data.jl @@ -2,6 +2,7 @@ using ClimaOcean using ClimaOcean.JRA55 using ClimaOcean.DataWrangling: download_dataset -dir = "forcing_data/" - -atmosphere = JRA55PrescribedAtmosphere(; dataset=JRA55MultipleYears(), dir, include_rivers_and_icebergs=true) \ No newline at end of file +atmosphere = JRA55PrescribedAtmosphere(; dir="forcing_data/", + dataset=JRA55MultipleYears(), + backend=JRA55NetCDFBackend(10), + include_rivers_and_icebergs=true) \ No newline at end of file From 24f7d93357df522828594d4417e2d465376a7598 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 3 Apr 2025 10:11:42 +0200 Subject: [PATCH 076/104] add a failsafe + a test --- src/DataWrangling/JRA55/JRA55.jl | 2 +- .../JRA55/JRA55_field_time_series.jl | 7 +++++++ test/test_jra55.jl | 19 ++++++++++++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55.jl b/src/DataWrangling/JRA55/JRA55.jl index 48a3d365..dc66003b 100644 --- a/src/DataWrangling/JRA55/JRA55.jl +++ b/src/DataWrangling/JRA55/JRA55.jl @@ -1,6 +1,6 @@ module JRA55 -export JRA55FieldTimeSeries, JRA55PrescribedAtmosphere, JRA55RepeatYear +export JRA55FieldTimeSeries, JRA55PrescribedAtmosphere, JRA55RepeatYear, JRA55MultipleYears using Oceananigans using Oceananigans.Units diff --git a/src/DataWrangling/JRA55/JRA55_field_time_series.jl b/src/DataWrangling/JRA55/JRA55_field_time_series.jl index 23baf51a..416ece3f 100644 --- a/src/DataWrangling/JRA55/JRA55_field_time_series.jl +++ b/src/DataWrangling/JRA55/JRA55_field_time_series.jl @@ -305,6 +305,13 @@ function JRA55FieldTimeSeries(metadata::JRA55Metadata, architecture=CPU(), FT=Fl backend = InMemory(), time_indexing = Cyclical()) + + # Cannot use `TotallyInMemory` backend with JRA55MultipleYear dataset + if metadata.dataset isa JRA55MultipleYears && backend isa TotallyInMemory + msg = string("The `InMemory` backend is not supported for the JRA55MultipleYears dataset.") + throw(ArgumentError(msg)) + end + # First thing: we download the dataset! download_dataset(metadata) diff --git a/test/test_jra55.jl b/test/test_jra55.jl index b4ad7315..78f6fde6 100644 --- a/test/test_jra55.jl +++ b/test/test_jra55.jl @@ -1,6 +1,6 @@ include("runtests_setup.jl") -using ClimaOcean.JRA55: download_JRA55_cache +using ClimaOcean.JRA55: download_JRA55_cache, JRA55MultipleYears using ClimaOcean.OceanSeaIceModels: PrescribedAtmosphere @testset "JRA55 and data wrangling utilities" begin @@ -129,5 +129,22 @@ using ClimaOcean.OceanSeaIceModels: PrescribedAtmosphere @test rivers_times != pressure_times @test length(rivers_times) != length(pressure_times) @test rivers_times[2] - rivers_times[1] == 86400 + + @info "Testing multi year JRA55 data on $A..." + dataset = JRA55MultipleYears() + dates = ClimaOcean.DataWrangling.all_dates(JRA55MultipleYears(), :temperature) + + # These dates correspond to a metadata that crosses between year 1958 and 1959. + # Therefore, this will download two files for the two years and concatenate them + # when reading the data. + start_date = dates[2800] + end_date = dates[3600] + backend = JRA55NetCDFBackend(10) + Ta = JRA55FieldTimeSeries(:temperature; dataset=JRA55MultipleYears(), start_date, end_date, backend) + + # Test we can access all the data + for t in Ta.times + @test Ta[t] isa Field + end end end From d231995c7226fcb7887e79f72f49a9bb4f6aad92 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 3 Apr 2025 10:18:51 +0200 Subject: [PATCH 077/104] some cleanup --- src/DataWrangling/JRA55/JRA55_metadata.jl | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_metadata.jl b/src/DataWrangling/JRA55/JRA55_metadata.jl index 69c2827f..ea952252 100644 --- a/src/DataWrangling/JRA55/JRA55_metadata.jl +++ b/src/DataWrangling/JRA55/JRA55_metadata.jl @@ -64,23 +64,18 @@ function metadata_filename(metadata::Metadata{<:JRA55RepeatYear}) # No differenc return "RYF." * shortname * ".1990_1991.nc" end -multiple_year_time_displaced_variables = [:rain_freshwater_flux, - :snow_freshwater_flux, - :downwelling_shortwave_radiation, - :downwelling_longwave_radiation] - function metadata_filename(metadata::Metadatum{<:JRA55MultipleYears}) # fix the filename shortname = short_name(metadata) year = Dates.year(metadata.dates) suffix = "_input4MIPs_atmosphericState_OMIP_MRI-JRA55-do-1-5-0_gr_" - if metadata.name ∈ [:river_freshwater_flux, :iceberg_freshwater_flux] + end_date = JRA55_multiple_year_dates[metadata.name][end] + end_hour = Hour(end_date) + + if end_hour == Hour(0) dates = "$(year)0101-$(year)1231" - elseif metadata.name ∈ [:rain_freshwater_flux, - :snow_freshwater_flux, - :downwelling_shortwave_radiation, - :downwelling_longwave_radiation] + elseif end_hour == Hour(22) dates = "$(year)01010130-$(year)12312230" else dates = "$(year)01010000-$(year)12312100" From ca1980c66d9995dfe167b199b59ad266ae526d47 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 3 Apr 2025 10:22:01 +0200 Subject: [PATCH 078/104] better to write last --- src/DataWrangling/JRA55/JRA55_metadata.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataWrangling/JRA55/JRA55_metadata.jl b/src/DataWrangling/JRA55/JRA55_metadata.jl index ea952252..51446231 100644 --- a/src/DataWrangling/JRA55/JRA55_metadata.jl +++ b/src/DataWrangling/JRA55/JRA55_metadata.jl @@ -70,7 +70,7 @@ function metadata_filename(metadata::Metadatum{<:JRA55MultipleYears}) year = Dates.year(metadata.dates) suffix = "_input4MIPs_atmosphericState_OMIP_MRI-JRA55-do-1-5-0_gr_" - end_date = JRA55_multiple_year_dates[metadata.name][end] + end_date = last(JRA55_multiple_year_dates[metadata.name]) end_hour = Hour(end_date) if end_hour == Hour(0) From aeedd2f8a0b1370f203853ab28a0c55c1ba22829 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 3 Apr 2025 10:32:57 +0200 Subject: [PATCH 079/104] last index --- .../InterfaceComputations/sea_ice_ocean_fluxes.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OceanSeaIceModels/InterfaceComputations/sea_ice_ocean_fluxes.jl b/src/OceanSeaIceModels/InterfaceComputations/sea_ice_ocean_fluxes.jl index 98d15607..236eba75 100644 --- a/src/OceanSeaIceModels/InterfaceComputations/sea_ice_ocean_fluxes.jl +++ b/src/OceanSeaIceModels/InterfaceComputations/sea_ice_ocean_fluxes.jl @@ -147,7 +147,7 @@ end h⁻[i, j, 1] = hᵢ[i, j, 1] # Momentum stresses - τx[i, j, 1] = x_momentum_stress(i, j, 1, grid, τₒᵢ, clock, sea_ice_fields) - τy[i, j, 1] = y_momentum_stress(i, j, 1, grid, τₒᵢ, clock, sea_ice_fields) + τx[i, j, 1] = x_momentum_stress(i, j, Nz, grid, τₒᵢ, clock, sea_ice_fields) + τy[i, j, 1] = y_momentum_stress(i, j, Nz, grid, τₒᵢ, clock, sea_ice_fields) end end \ No newline at end of file From ddee7f06e1fb6fe5ecd86cda8c5502b9fbb3cc99 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 3 Apr 2025 10:34:18 +0200 Subject: [PATCH 080/104] add this --- .../InterfaceComputations/assemble_net_fluxes.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl b/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl index aa045fb5..5ff12337 100644 --- a/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl +++ b/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl @@ -4,7 +4,11 @@ using Oceananigans.Operators: ℑxᶠᵃᵃ, ℑyᵃᶠᵃ using ClimaOcean.OceanSeaIceModels: sea_ice_concentration @inline computed_sea_ice_ocean_fluxes(interface) = interface.fluxes -@inline computed_sea_ice_ocean_fluxes(::Nothing) = (interface_heat = ZeroField(), frazil_heat = ZeroField(), salt = ZeroField()) +@inline computed_sea_ice_ocean_fluxes(::Nothing) = (interface_heat = ZeroField(), + frazil_heat = ZeroField(), + salt = ZeroField(), + x_momentum = ZeroField(), + y_momentum = ZeroField()) function compute_net_ocean_fluxes!(coupled_model) ocean = coupled_model.ocean From daeb12f2b67a463848b9af5582adff2e643dc87c Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 3 Apr 2025 10:38:19 +0200 Subject: [PATCH 081/104] add a clock --- .../InterfaceComputations/sea_ice_ocean_fluxes.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/OceanSeaIceModels/InterfaceComputations/sea_ice_ocean_fluxes.jl b/src/OceanSeaIceModels/InterfaceComputations/sea_ice_ocean_fluxes.jl index 236eba75..ab87e08d 100644 --- a/src/OceanSeaIceModels/InterfaceComputations/sea_ice_ocean_fluxes.jl +++ b/src/OceanSeaIceModels/InterfaceComputations/sea_ice_ocean_fluxes.jl @@ -19,8 +19,9 @@ function compute_sea_ice_ocean_fluxes!(coupled_model) ocean_properties = coupled_model.interfaces.ocean_properties liquidus = sea_ice.model.ice_thermodynamics.phase_transitions.liquidus - grid = ocean.model.grid - arch = architecture(grid) + grid = ocean.model.grid + clock = ocean.model.clock + arch = architecture(grid) uᵢ, vᵢ = sea_ice.model.velocities dynamics = sea_ice.model.dynamics From 629d582c8e3712bd0a34698612e841fee7976dd5 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 3 Apr 2025 10:50:41 +0200 Subject: [PATCH 082/104] add the arctic --- experiments/arctic_simulation.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/experiments/arctic_simulation.jl b/experiments/arctic_simulation.jl index 8cfe5cd6..d87a7fb3 100644 --- a/experiments/arctic_simulation.jl +++ b/experiments/arctic_simulation.jl @@ -63,7 +63,7 @@ using ClimaSeaIce.SeaIceMomentumEquations using ClimaSeaIce.Rheologies # Remember to pass the SSS as a bottom bc to the sea ice! -SSS = view(ocean.model.tracers.S, :, :, grid.Nz) +SSS = view(ocean.model.tracers.S.data, :, :, grid.Nz) bottom_heat_boundary_condition = IceWaterThermalEquilibrium(SSS) SSU = view(ocean.model.velocities.u, :, :, grid.Nz) @@ -77,7 +77,7 @@ dynamics = SeaIceMomentumEquation(grid; coriolis = ocean.model.coriolis, top_momentum_stress = (u=τua, v=τva), bottom_momentum_stress = τo, - ocean_velocities = (u=SSU, v=SSV), + ocean_velocities = (u=0.1*SSU, v=0.1*SSV), rheology = ElastoViscoPlasticRheology(), solver = SplitExplicitSolver(120)) From 35fe01dced1f5f977cca73248a8bad1f5f6d280e Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 3 Apr 2025 11:57:40 +0200 Subject: [PATCH 083/104] try it out --- .../omip_prototype/quarter_degree_omip.jl | 123 ++++++++++++++++++ src/SeaIceSimulations.jl | 20 +++ 2 files changed, 143 insertions(+) create mode 100644 experiments/omip_prototype/quarter_degree_omip.jl diff --git a/experiments/omip_prototype/quarter_degree_omip.jl b/experiments/omip_prototype/quarter_degree_omip.jl new file mode 100644 index 00000000..8d8feec1 --- /dev/null +++ b/experiments/omip_prototype/quarter_degree_omip.jl @@ -0,0 +1,123 @@ +using ClimaOcean +using ClimaSeaIce +using Oceananigans +using Oceananigans.Grids +using Oceananigans.Units +using Oceananigans.OrthogonalSphericalShellGrids +using ClimaOcean.OceanSimulations +using ClimaOcean.ECCO +using ClimaOcean.DataWrangling +using ClimaSeaIce.SeaIceThermodynamics: IceWaterThermalEquilibrium +using Printf + +using CUDA +CUDA.device!(1) +arch = GPU() + +r_faces = ClimaOcean.exponential_z_faces(; Nz=100, h=30, depth=6200) +z_faces = MutableVerticalDiscretization(r_faces) + +Nx = 1440 # longitudinal direction +Ny = 700 # meridional direction +Nz = length(r_faces) - 1 + +grid = TripolarGrid(arch; + size = (Nx, Ny, Nz), + z = z_faces, + halo = (7, 7, 7)) + +bottom_height = regrid_bathymetry(grid; minimum_depth=15, major_basins=1) +grid = ImmersedBoundaryGrid(grid, GridFittedBottom(bottom_height); active_cells_map=true) + +##### +##### A Propgnostic Ocean model +##### + +# A very diffusive ocean +momentum_advection = WENOVectorInvariant() +tracer_advection = WENO(order=7) + +free_surface = SplitExplicitFreeSurface(grid; substeps=70) +closure = ClimaOcean.OceanSimulations.default_ocean_closure() + +ocean = ocean_simulation(grid; + momentum_advection, + tracer_advection, + free_surface, + closure) + +dataset = ECCO4Monthly() + +set!(ocean.model, T=Metadatum(:temperature; dataset), + S=Metadatum(:salinity; dataset)) + +##### +##### A Prognostic Sea-ice model +##### + +# Remember to pass the SSS as a bottom bc to the sea ice! +SSS = view(ocean.model.tracers.S.data, :, :, grid.Nz) +bottom_heat_boundary_condition = IceWaterThermalEquilibrium(SSS) + +# Default dynamics +dynamics = ClimaOcean.SeaIceSimulations.default_sea_ice_dynamics(grid; ocean) + +sea_ice = sea_ice_simulation(grid; bottom_heat_boundary_condition, dynamics, advection=WENO(order=7)) + +set!(sea_ice.model, h=Metadatum(:sea_ice_thickness; dataset), + ℵ=Metadatum(:sea_ice_concentration; dataset)) + +##### +##### A Prescribed Atmosphere model +##### + +atmosphere = JRA55PrescribedAtmosphere(arch; backend=JRA55NetCDFBackend(40)) +radiation = Radiation() + +##### +##### Arctic coupled model +##### + +omip = OceanSeaIceModel(ocean, sea_ice; atmosphere, radiation) +omip = Simulation(arctic, Δt=1minutes, stop_time=30days) + +# Figure out the outputs.... + +wall_time = Ref(time_ns()) + +using Statistics + +function progress(sim) + sea_ice = sim.model.sea_ice + hmax = maximum(sea_ice.model.ice_thickness) + ℵmax = maximum(sea_ice.model.ice_concentration) + hmean = mean(sea_ice.model.ice_thickness) + ℵmean = mean(sea_ice.model.ice_concentration) + Tmax = maximum(sim.model.interfaces.atmosphere_sea_ice_interface.temperature) + Tmin = minimum(sim.model.interfaces.atmosphere_sea_ice_interface.temperature) + + step_time = 1e-9 * (time_ns() - wall_time[]) + + msg1 = @sprintf("time: %s, iteration: %d, Δt: %s, ", prettytime(sim), iteration(sim), prettytime(sim.Δt)) + msg2 = @sprintf("max(h): %.2e m, max(ℵ): %.2e ", hmax, ℵmax) + msg3 = @sprintf("mean(h): %.2e m, mean(ℵ): %.2e ", hmean, ℵmean) + msg4 = @sprintf("extrema(T): (%.2f, %.2f) ᵒC, ", Tmax, Tmin) + msg5 = @sprintf("wall time: %s \n", prettytime(step_time)) + + @info msg1 * msg2 * msg3 * msg4 * msg5 + + wall_time[] = time_ns() + + return nothing +end + +# And add it as a callback to the simulation. +add_callback!(omip, progress, IterationInterval(10)) + +run!(omip) + +# The full OMIP cycle! +omip.stop_time = 60*365days +omip.Δt = 600 + +run!(omip) \ No newline at end of file diff --git a/src/SeaIceSimulations.jl b/src/SeaIceSimulations.jl index 2b7dc17b..ec58108a 100644 --- a/src/SeaIceSimulations.jl +++ b/src/SeaIceSimulations.jl @@ -17,6 +17,8 @@ using Oceananigans.Operators using ClimaSeaIce using ClimaSeaIce: SeaIceModel, SlabSeaIceThermodynamics, PhaseTransitions, ConductiveFlux using ClimaSeaIce.SeaIceThermodynamics: IceWaterThermalEquilibrium +using ClimaSeaIce.SeaIceMomentumEquations +using ClimaSeaIce.Rheologies using ClimaOcean.OceanSimulations: Default @@ -73,4 +75,22 @@ function sea_ice_simulation(grid; return sea_ice end +function default_sea_ice_dynamics(grid; ocean) + + SSU = view(ocean.model.velocities.u, :, :, grid.Nz) + SSV = view(ocean.model.velocities.u, :, :, grid.Nz) + + τo = SemiImplicitStress(uₑ=SSU, vₑ=SSV) + τua = Field{Face, Center, Nothing}(grid) + τva = Field{Center, Face, Nothing}(grid) + + return SeaIceMomentumEquation(grid; + coriolis = ocean.model.coriolis, + top_momentum_stress = (u=τua, v=τva), + bottom_momentum_stress = τo, + ocean_velocities = (u=0.1*SSU, v=0.1*SSV), + rheology = ElastoViscoPlasticRheology(), + solver = SplitExplicitSolver(120)) +end + end \ No newline at end of file From 5548a99f48f850c216b20117b0300bb8397361a2 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 3 Apr 2025 12:20:18 +0200 Subject: [PATCH 084/104] correct dates --- src/DataWrangling/JRA55/JRA55_metadata.jl | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/DataWrangling/JRA55/JRA55_metadata.jl b/src/DataWrangling/JRA55/JRA55_metadata.jl index 51446231..7a80670c 100644 --- a/src/DataWrangling/JRA55/JRA55_metadata.jl +++ b/src/DataWrangling/JRA55/JRA55_metadata.jl @@ -132,17 +132,17 @@ JRA55_multiple_year_prefix = Dict( ) JRA55_multiple_year_dates = Dict( - :river_freshwater_flux => DateTime(1958, 1, 1) : Day(1) : DateTime(2021, 12, 31), - :rain_freshwater_flux => DateTime(1958, 1, 1, 1, 30) : Hour(3) : DateTime(2021, 12, 31, 22, 30), - :snow_freshwater_flux => DateTime(1958, 1, 1, 1, 30) : Hour(3) : DateTime(2021, 12, 31, 22, 30), - :iceberg_freshwater_flux => DateTime(1958, 1, 1) : Day(1) : DateTime(2021, 12, 31), - :specific_humidity => DateTime(1958, 1, 1) : Hour(3) : DateTime(2021, 12, 31, 21), - :sea_level_pressure => DateTime(1958, 1, 1) : Hour(3) : DateTime(2021, 12, 31, 21), - :downwelling_longwave_radiation => DateTime(1958, 1, 1, 1, 30) : Hour(3) : DateTime(2021, 12, 31, 22, 30), - :downwelling_shortwave_radiation => DateTime(1958, 1, 1, 1, 30) : Hour(3) : DateTime(2021, 12, 31, 22, 30), - :temperature => DateTime(1958, 1, 1) : Hour(3) : DateTime(2021, 12, 31, 21), - :eastward_velocity => DateTime(1958, 1, 1) : Hour(3) : DateTime(2021, 12, 31, 21), - :northward_velocity => DateTime(1958, 1, 1) : Hour(3) : DateTime(2021, 12, 31, 21) + :river_freshwater_flux => DateTime(1958, 1, 1) : Day(1) : DateTime(2019, 12, 31), + :rain_freshwater_flux => DateTime(1958, 1, 1, 1, 30) : Hour(3) : DateTime(2019, 12, 31, 22, 30), + :snow_freshwater_flux => DateTime(1958, 1, 1, 1, 30) : Hour(3) : DateTime(2019, 12, 31, 22, 30), + :iceberg_freshwater_flux => DateTime(1958, 1, 1) : Day(1) : DateTime(2019, 12, 31), + :specific_humidity => DateTime(1958, 1, 1) : Hour(3) : DateTime(2019, 12, 31, 21), + :sea_level_pressure => DateTime(1958, 1, 1) : Hour(3) : DateTime(2019, 12, 31, 21), + :downwelling_longwave_radiation => DateTime(1958, 1, 1, 1, 30) : Hour(3) : DateTime(2019, 12, 31, 22, 30), + :downwelling_shortwave_radiation => DateTime(1958, 1, 1, 1, 30) : Hour(3) : DateTime(2019, 12, 31, 22, 30), + :temperature => DateTime(1958, 1, 1) : Hour(3) : DateTime(2019, 12, 31, 21), + :eastward_velocity => DateTime(1958, 1, 1) : Hour(3) : DateTime(2019, 12, 31, 21), + :northward_velocity => DateTime(1958, 1, 1) : Hour(3) : DateTime(2019, 12, 31, 21) ) JRA55_repeat_year_urls = Dict( From dfb8590e47604f291de2909c4293ace8c3db6619 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Fri, 4 Apr 2025 08:49:35 +0200 Subject: [PATCH 085/104] Update experiments/omip_prototype/quarter_degree_omip.jl Co-authored-by: Navid C. Constantinou --- experiments/omip_prototype/quarter_degree_omip.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/experiments/omip_prototype/quarter_degree_omip.jl b/experiments/omip_prototype/quarter_degree_omip.jl index 8d8feec1..bc324212 100644 --- a/experiments/omip_prototype/quarter_degree_omip.jl +++ b/experiments/omip_prototype/quarter_degree_omip.jl @@ -59,10 +59,12 @@ set!(ocean.model, T=Metadatum(:temperature; dataset), SSS = view(ocean.model.tracers.S.data, :, :, grid.Nz) bottom_heat_boundary_condition = IceWaterThermalEquilibrium(SSS) -# Default dynamics -dynamics = ClimaOcean.SeaIceSimulations.default_sea_ice_dynamics(grid; ocean) +# Default sea-ice dynamics +sea_ice_dynamics = ClimaOcean.SeaIceSimulations.default_sea_ice_dynamics(grid; ocean) -sea_ice = sea_ice_simulation(grid; bottom_heat_boundary_condition, dynamics, advection=WENO(order=7)) +sea_ice = sea_ice_simulation(grid; bottom_heat_boundary_condition, + dynamics = sea_ice_dynamics, + advection=WENO(order=7)) set!(sea_ice.model, h=Metadatum(:sea_ice_thickness; dataset), ℵ=Metadatum(:sea_ice_concentration; dataset)) From a049dc1d255866d84463097fd4fa3d0505bc69ca Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Fri, 4 Apr 2025 08:49:45 +0200 Subject: [PATCH 086/104] Update experiments/omip_prototype/quarter_degree_omip.jl Co-authored-by: Navid C. Constantinou --- experiments/omip_prototype/quarter_degree_omip.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/experiments/omip_prototype/quarter_degree_omip.jl b/experiments/omip_prototype/quarter_degree_omip.jl index bc324212..29a5102c 100644 --- a/experiments/omip_prototype/quarter_degree_omip.jl +++ b/experiments/omip_prototype/quarter_degree_omip.jl @@ -77,7 +77,7 @@ atmosphere = JRA55PrescribedAtmosphere(arch; backend=JRA55NetCDFBackend(40)) radiation = Radiation() ##### -##### Arctic coupled model +##### An ocean-sea ice coupled model ##### omip = OceanSeaIceModel(ocean, sea_ice; atmosphere, radiation) From d1e6fafbbe03133185ca01e08a936403a83d6c70 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Fri, 4 Apr 2025 18:36:31 +0200 Subject: [PATCH 087/104] add stuff --- experiments/omip_prototype/quarter_degree_omip.jl | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/experiments/omip_prototype/quarter_degree_omip.jl b/experiments/omip_prototype/quarter_degree_omip.jl index 29a5102c..65f81f4c 100644 --- a/experiments/omip_prototype/quarter_degree_omip.jl +++ b/experiments/omip_prototype/quarter_degree_omip.jl @@ -73,7 +73,7 @@ set!(sea_ice.model, h=Metadatum(:sea_ice_thickness; dataset), ##### A Prescribed Atmosphere model ##### -atmosphere = JRA55PrescribedAtmosphere(arch; backend=JRA55NetCDFBackend(40)) +atmosphere = JRA55PrescribedAtmosphere(arch; dataset=JRA55MultipleYears(), backend=JRA55NetCDFBackend(40), include_rivers_and_icebergs=true) radiation = Radiation() ##### @@ -85,6 +85,17 @@ omip = Simulation(arctic, Δt=1minutes, stop_time=30days) # Figure out the outputs.... +ocean.output_writer[:checkpointer] = Checkpointer(ocean.model, + schedule = IterationInterval(10000), + prefix = "ocean_checkpoint", + overwrite_existing = true) + + +sea_ice.output_writer[:checkpointer] = Checkpointer(sea_ice.model, + schedule = IterationInterval(10000), + prefix = "sea_ice_checkpoint", + overwrite_existing = true) + wall_time = Ref(time_ns()) using Statistics From a6d758d729d80dd849a5794e9997eea3a90106de Mon Sep 17 00:00:00 2001 From: simone-silvestri Date: Thu, 10 Apr 2025 05:25:45 -0400 Subject: [PATCH 088/104] going sixth degree --- ...er_degree_omip.jl => sixth_degree_omip.jl} | 34 +++++++------------ 1 file changed, 12 insertions(+), 22 deletions(-) rename experiments/omip_prototype/{quarter_degree_omip.jl => sixth_degree_omip.jl} (76%) diff --git a/experiments/omip_prototype/quarter_degree_omip.jl b/experiments/omip_prototype/sixth_degree_omip.jl similarity index 76% rename from experiments/omip_prototype/quarter_degree_omip.jl rename to experiments/omip_prototype/sixth_degree_omip.jl index 65f81f4c..81cda71e 100644 --- a/experiments/omip_prototype/quarter_degree_omip.jl +++ b/experiments/omip_prototype/sixth_degree_omip.jl @@ -6,19 +6,18 @@ using Oceananigans.Units using Oceananigans.OrthogonalSphericalShellGrids using ClimaOcean.OceanSimulations using ClimaOcean.ECCO +using ClimaOcean.JRA55: JRA55MultipleYears using ClimaOcean.DataWrangling using ClimaSeaIce.SeaIceThermodynamics: IceWaterThermalEquilibrium using Printf - using CUDA -CUDA.device!(1) -arch = GPU() -r_faces = ClimaOcean.exponential_z_faces(; Nz=100, h=30, depth=6200) +arch = GPU() +r_faces = ClimaOcean.exponential_z_faces(; Nz=60, depth=6200) z_faces = MutableVerticalDiscretization(r_faces) -Nx = 1440 # longitudinal direction -Ny = 700 # meridional direction +Nx = 2160 # longitudinal direction +Ny = 1080 # meridional direction Nz = length(r_faces) - 1 grid = TripolarGrid(arch; @@ -35,12 +34,12 @@ grid = ImmersedBoundaryGrid(grid, GridFittedBottom(bottom_height); active_cells_ # A very diffusive ocean momentum_advection = WENOVectorInvariant() -tracer_advection = WENO(order=7) +tracer_advection = FluxFormAdvection(WENO(order=7), WENO(order=7), Centered()) free_surface = SplitExplicitFreeSurface(grid; substeps=70) closure = ClimaOcean.OceanSimulations.default_ocean_closure() -ocean = ocean_simulation(grid; +ocean = ocean_simulation(grid; Δt=1minutes, momentum_advection, tracer_advection, free_surface, @@ -73,7 +72,7 @@ set!(sea_ice.model, h=Metadatum(:sea_ice_thickness; dataset), ##### A Prescribed Atmosphere model ##### -atmosphere = JRA55PrescribedAtmosphere(arch; dataset=JRA55MultipleYears(), backend=JRA55NetCDFBackend(40), include_rivers_and_icebergs=true) +atmosphere = JRA55PrescribedAtmosphere(arch; dir="./forcing_data", dataset=JRA55MultipleYears(), backend=JRA55NetCDFBackend(40), include_rivers_and_icebergs=true) radiation = Radiation() ##### @@ -81,21 +80,15 @@ radiation = Radiation() ##### omip = OceanSeaIceModel(ocean, sea_ice; atmosphere, radiation) -omip = Simulation(arctic, Δt=1minutes, stop_time=30days) +omip = Simulation(omip, Δt=20, stop_time=30days) # Figure out the outputs.... -ocean.output_writer[:checkpointer] = Checkpointer(ocean.model, +ocean.output_writers[:checkpointer] = Checkpointer(ocean.model, schedule = IterationInterval(10000), prefix = "ocean_checkpoint", overwrite_existing = true) - -sea_ice.output_writer[:checkpointer] = Checkpointer(sea_ice.model, - schedule = IterationInterval(10000), - prefix = "sea_ice_checkpoint", - overwrite_existing = true) - wall_time = Ref(time_ns()) using Statistics @@ -104,8 +97,6 @@ function progress(sim) sea_ice = sim.model.sea_ice hmax = maximum(sea_ice.model.ice_thickness) ℵmax = maximum(sea_ice.model.ice_concentration) - hmean = mean(sea_ice.model.ice_thickness) - ℵmean = mean(sea_ice.model.ice_concentration) Tmax = maximum(sim.model.interfaces.atmosphere_sea_ice_interface.temperature) Tmin = minimum(sim.model.interfaces.atmosphere_sea_ice_interface.temperature) @@ -113,11 +104,10 @@ function progress(sim) msg1 = @sprintf("time: %s, iteration: %d, Δt: %s, ", prettytime(sim), iteration(sim), prettytime(sim.Δt)) msg2 = @sprintf("max(h): %.2e m, max(ℵ): %.2e ", hmax, ℵmax) - msg3 = @sprintf("mean(h): %.2e m, mean(ℵ): %.2e ", hmean, ℵmean) msg4 = @sprintf("extrema(T): (%.2f, %.2f) ᵒC, ", Tmax, Tmin) msg5 = @sprintf("wall time: %s \n", prettytime(step_time)) - @info msg1 * msg2 * msg3 * msg4 * msg5 + @info msg1 * msg2 * msg4 * msg5 wall_time[] = time_ns() @@ -133,4 +123,4 @@ run!(omip) omip.stop_time = 60*365days omip.Δt = 600 -run!(omip) \ No newline at end of file +run!(omip) From f18a772e3d6dbe08228ca52f56bff8a2672de6f4 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 10 Apr 2025 11:36:56 +0200 Subject: [PATCH 089/104] some changes --- Project.toml | 2 +- .../omip_prototype/sixth_degree_omip.jl | 50 +++++++++++-------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/Project.toml b/Project.toml index 9b43e5a5..bc4f4f72 100644 --- a/Project.toml +++ b/Project.toml @@ -39,7 +39,7 @@ ClimaOceanReactantExt = "Reactant" Adapt = "4" CFTime = "0.1" CUDA = "4, 5" -ClimaSeaIce = "0.2.4" +ClimaSeaIce = "0.2.6" CubicSplines = "0.2" DataDeps = "0.7" Downloads = "1.6" diff --git a/experiments/omip_prototype/sixth_degree_omip.jl b/experiments/omip_prototype/sixth_degree_omip.jl index 81cda71e..7a3ddd05 100644 --- a/experiments/omip_prototype/sixth_degree_omip.jl +++ b/experiments/omip_prototype/sixth_degree_omip.jl @@ -6,7 +6,7 @@ using Oceananigans.Units using Oceananigans.OrthogonalSphericalShellGrids using ClimaOcean.OceanSimulations using ClimaOcean.ECCO -using ClimaOcean.JRA55: JRA55MultipleYears +using ClimaOcean.JRA55 using ClimaOcean.DataWrangling using ClimaSeaIce.SeaIceThermodynamics: IceWaterThermalEquilibrium using Printf @@ -34,10 +34,11 @@ grid = ImmersedBoundaryGrid(grid, GridFittedBottom(bottom_height); active_cells_ # A very diffusive ocean momentum_advection = WENOVectorInvariant() -tracer_advection = FluxFormAdvection(WENO(order=7), WENO(order=7), Centered()) +tracer_advection = WENO(order=7) free_surface = SplitExplicitFreeSurface(grid; substeps=70) -closure = ClimaOcean.OceanSimulations.default_ocean_closure() +closure = (ClimaOcean.OceanSimulations.default_ocean_closure(), + VerticalScalarDiffusivity(κ=1e-5, ν=1e-5)) ocean = ocean_simulation(grid; Δt=1minutes, momentum_advection, @@ -45,10 +46,8 @@ ocean = ocean_simulation(grid; Δt=1minutes, free_surface, closure) -dataset = ECCO4Monthly() - -set!(ocean.model, T=Metadatum(:temperature; dataset), - S=Metadatum(:salinity; dataset)) +restart_file = "ocean_checkpoint_iteration130000.jld2" +set!(ocean.model, restart_file) ##### ##### A Prognostic Sea-ice model @@ -65,14 +64,21 @@ sea_ice = sea_ice_simulation(grid; bottom_heat_boundary_condition, dynamics = sea_ice_dynamics, advection=WENO(order=7)) -set!(sea_ice.model, h=Metadatum(:sea_ice_thickness; dataset), - ℵ=Metadatum(:sea_ice_concentration; dataset)) +dataset = ECCO4Monthly() +date = DateTime(1992, 2, 1) # 1st Feb 1992 + +set!(sea_ice.model, h=Metadatum(:sea_ice_thickness; date, dataset), + ℵ=Metadatum(:sea_ice_concentration; date, dataset)) ##### ##### A Prescribed Atmosphere model ##### -atmosphere = JRA55PrescribedAtmosphere(arch; dir="./forcing_data", dataset=JRA55MultipleYears(), backend=JRA55NetCDFBackend(40), include_rivers_and_icebergs=true) +dir = "./forcing_data" +dataset = MultiyearJRA55() +backend = JRA55NetCDFBackend(40) + +atmosphere = JRA55PrescribedAtmosphere(arch; dir, dataset, backend, include_rivers_and_icebergs=true) radiation = Radiation() ##### @@ -80,7 +86,7 @@ radiation = Radiation() ##### omip = OceanSeaIceModel(ocean, sea_ice; atmosphere, radiation) -omip = Simulation(omip, Δt=20, stop_time=30days) +omip = Simulation(omip, Δt=10minutes, stop_time=60*365days) # Figure out the outputs.... @@ -89,25 +95,35 @@ ocean.output_writers[:checkpointer] = Checkpointer(ocean.model, prefix = "ocean_checkpoint", overwrite_existing = true) +sea_ice.output_writers[:checkpointer] = Checkpointer(sea_ice.model, + schedule = IterationInterval(10000), + prefix = "sea_ice_checkpoint", + overwrite_existing = true) + wall_time = Ref(time_ns()) using Statistics function progress(sim) sea_ice = sim.model.sea_ice + ocean = sim.model.ocean hmax = maximum(sea_ice.model.ice_thickness) ℵmax = maximum(sea_ice.model.ice_concentration) Tmax = maximum(sim.model.interfaces.atmosphere_sea_ice_interface.temperature) Tmin = minimum(sim.model.interfaces.atmosphere_sea_ice_interface.temperature) + umax = maximum(ocean.model.velocities.u) + vmax = maximum(ocean.model.velocities.v) + wmax = maximum(ocean.model.velocities.w) step_time = 1e-9 * (time_ns() - wall_time[]) msg1 = @sprintf("time: %s, iteration: %d, Δt: %s, ", prettytime(sim), iteration(sim), prettytime(sim.Δt)) msg2 = @sprintf("max(h): %.2e m, max(ℵ): %.2e ", hmax, ℵmax) msg4 = @sprintf("extrema(T): (%.2f, %.2f) ᵒC, ", Tmax, Tmin) - msg5 = @sprintf("wall time: %s \n", prettytime(step_time)) + msg5 = @sprintf("maximum(u): (%.2f, %.2f, %.2f) m/s, ", umax, vmax, wmax) + msg6 = @sprintf("wall time: %s \n", prettytime(step_time)) - @info msg1 * msg2 * msg4 * msg5 + @info msg1 * msg2 * msg4 * msg5 * msg6 wall_time[] = time_ns() @@ -115,12 +131,6 @@ function progress(sim) end # And add it as a callback to the simulation. -add_callback!(omip, progress, IterationInterval(10)) - -run!(omip) - -# The full OMIP cycle! -omip.stop_time = 60*365days -omip.Δt = 600 +add_callback!(omip, progress, IterationInterval(50)) run!(omip) From 6679bbd73d8c730dd7a95859b5ee5ced80e98fba Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 10 Apr 2025 12:14:35 +0200 Subject: [PATCH 090/104] synchronize the clocks for the moment --- experiments/omip_prototype/sixth_degree_omip.jl | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/experiments/omip_prototype/sixth_degree_omip.jl b/experiments/omip_prototype/sixth_degree_omip.jl index 7a3ddd05..4da8f4ce 100644 --- a/experiments/omip_prototype/sixth_degree_omip.jl +++ b/experiments/omip_prototype/sixth_degree_omip.jl @@ -12,6 +12,15 @@ using ClimaSeaIce.SeaIceThermodynamics: IceWaterThermalEquilibrium using Printf using CUDA +function synch!(clock1::Clock, clock2) + # Synchronize the clocks + clock1.time = clock2.time + clock1.iteration = clock2.iteration + clock1.last_Δt = clock2.last_Δt +end + +synch!(model1, model2) = synch!(model1.clock, model2.clock) + arch = GPU() r_faces = ClimaOcean.exponential_z_faces(; Nz=60, depth=6200) z_faces = MutableVerticalDiscretization(r_faces) @@ -88,6 +97,12 @@ radiation = Radiation() omip = OceanSeaIceModel(ocean, sea_ice; atmosphere, radiation) omip = Simulation(omip, Δt=10minutes, stop_time=60*365days) +synch!(atmosphere, ocean.model) +synch!(sea_ice.model, ocean.model) +synch!(omip.model, atmosphere) + +@show omip.model.clock + # Figure out the outputs.... ocean.output_writers[:checkpointer] = Checkpointer(ocean.model, From 7975dd4494482f368989026e7f87ab083b8d899e Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 10 Apr 2025 13:57:29 +0200 Subject: [PATCH 091/104] Update test_jra55.jl --- test/test_jra55.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_jra55.jl b/test/test_jra55.jl index 77e7e158..929529c0 100644 --- a/test/test_jra55.jl +++ b/test/test_jra55.jl @@ -1,6 +1,7 @@ include("runtests_setup.jl") -using ClimaOcean.JRA55: download_JRA55_cache, JRA55MultipleYears +using ClimaOcean.JRA55 +using ClimaOcean.JRA55: download_JRA55_cache using ClimaOcean.OceanSeaIceModels: PrescribedAtmosphere @testset "JRA55 and data wrangling utilities" begin From 77e440b2264c315bf6601693e24d0ba854482bc8 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 10 Apr 2025 14:09:41 +0200 Subject: [PATCH 092/104] bugfix --- src/SeaIceSimulations.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SeaIceSimulations.jl b/src/SeaIceSimulations.jl index ec58108a..f51830ee 100644 --- a/src/SeaIceSimulations.jl +++ b/src/SeaIceSimulations.jl @@ -78,7 +78,7 @@ end function default_sea_ice_dynamics(grid; ocean) SSU = view(ocean.model.velocities.u, :, :, grid.Nz) - SSV = view(ocean.model.velocities.u, :, :, grid.Nz) + SSV = view(ocean.model.velocities.v, :, :, grid.Nz) τo = SemiImplicitStress(uₑ=SSU, vₑ=SSV) τua = Field{Face, Center, Nothing}(grid) From b05fb8393635e8ba793e76c5e2d59921c3e3eefb Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 10 Apr 2025 14:20:36 +0200 Subject: [PATCH 093/104] improve it quickly for now --- src/SeaIceSimulations.jl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/SeaIceSimulations.jl b/src/SeaIceSimulations.jl index f51830ee..b7d905a8 100644 --- a/src/SeaIceSimulations.jl +++ b/src/SeaIceSimulations.jl @@ -75,12 +75,16 @@ function sea_ice_simulation(grid; return sea_ice end -function default_sea_ice_dynamics(grid; ocean) +function default_sea_ice_dynamics(grid; + ocean, # Cannot do it without an ocean + sea_ice_ocean_drag_coefficient = 5.5e-3, + rheology = ElastoViscoPlasticRheology(), + solver = SplitExplicitSolver(120)) SSU = view(ocean.model.velocities.u, :, :, grid.Nz) SSV = view(ocean.model.velocities.v, :, :, grid.Nz) - τo = SemiImplicitStress(uₑ=SSU, vₑ=SSV) + τo = SemiImplicitStress(uₑ=SSU, vₑ=SSV, Cᴰ=sea_ice_ocean_drag_coefficient) τua = Field{Face, Center, Nothing}(grid) τva = Field{Center, Face, Nothing}(grid) @@ -89,8 +93,8 @@ function default_sea_ice_dynamics(grid; ocean) top_momentum_stress = (u=τua, v=τva), bottom_momentum_stress = τo, ocean_velocities = (u=0.1*SSU, v=0.1*SSV), - rheology = ElastoViscoPlasticRheology(), - solver = SplitExplicitSolver(120)) + rheology, + solver) end end \ No newline at end of file From 501b3fa6880365fb020153a6068c2c0ef55686d3 Mon Sep 17 00:00:00 2001 From: simone-silvestri Date: Thu, 10 Apr 2025 11:26:26 -0400 Subject: [PATCH 094/104] continue --- .../omip_prototype/sixth_degree_omip.jl | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/experiments/omip_prototype/sixth_degree_omip.jl b/experiments/omip_prototype/sixth_degree_omip.jl index 4da8f4ce..9d66a6ac 100644 --- a/experiments/omip_prototype/sixth_degree_omip.jl +++ b/experiments/omip_prototype/sixth_degree_omip.jl @@ -10,6 +10,7 @@ using ClimaOcean.JRA55 using ClimaOcean.DataWrangling using ClimaSeaIce.SeaIceThermodynamics: IceWaterThermalEquilibrium using Printf +using Dates using CUDA function synch!(clock1::Clock, clock2) @@ -55,8 +56,10 @@ ocean = ocean_simulation(grid; Δt=1minutes, free_surface, closure) -restart_file = "ocean_checkpoint_iteration130000.jld2" -set!(ocean.model, restart_file) +dataset = ECCO4Monthly() + +set!(ocean.model, T=Metadatum(:temperature; dataset), + S=Metadatum(:salinity; dataset)) ##### ##### A Prognostic Sea-ice model @@ -73,18 +76,15 @@ sea_ice = sea_ice_simulation(grid; bottom_heat_boundary_condition, dynamics = sea_ice_dynamics, advection=WENO(order=7)) -dataset = ECCO4Monthly() -date = DateTime(1992, 2, 1) # 1st Feb 1992 - -set!(sea_ice.model, h=Metadatum(:sea_ice_thickness; date, dataset), - ℵ=Metadatum(:sea_ice_concentration; date, dataset)) +set!(sea_ice.model, h=Metadatum(:sea_ice_thickness; dataset), + ℵ=Metadatum(:sea_ice_concentration; dataset)) ##### ##### A Prescribed Atmosphere model ##### dir = "./forcing_data" -dataset = MultiyearJRA55() +dataset = MultiYearJRA55() backend = JRA55NetCDFBackend(40) atmosphere = JRA55PrescribedAtmosphere(arch; dir, dataset, backend, include_rivers_and_icebergs=true) @@ -95,13 +95,7 @@ radiation = Radiation() ##### omip = OceanSeaIceModel(ocean, sea_ice; atmosphere, radiation) -omip = Simulation(omip, Δt=10minutes, stop_time=60*365days) - -synch!(atmosphere, ocean.model) -synch!(sea_ice.model, ocean.model) -synch!(omip.model, atmosphere) - -@show omip.model.clock +omip = Simulation(omip, Δt=20, stop_time=30days) # Figure out the outputs.... @@ -149,3 +143,8 @@ end add_callback!(omip, progress, IterationInterval(50)) run!(omip) + +omip.Δt = 10minutes +omip.stop_time = 58 * 365days + +run!(omip) From 5751899fd655cd6b632d93099a2e121f23a8d7c3 Mon Sep 17 00:00:00 2001 From: simone-silvestri Date: Mon, 14 Apr 2025 03:20:58 -0400 Subject: [PATCH 095/104] another try... --- .../omip_prototype/sixth_degree_omip.jl | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/experiments/omip_prototype/sixth_degree_omip.jl b/experiments/omip_prototype/sixth_degree_omip.jl index 9d66a6ac..6a482bb0 100644 --- a/experiments/omip_prototype/sixth_degree_omip.jl +++ b/experiments/omip_prototype/sixth_degree_omip.jl @@ -13,6 +13,8 @@ using Printf using Dates using CUDA +import Oceananigans.OutputWriters: checkpointer_address + function synch!(clock1::Clock, clock2) # Synchronize the clocks clock1.time = clock2.time @@ -42,13 +44,19 @@ grid = ImmersedBoundaryGrid(grid, GridFittedBottom(bottom_height); active_cells_ ##### A Propgnostic Ocean model ##### -# A very diffusive ocean +using Oceananigans.TurbulenceClosures: ExplicitTimeDiscretization +using Oceananigans.TurbulenceClosures.TKEBasedVerticalDiffusivities: CATKEVerticalDiffusivity, CATKEMixingLength, CATKEEquation + momentum_advection = WENOVectorInvariant() tracer_advection = WENO(order=7) free_surface = SplitExplicitFreeSurface(grid; substeps=70) -closure = (ClimaOcean.OceanSimulations.default_ocean_closure(), - VerticalScalarDiffusivity(κ=1e-5, ν=1e-5)) + +mixing_length = CATKEMixingLength(Cᵇ=0.01) +turbulent_kinetic_energy_equation = CATKEEquation(Cᵂϵ=1.0) + +catke_closure = CATKEVerticalDiffusivity(ExplicitTimeDiscretization(); mixing_length, turbulent_kinetic_energy_equation) +closure = (catke_closure, VerticalScalarDiffusivity(κ=1e-5, ν=1e-5)) ocean = ocean_simulation(grid; Δt=1minutes, momentum_advection, @@ -93,12 +101,14 @@ radiation = Radiation() ##### ##### An ocean-sea ice coupled model ##### - + omip = OceanSeaIceModel(ocean, sea_ice; atmosphere, radiation) -omip = Simulation(omip, Δt=20, stop_time=30days) +omip = Simulation(omip, Δt=20, stop_time=60days) # Figure out the outputs.... +checkpointer_address(::SeaIceModel) = "SeaIceModel" + ocean.output_writers[:checkpointer] = Checkpointer(ocean.model, schedule = IterationInterval(10000), prefix = "ocean_checkpoint", From af67adc8c2d66a4b977e6352e344c80ee09d00c7 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 24 Apr 2025 16:14:53 +0200 Subject: [PATCH 096/104] bugfix for the salt flux --- .../InterfaceComputations/assemble_net_fluxes.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl b/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl index c53e3613..d6380f69 100644 --- a/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl +++ b/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl @@ -134,7 +134,7 @@ end Jᵀao = ΣQao * ρₒ⁻¹ / cₒ Jˢao = - Sₒ * ΣFao Jᵀio = Qio * ρₒ⁻¹ / cₒ - Jˢio = sea_ice_ocean_fluxes.salt[i, j, 1] + Jˢio = sea_ice_ocean_fluxes.salt[i, j, 1] * ℵᵢ τxao = ℑxᶠᵃᵃ(i, j, 1, grid, τᶜᶜᶜ, ρₒ⁻¹, ℵ, ρτxao) τyao = ℑyᵃᶠᵃ(i, j, 1, grid, τᶜᶜᶜ, ρₒ⁻¹, ℵ, ρτyao) From 83f97e98230076bbedb8e3e5d7ca6bcc585870fb Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 30 Apr 2025 15:02:38 +0200 Subject: [PATCH 097/104] try it out --- .../InterfaceComputations/assemble_net_fluxes.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl b/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl index cb2327c4..8b9cc392 100644 --- a/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl +++ b/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl @@ -183,6 +183,7 @@ function compute_net_sea_ice_fluxes!(coupled_model) kernel_parameters = interface_kernel_parameters(grid) sea_ice_surface_temperature = coupled_model.interfaces.atmosphere_sea_ice_interface.temperature + ice_concentration = sea_ice_concentration(sea_ice) launch!(arch, grid, kernel_parameters, _assemble_net_sea_ice_fluxes!, @@ -192,6 +193,7 @@ function compute_net_sea_ice_fluxes!(coupled_model) clock, atmosphere_sea_ice_fluxes, sea_ice_ocean_fluxes, + ice_concentration, freshwater_flux, sea_ice_surface_temperature, downwelling_radiation, @@ -207,6 +209,7 @@ end clock, atmosphere_sea_ice_fluxes, sea_ice_ocean_fluxes, + ice_concentration, freshwater_flux, # Where do we add this one? surface_temperature, downwelling_radiation, @@ -220,6 +223,7 @@ end @inbounds begin Ts = surface_temperature[i, j, kᴺ] Ts = convert_to_kelvin(sea_ice_properties.temperature_units, Ts) + ℵi = ice_concentration[i, j, kᴺ] Qs = downwelling_radiation.Qs[i, j, 1] Qℓ = downwelling_radiation.Qℓ[i, j, 1] @@ -239,7 +243,7 @@ end Qu = upwelling_radiation(Ts, σ, ϵ) Qd = net_downwelling_radiation(i, j, grid, time, α, ϵ, Qs, Qℓ) - ΣQt = Qd + Qu + Qc + Qv + ΣQt = (Qd + Qu + Qc + Qv) * ℵi ΣQb = Qf + Qi # Mask fluxes over land for convenience From 03e7f19fed6cbdb50ab09d5327d2267fdfe9cd20 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 30 Apr 2025 15:03:52 +0200 Subject: [PATCH 098/104] correction --- .../InterfaceComputations/assemble_net_fluxes.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl b/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl index 8b9cc392..22381a4f 100644 --- a/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl +++ b/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl @@ -243,7 +243,7 @@ end Qu = upwelling_radiation(Ts, σ, ϵ) Qd = net_downwelling_radiation(i, j, grid, time, α, ϵ, Qs, Qℓ) - ΣQt = (Qd + Qu + Qc + Qv) * ℵi + ΣQt = (Qd + Qu + Qc + Qv) * ℵi # If ℵi == 0 there is no heat flux from the top! ΣQb = Qf + Qi # Mask fluxes over land for convenience From 1eedc44f89280b49f0bfc1de0398cb933ff05eec Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 14 May 2025 18:12:05 +0200 Subject: [PATCH 099/104] correct --- .../InterfaceComputations/assemble_net_fluxes.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl b/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl index 918873d2..dff50271 100644 --- a/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl +++ b/src/OceanSeaIceModels/InterfaceComputations/assemble_net_fluxes.jl @@ -231,7 +231,6 @@ function compute_net_sea_ice_fluxes!(coupled_model) clock, atmosphere_sea_ice_fluxes, sea_ice_ocean_fluxes, - ice_concentration, freshwater_flux, ice_concentration, sea_ice_surface_temperature, @@ -248,7 +247,6 @@ end clock, atmosphere_sea_ice_fluxes, sea_ice_ocean_fluxes, - ice_concentration, freshwater_flux, # Where do we add this one? ice_concentration, surface_temperature, From 202212c2f7f3231d8e5b9ec1971e2d0265ab1fb2 Mon Sep 17 00:00:00 2001 From: simone-silvestri Date: Thu, 22 May 2025 04:11:01 -0400 Subject: [PATCH 100/104] update --- src/SeaIceSimulations.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SeaIceSimulations.jl b/src/SeaIceSimulations.jl index 9222a238..18d979f0 100644 --- a/src/SeaIceSimulations.jl +++ b/src/SeaIceSimulations.jl @@ -97,4 +97,4 @@ function default_sea_ice_dynamics(grid; solver) end -end \ No newline at end of file +end From 0c8d8b17d5b5aaae4596b60d9239071011151a64 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 22 May 2025 10:36:22 +0200 Subject: [PATCH 101/104] go for it --- src/SeaIceSimulations.jl | 44 +++++++++++++++++++++++++++----- test/test_ocean_sea_ice_model.jl | 12 +-------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/SeaIceSimulations.jl b/src/SeaIceSimulations.jl index 58766da0..d204bd89 100644 --- a/src/SeaIceSimulations.jl +++ b/src/SeaIceSimulations.jl @@ -20,7 +20,7 @@ using ClimaSeaIce.SeaIceThermodynamics: IceWaterThermalEquilibrium using ClimaOcean.OceanSimulations: Default -function sea_ice_simulation(grid; +function sea_ice_simulation(grid, ocean=nothing; Δt = 5minutes, ice_salinity = 0, # psu advection = nothing, # for the moment @@ -28,8 +28,8 @@ function sea_ice_simulation(grid; ice_heat_capacity = 2100, # J kg⁻¹ K⁻¹ ice_consolidation_thickness = 0.05, # m ice_density = 900, # kg m⁻³ - dynamics = nothing, - bottom_heat_boundary_condition = IceWaterThermalEquilibrium(), + dynamics = default_sea_ice_dynamics(grid, ocean), + bottom_heat_boundary_condition = nothing, phase_transitions = PhaseTransitions(; ice_heat_capacity, ice_density), conductivity = 2, # kg m s⁻³ K⁻¹ internal_heat_flux = ConductiveFlux(; conductivity)) @@ -41,6 +41,15 @@ function sea_ice_simulation(grid; top_surface_temperature = Field{Center, Center, Nothing}(grid) top_heat_boundary_condition = PrescribedTemperature(top_surface_temperature) + if isnothing(bottom_heat_boundary_condition) + if isnothing(ocean) + surface_ocean_salinity = 0 + else + surface_ocean_salinity = view(ocean.model.tracers.S, :, :, size(ocean.model.grid, 3)) + end + bottom_heat_boundary_condition = IceWaterThermalEquilibrium(surface_ocean_salinity) + end + ice_thermodynamics = SlabSeaIceThermodynamics(grid; internal_heat_flux, phase_transitions, @@ -50,16 +59,12 @@ function sea_ice_simulation(grid; bottom_heat_flux = Field{Center, Center, Nothing}(grid) top_heat_flux = Field{Center, Center, Nothing}(grid) - # top_momentum_stress = (u = Field{Face, Center, Nothing}(grid), - # v = Field{Center, Face, Nothing}(grid)) - # Build the sea ice model sea_ice_model = SeaIceModel(grid; ice_salinity, advection, tracers, ice_consolidation_thickness, - # top_momentum_stress, ice_thermodynamics, dynamics, bottom_heat_flux, @@ -73,4 +78,29 @@ function sea_ice_simulation(grid; return sea_ice end +function default_sea_ice_dynamics(grid, ocean=nothing; # Cannot do it without an ocean + sea_ice_ocean_drag_coefficient = 5.5e-3, + rheology = ElastoViscoPlasticRheology(), + solver = SplitExplicitSolver(120)) + + if isnothing(ocean) + SSU = Oceananigans.Fields.ZeroField() + SSV = Oceananigans.Fields.ZeroField() + else + SSU = view(ocean.model.velocities.u, :, :, grid.Nz) + SSV = view(ocean.model.velocities.v, :, :, grid.Nz) + end + + τo = SemiImplicitStress(uₑ=SSU, vₑ=SSV, Cᴰ=sea_ice_ocean_drag_coefficient) + τua = Field{Face, Center, Nothing}(grid) + τva = Field{Center, Face, Nothing}(grid) + + return SeaIceMomentumEquation(grid; + coriolis = ocean.model.coriolis, + top_momentum_stress = (u=τua, v=τva), + bottom_momentum_stress = τo, + rheology, + solver) +end + end \ No newline at end of file diff --git a/test/test_ocean_sea_ice_model.jl b/test/test_ocean_sea_ice_model.jl index 71e9e416..0d1b044c 100644 --- a/test/test_ocean_sea_ice_model.jl +++ b/test/test_ocean_sea_ice_model.jl @@ -81,17 +81,7 @@ using ClimaSeaIce.Rheologies ##### Coupled ocean-sea ice and prescribed atmosphere ##### - # Adding a sea ice model to the coupled model - τua = Field{Face, Center, Nothing}(grid) - τva = Field{Center, Face, Nothing}(grid) - - dynamics = SeaIceMomentumEquation(grid; - coriolis = ocean.model.coriolis, - top_momentum_stress = (u=τua, v=τva), - rheology = ElastoViscoPlasticRheology(), - solver = SplitExplicitSolver(120)) - - sea_ice = sea_ice_simulation(grid; dynamics, advection=WENO(order=7)) + sea_ice = sea_ice_simulation(grid, ocean; advection=WENO(order=7)) liquidus = sea_ice.model.ice_thermodynamics.phase_transitions.liquidus # Set the ocean temperature and salinity From 0f04d0fdf55645472485b73dea9e3fa1f3fc78f8 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 22 May 2025 10:37:32 +0200 Subject: [PATCH 102/104] Update src/SeaIceSimulations.jl --- src/SeaIceSimulations.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SeaIceSimulations.jl b/src/SeaIceSimulations.jl index d204bd89..9e123471 100644 --- a/src/SeaIceSimulations.jl +++ b/src/SeaIceSimulations.jl @@ -103,4 +103,4 @@ function default_sea_ice_dynamics(grid, ocean=nothing; # Cannot do it without an solver) end -end \ No newline at end of file +end From e28b9a4b429b880b043b7bd0194c9be9b16b9aed Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 22 May 2025 11:35:25 +0200 Subject: [PATCH 103/104] Update SeaIceSimulations.jl --- src/SeaIceSimulations.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SeaIceSimulations.jl b/src/SeaIceSimulations.jl index 9e123471..47cdf32c 100644 --- a/src/SeaIceSimulations.jl +++ b/src/SeaIceSimulations.jl @@ -78,7 +78,7 @@ function sea_ice_simulation(grid, ocean=nothing; return sea_ice end -function default_sea_ice_dynamics(grid, ocean=nothing; # Cannot do it without an ocean +function default_sea_ice_dynamics(grid, ocean=nothing; sea_ice_ocean_drag_coefficient = 5.5e-3, rheology = ElastoViscoPlasticRheology(), solver = SplitExplicitSolver(120)) From 2eeed4c1713128babf7c06a8f7659f798cb00a10 Mon Sep 17 00:00:00 2001 From: simone-silvestri Date: Thu, 22 May 2025 06:11:37 -0400 Subject: [PATCH 104/104] some.. salinity --- src/SeaIceSimulations.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SeaIceSimulations.jl b/src/SeaIceSimulations.jl index 943a7e72..f56dddf0 100644 --- a/src/SeaIceSimulations.jl +++ b/src/SeaIceSimulations.jl @@ -25,7 +25,7 @@ using ClimaOcean.OceanSimulations: Default function sea_ice_simulation(grid, ocean=nothing; Δt = 5minutes, - ice_salinity = 0, # psu + ice_salinity = 4, # psu advection = nothing, # for the moment tracers = (), ice_heat_capacity = 2100, # J kg⁻¹ K⁻¹