SpeedyWeather / SpeedyWeather.jl

Play atmospheric modelling like it's LEGO.
https://speedyweather.github.io/SpeedyWeather.jl/dev
MIT License
400 stars 24 forks source link

Exports more gradient operators from SpeedyTransforms #523

Closed milankl closed 2 months ago

milankl commented 2 months ago

@miniufo as discussed some convience functions to compute the gradient of a LowerTriangularMatrix or AbstractGrid. Also other operators ∇ ∇² ∇! ∇⁻² ∇²! ∇⁻²! are now all exported. Gradient can be used as

# create some data with wavenumbers 0,1,2,3,4
L = randn(LowerTriangularMatrix{ComplexF32}, 33, 32)
spectral_truncation!(L, 5)

# now assume you have that data in grid-point space as G
G = gridded(L)

# calculate the gradient (optionally pass on a spectral transform as second argument for performance)
dGdx, dGdy = ∇(G)

as always, radius scaling is omitted, so you'd need to /radius manually. Results in

image image

More methods in the docstring

help?> ∇
"∇" can be typed by \del<tab>

search: ∇ ∇² ∇! ∇⁻² ∇²! ∇⁻²!

  ∇(
      grid::Main.SpeedyWeather.RingGrids.AbstractGrid,
      S::SpectralTransform
  ) -> Tuple{Any, Any}

  The zonal and meridional gradient of grid. Transform to spectral space, takes the gradient
  and unscales the 1/coslat scaling in the gradient, but omits the 1/radius scaling that has to
  be applied manually. Makes use of an existing spectral transform S.

  ─────────────────────────────────────────────────────────────────────────────────────────────

  ∇(
      grid::Main.SpeedyWeather.RingGrids.AbstractGrid
  ) -> Tuple{Any, Any}

  The zonal and meridional gradient of grid. Transform to spectral space, takes the gradient
  and unscales the 1/coslat scaling in the gradient, but omits the 1/radius scaling that has to
  be applied manually.

  ─────────────────────────────────────────────────────────────────────────────────────────────

  ∇(p::LowerTriangularMatrix)

  The zonal and meridional gradient of p. Precomputes a SpectralTransform S. The
  1/radius-scaling is omitted, as is the 1/coslat scaling.

  ─────────────────────────────────────────────────────────────────────────────────────────────

  ∇(
      p::LowerTriangularMatrix,
      S::SpectralTransform
  ) -> Tuple{LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat, LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat}

  The zonal and meridional gradient of p using an existing SpectralTransform S. The
  1/radius-scaling is omitted, as is the 1/coslat scaling.
miniufo commented 2 months ago

That's great! Now it looks very close to the math expression.

milankl commented 2 months ago

Yes that's intended but I'm still struggling to make our functions consistent with respect to radius and or coslat scaling. Maybe you have ideas/opinions on that, for context

Now for the user-facing functions I keep facing questions of

Feel free to let us know what you would find most intuitive and clear or what you think a general user would expect?

miniufo commented 2 months ago

Yes, I see your "struggling" too. When I design some gradient APIs in python, I need to add a kwarg as

grdx, grdy = gradient(v, Rearth=6371e3)

so that users could change the default radius of earth (like scaling or not here, they may choose a slightly value of Rearth).

In such function-style APIs, one needs this "extra" kwarg in every function (gradient, laplacian, divergence etc.), which is bit annoying. So a solution would be define an Application class (OOP-style), and remember users' choice at the start when Application is initialized. I do this sometimes. I also tried to define some internal function with long list of args so that it is flexible for developers (e.g., grdx, grdy, grdz = __gradient(v, dims=['x', 'y', 'x'], Rearth=6371e3)), and then exposed them to users with pre-defined default parameters (e.g., grdx, grdy = hgrad(v, Rearth=6371e3), which uses __gradient) so that many details are hidden.

I feel the case here is a bit similar. For users with gridded data, I feel like hidding all the scaling is better, but also allow users to change Rearth, as users do not need to know the details of the spectral transform. For (advanced) users dealing with spectral data, hidding all scaling is not possible, as cos-scaling is intrinsic in spectral method (for velocity, and users need to know this). So let users take this responsibility should be fine. Although this cos/radius scaling should not be an overhead for diagnostic purpose, it is much better to store the pre-computed spectral coefficient for faster calculation (this seems to fit the OOP-style).

As one of the users, I currently prefer:

  1. Grid in/grid out, without lossing the information/accuracy;
  2. Scaling, and other details of spectral transform, being transparent to me;
  3. APIs being as simple as possible (not very long list of arguments), and close to maths;

Glad you share your thoughts, but I feel like never find a perfect solution (╥﹏╥).

milankl commented 2 months ago

Awesome thanks for the input, I've put this example into the docs: https://speedyweather.github.io/SpeedyWeather.jl/previews/PR523/gradients/