Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Var.slice_intp #176

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ new_var = ClimaAnalysis.convert_dim_units(
)
```

### Slicing by interpolating
Slicing is possible by nearest value using `ClimaAnalysis.slice`. There is now support by
slicing using linear interpolation through `ClimaAnalysis.slice_intp`. See the example
below.

```julia
ClimaAnalysis.slice_intp(var, lat = 30, lon = 20, time = 100)
```

## Bug fixes
- `Atmos.to_pressure_coordinates` now works with Unitful units.
- `Atmos.to_pressure_coordinates` now uses reasonable pressure values when `target_pressure`
Expand Down
1 change: 1 addition & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Var.long_name
Var.units
Var.has_units
Var.slice
Var.slice_intp
Var.average_lat
Var.weighted_average_lat
Var.average_lon
Expand Down
73 changes: 73 additions & 0 deletions src/Var.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
average_time,
is_z_1D,
slice,
slice_intp,
window,
arecompatible,
center_longitude!,
Expand Down Expand Up @@ -931,6 +932,9 @@

Return a new OutputVar by slicing across dimensions as defined by the keyword arguments.

Slicing is done by using the nearest value. See [`slice_intp`](@ref) for slicing by linear
interpolation.

Example
===========
```julia
Expand All @@ -945,6 +949,75 @@
return sliced_var
end

"""
_slice_intp_general(var::OutputVar, val, dim_name)

Return a new OutputVar by interpolating to the given `val` for the given dimension and
slicing across it.
"""
function _slice_intp_general(var::OutputVar, val, dim_name)
haskey(var.dims, dim_name) ||
error("Var does not have dimension $dim_name, found $(keys(var.dims))")

reduced_var = _reduce_over(_slice_intp_over, dim_name, var, var, val)

# Let's try adding this operation to the long_name, if possible (ie, if the correct
# attributes are available)
if haskey(var.attributes, "long_name") &&
haskey(var.dim_attributes, dim_name) &&
haskey(var.dim_attributes[dim_name], "units")
dim_units = var.dim_attributes[dim_name]["units"]
if (dim_name == "time" || dim_name == "t") && dim_units == "s"

Check warning on line 970 in src/Var.jl

View check run for this annotation

Codecov / codecov/patch

src/Var.jl#L969-L970

Added lines #L969 - L970 were not covered by tests
# Dimension is time and units are seconds. Let's convert them to something nicer
pretty_timestr = seconds_to_prettystr(val)
reduced_var.attributes["long_name"] *= " $dim_name = $pretty_timestr"

Check warning on line 973 in src/Var.jl

View check run for this annotation

Codecov / codecov/patch

src/Var.jl#L972-L973

Added lines #L972 - L973 were not covered by tests
else
reduced_var.attributes["long_name"] *= " $dim_name = $val $dim_units"

Check warning on line 975 in src/Var.jl

View check run for this annotation

Codecov / codecov/patch

src/Var.jl#L975

Added line #L975 was not covered by tests
end
reduced_var.attributes["slice_$dim_name"] = "$val"
reduced_var.attributes["slice_$(dim_name)_units"] = dim_units

Check warning on line 978 in src/Var.jl

View check run for this annotation

Codecov / codecov/patch

src/Var.jl#L977-L978

Added lines #L977 - L978 were not covered by tests
end
return reduced_var
end

"""
_slice_intp_over(data, var::OutputVar, val; dims)

Slice a `OutputVar` by using its interpolat at `val` for the dimension whose index is
`dims`.

The argument `data` is included, but we do not use `data` in the function. This is because
`_reduce_over` requires the first argument to be `data`.
"""
function _slice_intp_over(data, var::OutputVar, val; dims)
dim_arrays = [values(var.dims)...]
dim_arrays[dims] = [val]
ret_data = [var(pt) for pt in Base.product(dim_arrays...)]
return ret_data
end

"""
slice_intp(var::OutputVar, kwargs...)

Return a new OutputVar by slicing across dimensions as defined by the keyword arguments.

Slicing is done by linear interpolation. See [`slice`](@ref) for slicing by using the
nearest value.

Example
===========
```julia
slice_intp(var, lat = 30, lon = 20, time = 100)
```
"""
function slice_intp(var::OutputVar; kwargs...)
sliced_var = var
for (dim_name, val) in kwargs
sliced_var = _slice_intp_general(sliced_var, val, String(dim_name))
end
return sliced_var
end

"""
window(var::OutputVar, dim_name; left = nothing, right = nothing)

Expand Down
19 changes: 19 additions & 0 deletions test/test_Var.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1945,3 +1945,22 @@ end
"rads",
)
end

@testset "Slice by interpolating" begin
lat = collect(range(-89.5, 89.5, 180))
lon = collect(range(-42.0, 42.0, 360))
data = reshape(collect(1:(360 * 180)), (180, 360))
dims = OrderedDict(["lat" => lat, "lon" => lon])
attribs = Dict("long_name" => "hi")
dim_attribs = OrderedDict(["lon" => Dict("units" => "deg")])
var = ClimaAnalysis.OutputVar(attribs, dims, dim_attribs, data)

# Check slice and slice_intp produces the same result when the value already exists
var_nearest = ClimaAnalysis.slice(var, lat = lat[1])
var_intp = ClimaAnalysis.slice_intp(var, lat = lat[1])

@test var_nearest.dims == var_intp.dims
@test var_nearest.data == var_intp.data
@test var_nearest.attributes == var_intp.attributes
@test var_nearest.dim_attributes == var_nearest.dim_attributes
end