The land-sea mask

The following describes how a custom land-sea mask can be defined. SpeedyWeather uses a fractional land-sea mask, i.e. for every grid-point

  • 1 indicates land
  • 0 indicates ocean
  • a value in between indicates a grid-cell partially covered by ocean and land

Setting the land-sea mask to ocean therefore will disable any fluxes that may come from land, and vice versa. However, with an ocean-everywhere land-sea mask you must also define sea surface temperatures everywhere, otherwise the fluxes in those regions will be zero.

For more details, see Surface fluxes and the Land-sea mask section therein.

Manual land-sea mask

You can create the default land-sea mask as follows

using SpeedyWeather
spectral_grid = SpectralGrid(trunc=31, nlev=8)
land_sea_mask = LandSeaMask(spectral_grid)
LandSeaMask{Float32, OctahedralGaussianGrid{Float32}} <: AbstractLandSeaMask
├ path::String = SpeedyWeather.jl/input_data
├ file::String = land-sea_mask.nc
├ file_Grid::UnionAll = FullClenshawGrid
└── arrays: mask

which will automatically interpolate the land-sea mask onto grid and resolution as defined in spectral_grid at initialization. The actual mask is in land_sea_mask.mask and you can visualise it with

model = PrimitiveWetModel(;spectral_grid, land_sea_mask)
simulation = initialize!(model)     # triggers also initialization of model.land_sea_mask
plot(land_sea_mask.mask)
                   48-ring OctahedralGaussianGrid{Float32}                
       ┌────────────────────────────────────────────────────────────┐  1  
    90 ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ┌──┐
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
    ˚N ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
   -90 ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ └──┘
       └────────────────────────────────────────────────────────────┘  0  
        0                           ˚E                           360      

Now before you run a simulation you could manually change the land-sea mask by

# unpack, this is a flat copy, changing it will also change the mask inside model
(; mask) = land_sea_mask

# ocean everywhere, or
mask .= 0

# random land-sea mask, or
for i in eachindex(mask)
    mask[i] = rand()
end

# ocean only between 10˚S and 10˚N
for (j, ring) in enumerate(RingGrids.eachring(mask))
    for ij in ring
        mask[ij] = abs(model.geometry.latd[j]) > 10 ? 1 : 0
    end
end

And now you can run the simulation as usual with run!(simulation). Most useful for the generation of custom land-sea masks in this manual way is probably the model.geometry component which has all sorts of coordinates like latd (latitudes in degrees on rings) or latds, londs (latitude, longitude in degrees for every grid point).

Earth's land-sea mask

The Earth's LandSeaMask has itself the option to load another land-sea mask from file, but you also have to specify the grid that mask from files comes on. It will then attempt to read it via NCDatasets and interpolate onto the model grid.

AquaPlanetMask

Predefined is also the AquaPlanetMask which can be created as

land_sea_mask = AquaPlanetMask(spectral_grid)
AquaPlanetMask{Float32, OctahedralGaussianGrid{Float32}} <: AbstractLandSeaMask
└── arrays: mask

and is equivalent to using Earth's LandSeaMask but setting the entire mask to zero afterwards land_sea_mask.mask .= 0.

Custom land-sea mask

Every (custom) land-sea mask has to be a subtype of AbstractLandSeaMask. A custom land-sea mask has to be defined as a new type (struct or mutable struct)

CustomMask{NF<:AbstractFloat, Grid<:AbstractGrid{NF}} <: AbstractLandSeaMask{NF, Grid}

and needs to have at least a field called mask::Grid that uses a Grid as defined by the spectral grid object, so of correct size and with the number format NF. All AbstractLandSeaMask have a convenient generator function to be used like mask = CustomMask(spectral_grid, option=argument), but you may add your own or customize by defining CustomMask(args...) which should return an instance of type CustomMask{NF, Grid} with parameters matching the spectral grid. Then the initialize function has to be extended for that new mask

initialize!(mask::CustomMask, model::PrimitiveEquation)

which generally is used to tweak the mask.mask grid as you like, using any other options you have included in CustomMask as fields or anything else (preferably read-only, because this is only to initialize the land-sea mask, nothing else) from model. You can for example read something from file, set some values manually, or use coordinates from model.geometry.

Time-dependent land-sea mask

It is possible to define an intrusive callback to change the land-sea mask during integration. The grid in model.land_sea_mask.mask is mutable, meaning you can change the values of grid points in-place but not replace the entire mask or change its size. If that mask is changed, this will be reflected in all relevant model components. For example, we can define a callback that floods the entire planet at the beginning of the 21st century as

Base.@kwdef struct MilleniumFlood <: SpeedyWeather.AbstractCallback
    schedule::Schedule = Schedule(DateTime(2000,1,1))
end

# initialize the schedule
function SpeedyWeather.initialize!(
    callback::MilleniumFlood,
    progn::PrognosticVariables,
    diagn::DiagnosticVariables,
    model::ModelSetup,
)
    initialize!(callback.schedule, progn.clock)
end

function SpeedyWeather.callback!(
    callback::MilleniumFlood,
    progn::PrognosticVariables,
    diagn::DiagnosticVariables,
    model::ModelSetup,
)
    # escape immediately if not scheduled yet
    isscheduled(callback.schedule, progn.clock) || return nothing

    # otherwise set the entire land-sea mask to ocean
    model.land_sea_mask.mask .= 0
    @info "Everything flooded on $(progn.clock.time)"
end

# nothing needs to be done when finishing
SpeedyWeather.finish!(::MilleniumFlood, args...) = nothing

Note that the flooding will take place only at the start of the 21st century, last indefinitely, but not if the model integration period does not cover that exact event, see Schedules. Initializing a model a few days earlier would then have this MilleniumFlood take place

land_sea_mask = LandSeaMask(spectral_grid)      # start with Earth's land-sea mask
model = PrimitiveWetModel(;spectral_grid, land_sea_mask)
add!(model, MilleniumFlood())   # or MilleniumFlood(::DateTime) for any non-default date

simulation = initialize!(model, time=DateTime(1999,12,29))
run!(simulation, period=Day(5))
plot(model.land_sea_mask.mask)
                   48-ring OctahedralGaussianGrid{Float32}                
       ┌────────────────────────────────────────────────────────────┐  0  
    90 ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ┌──┐
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
    ˚N ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄
   -90 ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ └──┘
       └────────────────────────────────────────────────────────────┘  0  
        0                           ˚E                           360      

And the land-sea mask has succesfully been set to ocean everywhere at the start of the 21st century. Note that while we added an @info line into the callback! function, this is here not printed because of how the Documenter works. If you execute this in the REPL you'll see it.