-
Notifications
You must be signed in to change notification settings - Fork 33
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
global spectrum #610
Comments
Check Power spectrum and the |
Great! I will take a look at it and report back. Just curious, Does the power spectrum here, based on spherical harmonics, only give a scalar spectrum, not zonal and meridional ones (sometimes one may need something like this)? However, those harmonics are composed by zonal FFT and meridional Legendre eigen-functions. So I am trying to figure out the conceptual picture. |
You could take |
I have try to get the spectra of # add callbacks for power spectrum
function spectrum_diagnostics(diagn::DiagnosticVariables, model::AbstractModel)
u = diagn.grid.u_grid[:, 1].data
v = diagn.grid.v_grid[:, 1].data
η = diagn.grid.pres_grid.data
umap = FullClenshawGrid(u, input_as=Matrix) # is this efficient repeating three times???
vmap = FullClenshawGrid(v, input_as=Matrix)
ηmap = FullClenshawGrid(η, input_as=Matrix)
uspec = SpeedyTransforms.power_spectrum(transform(umap))
vspec = SpeedyTransforms.power_spectrum(transform(vmap))
hspec = SpeedyTransforms.power_spectrum(transform(ηmap))
return uspec, vspec, hspec
end
# define a SpectrumDiagnostics callback and the fields it needs
Base.@kwdef mutable struct SpectrumDiagnostics <: SpeedyWeather.AbstractCallback
timestep_counter::Int = 0
time::Vector{DateTime} = []
uspec::Vector{Float64} = [] # u spectrum
vspec::Vector{Float64} = [] # v spectrum
hspec::Vector{Float64} = [] # η spectrum
end
# define how to initialize a SpectrumDiagnostics callback
function SpeedyWeather.initialize!(
callback::SpectrumDiagnostics,
progn::PrognosticVariables,
diagn::DiagnosticVariables,
model::AbstractModel,
)
# replace with vector of correct length
n = progn.clock.n_timesteps + 1 # +1 for initial conditions
callback.time = zeros(DateTime, n)
uspec, vspec, hspec = spectrum_diagnostics(diagn, model)
callback.uspec = zeros(n) # how to get the right size???
callback.vspec = zeros(n)
callback.hspec = zeros(n)
callback.time[1] = progn.clock.time
callback.uspec[1] = uspec # array assignment could be wrong???
callback.vspec[1] = vspec #
callback.hspec[1] = hspec #
callback.timestep_counter = 1 # (re)set counter to 1
return nothing
end
# define what a GlobalDiagnostics callback does on every time step
function SpeedyWeather.callback!(
callback::SpectrumDiagnostics,
progn::PrognosticVariables,
diagn::DiagnosticVariables,
model::AbstractModel,
)
callback.timestep_counter += 1
i = callback.timestep_counter
uspec, vspec, hspec = spectrum_diagnostics(diagn, model)
# store current time and diagnostics for timestep i
callback.time[i] = progn.clock.time
callback.uspec[1] = uspec
callback.vspec[1] = vspec
callback.hspec[1] = hspec
end
# define how to finish a GlobalDiagnostics callback after simulation finished
function SpeedyWeather.finalize!(
callback::SpectrumDiagnostics,
progn::PrognosticVariables,
diagn::DiagnosticVariables,
model::AbstractModel,
)
n_timesteps = callback.timestep_counter
# create a netCDF file in current path
ds = NCDataset(joinpath(pwd(), "spectrum_diagnostics.nc"), "c")
# save diagnostics variables within
defDim(ds, "time" , n_timesteps)
defVar(ds, "time" , callback.time , ("time",))
defVar(ds, "uspec" , callback.uspec, ("time",))
defVar(ds, "vspec" , callback.vspec, ("time",))
defVar(ds, "hspec" , callback.hspec, ("time",))
close(ds)
return nothing
end
spectrum_diagnostics_recorder = SpectrumDiagnostics()
add!(model.callbacks, :diagnostics_recorder => spectrum_diagnostics_recorder) The power spectrum codes are copied from here. I am not sure about:
After this, I should have gridded fields and spectra, which makes it possible to validate Parseval/Plancherel theorem |
I think you have two options
For 1) julia> progn
PrognosticVariables{Float32, Array}
├ vor: T31, 1-layer, 2-steps LowerTriangularArray{Float32}
├ div: T31, 1-layer, 2-steps LowerTriangularArray{Float32}
├ temp: T31, 1-layer, 2-steps LowerTriangularArray{Float32}
├ humid: T31, 1-layer, 2-steps LowerTriangularArray{Float32}
├ pres: T31, 1-layer, 2-steps LowerTriangularArray{Float32}
├ random_pattern: T31, 1-layer LowerTriangularArray{Float32}
├┐ocean: PrognosticVariablesOcean{Float32}
│├ sea_surface_temperature: 48-ring OctahedralGaussianGrid{Float32}
│└ sea_ice_concentration: 48-ring OctahedralGaussianGrid{Float32}
├┐land: PrognosticVariablesLand{Float32}
│├ land_surface_temperature: 48-ring OctahedralGaussianGrid{Float32}
│├ snow_depth: 48-ring OctahedralGaussianGrid{Float32}
│├ soil_moisture_layer1: 48-ring OctahedralGaussianGrid{Float32}
│└ soil_moisture_layer2: 48-ring OctahedralGaussianGrid{Float32}
├ particles: 0-element Vector{Particle{Float32}}
├ scale: 1.0
└ clock: 2000-01-01T00:00:00
julia> SpeedyTransforms.power_spectrum(progn.pres[1])
32-element Vector{Float32}:
1.3960999
226390.88
...
0.24390882
0.21223953 This would be the power spectrum of For 2) run with ds = NCDataset("run_0001/output.nc") # or other run id, you can also use `model.output.id` (="0001" here), i.e. "run_$(model.output.id)/output.nc"
# load u data, use .var to ignore possible "missing", use [:, :, 1, :] to remove singleton dimension for vertical
u = ds["u"].var[:, :, 1, :]
# wrap into `FullGaussianGrid`, this doesn't copy any data! = it's efficient.
u_grid = FullGaussianGrid(u, input_as=Matrix);
# transform to spectral space
u_spec = transform(u_grid)
# you can also create and reuse a spectral transform object
S = SpectralTransform(u_grid)
u_spec = transform(u_grid, S)
# now compute the spectra, one for each time step in the netcdf file
spectra = power_spectrum(u_spec) which returns a lmax x ntimesteps matrix with one spectrum per timestep. This however only works with #618 without that you'd need to manually loop over the timesteps like so |
Didn't realize that the prognostic variables are spectral and diagnostic ones are gridded. I think your options 2) is more flexiable for the purpose, just hoping that the write-out and read-in do not lose any information as the on-the-fly calculation. I played with my own dataset: ds = NCDataset("/Data/Speedy/SWMrun/run_T127/output.nc")
u = ds["u"].var[:, :, 1, :]
u_grid = FullGaussianGrid(u, input_as=Matrix) # u[x, y, t] is 512 * 255 * 201 but it showed: 130560×201 Matrix{Float32} cannot be used to create a 256-ring FullGaussianArray{Float32, 2, Matrix{Float32}}
Stacktrace:
[1] error(s::String)
@ Base ./error.jl:35
[2] error_message(data::Matrix{Float32}, nlat_half::Int64, rings::Vector{UnitRange{Int64}}, G::Type, T::Type, N::Int64, A::Type)
@ SpeedyWeather.RingGrids ~/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/general.jl:107
[3] FullGaussianArray
@ [~/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/grids/full_gaussian.jl:19](http://192.168.1.105:8888/home/qianyk/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/grids/full_gaussian.jl#line=18) [inlined]
[4] AbstractGridArray
@ [~/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/general.jl:115](http://192.168.1.105:8888/home/qianyk/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/general.jl#line=114) [inlined]
[5] (FullGaussianGrid)(data::Matrix{Float32}, input_as::Type{Vector})
@ SpeedyWeather.RingGrids ~/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/general.jl:124
[6] AbstractFullGridArray
@ [~/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/full_grids.jl:29](http://192.168.1.105:8888/home/qianyk/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/full_grids.jl#line=28) [inlined]
[7] AbstractFullGridArray
@ [~/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/full_grids.jl:34](http://192.168.1.105:8888/home/qianyk/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/full_grids.jl#line=33) [inlined]
[8] (FullGaussianGrid)(M::Array{Float32, 3}; input_as::Type)
@ SpeedyWeather.RingGrids [~/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/full_grids.jl:29](http://192.168.1.105:8888/home/qianyk/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/full_grids.jl#line=28)
[9] top-level scope
@ In[27]:5 I changed to: ds = NCDataset("/Data/Speedy/SWMrun/run_T127/output.nc")
u = ds["u"].var[:, :, 1, :]
u_grid = FullGaussianGrid(u[:,:,1], input_as=Matrix) # u[x, y] is 512 * 255 but it showed: 130560-element Vector{Float32} cannot be used to create a 256-ring FullGaussianArray{Float32, 1, Vector{Float32}}
Stacktrace:
[1] error(s::String)
@ Base ./error.jl:35
[2] error_message(data::Vector{Float32}, nlat_half::Int64, rings::Vector{UnitRange{Int64}}, G::Type, T::Type, N::Int64, A::Type)
@ SpeedyWeather.RingGrids ~/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/general.jl:107
[3] FullGaussianArray
@ [~/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/grids/full_gaussian.jl:19](http://192.168.1.105:8888/home/qianyk/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/grids/full_gaussian.jl#line=18) [inlined]
[4] AbstractGridArray
@ [~/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/general.jl:115](http://192.168.1.105:8888/home/qianyk/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/general.jl#line=114) [inlined]
[5] (FullGaussianGrid)(data::Vector{Float32}, input_as::Type{Vector})
@ SpeedyWeather.RingGrids ~/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/general.jl:124
[6] AbstractFullGridArray
@ [~/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/full_grids.jl:29](http://192.168.1.105:8888/home/qianyk/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/full_grids.jl#line=28) [inlined]
[7] AbstractFullGridArray
@ [~/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/full_grids.jl:34](http://192.168.1.105:8888/home/qianyk/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/full_grids.jl#line=33) [inlined]
[8] (FullGaussianGrid)(M::Matrix{Float32}; input_as::Type)
@ SpeedyWeather.RingGrids [~/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/full_grids.jl:29](http://192.168.1.105:8888/home/qianyk/.julia/packages/SpeedyWeather/7mNUb/src/RingGrids/full_grids.jl#line=28)
[9] top-level scope
@ In[28]:5 Does this relate to the version of my speedy? |
It sounds like you're using one type of grid for output (maybe |
I agree that the error message isn't the most informative. Maybe we should just have a function like |
OK, you are right. I see the inconsistency. So the Gaussian grid is recommended. I just don't remember why I used Then how to sumup all the energy on a Gaussian grid? For an equal lat-lon grid, I used
Great, that's really thinking on the user side! But I am not sure how to do this. Maybe we could store the grid type as a global netcdf attribute. |
Yes, that's technically all that's needed an then a convenience function around the functionality of NCDatasets.jl
you can do that too, the clenshaw grids are exact with dealiasing >=3. But that also makes the grid bigger and therefore increases the netCDF files as well as makes the model slower.
Best as before, transform to spectral space and use the |
Can we use reduced grid during model integration and then output with a full grid so that we can both make the model faster and keep output as exact as possible? We also tried to verify the Parseval/Plancherel theorem. We get the total energy from
For a simulation of 30 days, we got the three time series as: So there are a couple of things we are not sure:
Do you have any suggestions here? |
Yes, look at the harmonics here for l=1, m=0, so wavenumber k=1, this is a wave that goes from positive (N) to negative (S) to positive (N) so exactly one wavenlength would be around the globe.
should be
it and then it should be divided by the "frequency dimesion" given we're computing the spectrum across space in meters, it's "frequency" is the inverse 1/m. if you do spectrum of u plus spectrum of v you essentially compute u^2 + v^2 per meter so that's a kinetic energy density. See also here. But please check that this is all correctly implemented. I haven't usede this functionality much yet and haven't tested it thoroughly therefore.
Not sure what's needed to close Parseval's theorem, never attempeted that with spherical harmonics. But given above and because you integrate the spectral density (=energy per meter) that makes sense to me.
Note that we use a definition of the spherical harmonics such that the l=m=0 mode is the mean of the gridded field but mulitplied by |
Also relevant for you might be to check how the default horizontal_diffusion = SpectralFilter(spectral_grid) and just pass that on to the model constructor. Both |
Hi, I am trying to analyze the energy at different scales from a set of simulations based on this nice model. So how could I do this on the spherical earth?
I know I could do FFT along the zonal direction, and make sure some scaling can ensure that the energy in the spectral space sum up to the total energy in the physical (gridded) space. But how to do this in the meridional direction? It is not periodic along the meridion and some window function should be applied if FFT is applied, which also modified the total energy to some extent. Maybe the spherical harmonics will help here.
Any advice will be much appreciated.
The text was updated successfully, but these errors were encountered: