ConservativeRegridding.jl provides functionality to regrid between two grids. A grid is a tessellation of a space into polygons (or grid cells) each with an associated value. Data on a grid is then referred to as field whereas the grid itself just defines that tessellation. Regridding is performed conservatively, meaning the area-weighted mean is preserved. This is achieved by computing the intersection areas of all combinations between grid cells of one grid and the other. All those intersections then provide the respective weights to average from neighbouring cells.
Let grid2 (destination), grid1 (source) be two arrays describing the polygon vertices on each grid.
Generally this is a vector of polygons (a vector of a vector of coordinate tuples) but this may also be
arranged as a matrix, with each column being one polygon, then
using ConservativeRegridding
R = ConservativeRegridding.Regridder(grid2, grid1) # grid1 is source, grid2 the destinationAnd two fields field2 (destination), field1 (source) of data on these grids (normally a vector
unravelling the grid cells in the same order as the polygons are defined in grid1, grid2 above)
are then regridded via
ConservativeRegridding.regrid!(field2, R, field1)the re-use the regridder R for interpolation in the other direction (from field2 to field1)
use
ConservativeRegridding.regrid!(field1, transpose(R), field2)Note that R, transpose(R) share the same underlying data, transpose(R) just creates a
new regridder based on a matrix tranpose and by swapping the area vectors, see below.
Let us tessellate the [0, 2] x [0, 2] space in two ways, blue grid and orange grid.
These grids are defined through their polygons, namely
# blue grid
blue_grid = [[(1, 1), (1, 0), (0, 0), (0, 1), (1, 1)], # polygon 1
[(2, 1), (2, 0), (1, 0), (1, 1), (2, 1)], # polygon 2
[(1, 2), (1, 1), (0, 1), (0, 2), (1, 2)], # polygon 3
[(2, 2), (2, 1), (1, 1), (1, 2), (2, 2)]] # polygon 4
# orange grid
orange_grid = [[(0, 2), (1, 2), (0, 1), (0, 2)], # polygon 1
[(1, 2), (2, 2), (2, 1), (1, 2)], # polygon 2
[(0, 1), (1, 2), (2, 1), (1, 0), (0, 1)], # polygon 3
[(0, 0), (0, 1), (1, 0), (0, 0)], # polygon 4
[(1, 0), (2, 1), (2, 0), (1, 0)]] # polygon 5Note how all polygons are closed by repeating the first vertex as the last. The regridder from 5-element orange grid to 4-element blue grid is now
julia> R = ConservativeRegridding.Regridder(blue_grid, orange_grid, normalize=false)
4×5 Regridder{SparseArrays.SparseMatrixCSC{Float64, Int64}, Vector{Float64}}
⋅ ⋅ 0.5 0.5 ⋅
⋅ ⋅ 0.5 ⋅ 0.5
0.5 ⋅ 0.5 ⋅ ⋅
⋅ 0.5 0.5 ⋅ ⋅
Source areas: [0.5, 0.5, 2.0, 0.5, 0.5]
Dest. areas: [1.0, 1.0, 1.0, 1.0]We disabled the area normalization with normalize=false as the units of the
areas are technically irrelevant, but here it shows nicely how every intersect
has the area of 1/2.
Start with some data on orange, allocate blue, and regrid
orange = rand(5)
blue = similar(orange, 4)
ConservativeRegridding.regrid!(blue, R, orange)Now we test whether the mean is conserved
julia> sum(orange .* R.src_areas) / sum(R.src_areas)
0.4120316695084635
julia> sum(blue .* R.dst_areas) / sum(R.dst_areas)
0.4120316695084635Indeed, even exactly. In this case with two very simple grids and intersects there is not even rounding error which however has to be expected in general.
The backwards regridding via tranpose(::Regridder) is
julia> orange_tilde = similar(orange)
julia> ConservativeRegridding.regrid!(orange_tilde, transpose(R), blue)
5-element Vector{Float64}:
0.48307071648257566
0.49377895828829815
0.4120316695084635
0.30573011277148604
0.3655468904914942
julia> orange
5-element Vector{Float64}:
0.9108833154485086
0.9322997990599536
0.05525811751664267
0.5562021080263294
0.6758356634663457still conserves the mean but has clearly lost in variance.
Let matrix
After the matrix-vector multiplication
While the division with
The tilde for
Conservative regridding means that the area-weighted means are conserved
Which however requires that both grids cover the same area and