# -*- coding: utf8 -*-
# Copyright or © or Copr. Actimar/IFREMER (2010-2015)
#
# This software is a computer program whose purpose is to provide
# utilities for handling oceanographic and atmospheric data,
# with the ultimate goal of validating the MARS model from IFREMER.
#
# This software is governed by the CeCILL license under French law and
# abiding by the rules of distribution of free software. You can use,
# modify and/ or redistribute the software under the terms of the CeCILL
# license as circulated by CEA, CNRS and INRIA at the following URL
# "http://www.cecill.info".
#
# As a counterpart to the access to the source code and rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty and the software's author, the holder of the
# economic rights, and the successive licensors have only limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading, using, modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean that it is complicated to manipulate, and that also
# therefore means that it is reserved for developers and experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and, more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL license and that you accept its terms.
#
import numpy as N
from vacumm.misc import kwfilter
from vacumm.misc.grid import resol
import matplotlib.pyplot as P
import cdms2
[docs]def get_kxky(shape, dx, dy, verbose=False):
"""Generate 2D wavenumbers"""
# to work with dimensional axes
if hasattr(shape, 'shape'):
shape = shape.shape
nlat, nlon = shape
Lx = nlon*dx
Ly = nlat*dy
# to work with non-dimensional axes
# Lx = N.pi
# Ly = Lx * nlat / nlon * dy / dx
coefx = Lx / (2*N.pi)
coefy = Ly / (2*N.pi)
if verbose: print "Lx : %s Ly : %s coefx : %s coefy : %s \n" %(Lx,Ly,coefx,coefy)
kx = N.hstack( ( N.arange(0.,nlon/2), N.array([0]), -N.arange(1.,nlon/2)[::-1] ) ) / coefx
ky = N.hstack( ( N.arange(0.,nlat/2), N.array([0]), -N.arange(1.,nlat/2)[::-1] ) ) / coefy
[kkx,kky] = N.meshgrid(kx,ky)
kk = N.sqrt( kkx**2 + kky**2 )
return kx,ky,kkx,kky,kk,Lx,Ly
[docs]def get_spec(var1, var2=None, dx=None, dy=None, verbose=False, fft=False, **kwargs):
"""Get the spectrum of a 2D variable
:Return: specvar,nbwave,dk
"""
# Get the resolution in meters
kwresol = kwfilter(kwargs, 'resol_')
if None in [dx, dy]:
if not cdms2.isVariable(var1):
raise TypeError('var1 must be a MV2 array to compute dx and dy')
kwresol.setdefault('proj', True)
ldx, ldy = resol(var1, **kwresol)
dx = dx or ldx
dy = dy or ldy
if N.ma.isMA(var1): var1= var1.filled(0.)
if N.ma.isMA(var2): var2 = var2.filled(0.)
# Part 1 - estimate of the wavenumbers
[kx,ky,kkx,kky,kk,Lx,Ly] = get_kxky(var1, dx, dy, verbose=verbose)
if verbose:
print "dx = %s, fy = %s " %(dx,dy)
print "kx = ",kx[0:3]
print "ky = ",ky[0:3]
print "kkx[0:3,2] = ",kkx[0:3,2]
print "kky[0:3,1] = ",kky[0:3,1]
print "kk[0:3,3] = ",kk[0:3,3]
print "shape",kx.shape,ky.shape,kkx.shape,kky.shape,kk.shape
# Part 2 - estimate of the spectrum
# - fast fourier transform
if fft:
hat_phi1=N.fft.fft2(var1)
hat_phi1=hat_phi1*hat_phi1.conj()
hat_phi1=hat_phi1.real.copy() # useless
else:
hat_phi1 = var1
if var2 is not None:
if fft:
hat_phi2=N.fft.fft2(var2)
hat_phi2=hat_phi2*hat_phi2.conj()
hat_phi2=hat_phi2.real.copy() # useless
else:
hat_phi2 = var2
hat_spec = hat_phi1 + hat_phi2
else:
hat_spec = hat_phi1
# - integration of the spectrum
# shift to have values centered on the intervals
dk = kx[1] - kx[0]
dk_half = dk/2 # half of the interval
k_= kx[0:min(var1.shape[1],var1.shape[0])/2] + dk
specvar = k_*0
for i in range(len(k_)):
# get indexes that satisfy two conditions
# integration over a spectral width
specvar[i] = ( hat_spec[ (kk<=k_[i]+dk_half) & (kk>k_[i]-dk_half) ] ).sum()
# Normalisation (Danioux 2011)
specvar /= (var1.shape[1]*var1.shape[0])**2 * dk
# Dimensionalization to be able to compare with the litterature
nbwave = k_*1000.0/2.0/N.pi # from rad/m to km/m
dk *= 1000.0/2.0/N.pi
specvar *= 2.0*N.pi/1000.0
if verbose:
if var2 is not None:
print "\n Normalized co-spectrum : \n",specvar
else:
print "\n Normalized spectrum : \n",specvar
return specvar,nbwave,dk
[docs]def get_cospec(var1, var2, dx=None, dy=None, verbose=False, **kwargs):
"""Co-sprectum of two variables"""
return get_spec(var1, var2, dx=dx, dy=dy, verbose=verbose, **kwargs)
[docs]def save_spec(filename,var,absc,time):
if os.path.isfile(filename):
#tmpabsc=N.column_stack((N.load(filename)['nbwave'],absc))
tmpvar=N.column_stack((N.load(filename)['spectrum'],var))
tmptime=N.append(N.load(filename)['time'],time)
else:
tmpvar = var
tmptime = time
N.savez(filename,spectrum=tmpvar,nbwave=absc,time=tmptime)
[docs]def plot_loglog_kspec(data,abscisse,title=None,subtitle=None,xlabel=None,ylabel=None,slope=-3,savefig=None):
# bound the range of the abscisses
abs_min = round( abscisse.min() , min([v for v in range(10) if round(abscisse.min(),v) != 0]) )
abs_min = abs_min * 0.9 # 90 percent of the value
abs_max = round( abscisse.max() , min([v for v in range(10) if round(abscisse.max(),v) != 0]) )
abs_max = abs_max * 1.1 # 110 percent of the value
ord_min = round( data.min() , min([v for v in range(10) if round(data.min(),v) != 0]) )
ord_min = ord_min * 0.9 # 90 percent of the value
ord_max = round( data.max() , min([v for v in range(10) if round(data.max(),v) != 0]) )
ord_max = ord_max * 1.1 # 110 percent of the value
# straight line with a slope of k**slope
a=data[len(abscisse)/2] / abscisse[len(abscisse)/2]**slope
line=a*abscisse**slope
# figure
P.figure()
P.loglog(abscisse, data, basex=10)
P.loglog(abscisse[1:-1], line[1:-1], basex=10)
text='k'+str(slope)
P.text(abscisse[len(abscisse)/10], data[len(abscisse)/10]*5,text,color='g')
P.xlim(abs_min,abs_max)
P.grid(True,which="major",ls="-")
P.grid(True,which="minor")
P.suptitle(title)
P.title(subtitle)
P.xlabel(xlabel)
P.ylabel(ylabel)
P.tight_layout()
P.subplots_adjust(top=.9)
P.show()
if savefig: P.savefig(savefig+'.png')
P.close()
[docs]def plot_semilogx_kspec(data,abscisse,title=None,subtitle=None,xlabel=None,ylabel=None,savefig=None):
# bound the range of the abscisses
abs_min = round( abscisse.min() , min([v for v in range(10) if round(abscisse.min(),v) != 0]) )
abs_min = abs_min * 0.9 # 90 percent of the value
abs_max = round( abscisse.max() , min([v for v in range(10) if round(abscisse.max(),v) != 0]) )
abs_max = abs_max * 1.1 # 110 percent of the value
ord_min = round( data.min() , min([v for v in range(10) if round(data.min(),v) != 0]) )
ord_min = ord_min * 0.9 # 90 percent of the value
ord_max = round( data.max() , min([v for v in range(10) if round(data.max(),v) != 0]) )
ord_max = ord_max * 1.1 # 110 percent of the value
# figure
P.figure()
P.semilogx(abscisse, data, basex=10)
P.axhline(linewidth=3, color='r')
P.xlim(abs_min,abs_max)
P.grid(True,which="major",ls="-")
P.grid(True,which="minor")
P.suptitle(title)
P.title(subtitle)
P.xlabel(xlabel)
P.ylabel(ylabel)
P.tight_layout()
P.subplots_adjust(top=.9)
P.show()
if savefig: P.savefig(savefig+'.png')
P.close()
[docs]def energy_spectrum(var, dx=None, dy=None, ctime=None, dispfig=False, latmean=None, verbose=False):
"""Compute the energy spectrum of a 2D variable"""
# Guess dx and/or dy
if None in [dx, dy]:
if not cdms2.isVariable(var):
raise TypeError('var must be a MV2 array to compute dx and dy')
kwresol.setdefault('proj', True)
ldx, ldy = resol(var, **kwresol)
dx = dx or ldx
dy = dy or ldy
if latmean is None:
if not cdms2.isVariable(var):
raise Exception('You must provide explicitly latmean')
latmean = var.getLatitude().getValue().mean()
# Numpy arrays
sshbox = var.filled(0.) if N.ma.isMA(var) else var
#####################################################
# estimate of the spatial resolution and the latitude
#####################################################
grav = 9.81
#latmean = int(float(latmean))
#dx = int(grid[0])*np.cos(np.pi*latmean/180.)
f0 = 2*N.pi/(24.*3600.)*2.0*N.sin(N.pi*latmean/180.)
gof0 = grav/f0
if verbose:
print "the spatial resolution of the grid is constant \n dx = %s m, dy = %s m" %(dx,dy)
print "the averaged latitude is %s degrees\n" %(latmean)
###############################################
# 1ST PART ANALYZE FROM THE SEA SURFACE HEIGHT
# --------------------------------------------
###############################################
# zonal average and its removing from ssh
# mean on i (we erase 1 dimension)
# and duplication along i (depends on sshbox.mean(1).shape)
# then we need to transpose to have constant value along i and variations along j
#revoir sshbox = sshbox - N.tile( sshbox.mean(1),(sshbox.shape[1],1) ).T
# plot of ssh (box domain)
#if options.dispfig: plot.contour_xy(sshbox,sshbox.shape[0],sshbox.shape[1],'ssh_box_minus_iaverage')
# estimate of the rms to further check
sshbox_rms = N.sqrt( (sshbox**2).mean() )
if verbose: print "rms of the ssh over the region of interest (from physical field) : %s \n" %sshbox_rms
################################
# create a double periodic field
################################
ssh2per = N.tile( sshbox, (2,2) )
ssh2per[ssh2per.shape[0]/2:ssh2per.shape[0],0:ssh2per.shape[1]/2] = sshbox[::-1,::]
ssh2per[::,ssh2per.shape[1]/2:] = N.fliplr(ssh2per[::,0:ssh2per.shape[1]/2])
#if options.dispfig: plot.contour_xy(ssh2per,ssh2per.shape[0],ssh2per.shape[1],'ssh_double_periodic')
del sshbox
# estimate of the rms to further check
ssh2per_rms = N.sqrt( (ssh2per**2).mean() )
if verbose: print "rms of the double periodic ssh over the region of interest (from physical field) : %s \n" %ssh2per_rms
################################
# wavenumber spectrum of the ssh (double periodic field)
################################
# estimate of the spectrum
ssh2per_spec, ssh2per_k, dk = get_spec(ssh2per, dx=dx, dy=dy, fft='x2k', verbose=verbose) # sshbox in physical space
# make sure the FFT is correct : we must have the same rms either from physics or from wavenumbers
ssh2per_rmsk = N.sqrt( (ssh2per_spec*dk).sum() )
if verbose: print "rms of the ssh over the region of interest (from wave number fields) : %s \n" %ssh2per_rmsk
# figure
if dispfig: plot_loglog_kspec(ssh2per_spec, ssh2per_k, savefig='ssh2per_spec',
title='SPECTRUM OF SSH (double periodic)', subtitle=str(ctime),
xlabel='Wavenumber (cycles/km)', ylabel='SSH [m^2/(cycle/km)]',
slope=-5)
###########################################
# the ssh pectrum after doubling the domain
# is much better than the previous one
###########################################
####################################################
# 2ND PART ANALYZIS FROM THE GEOSTROPHIC VELOCITIES
# -------------------------------------------------
####################################################
###########################################
# estimate of the geostrophic velocities (from the spectrum of the periodic field of ssh)
###########################################
# estimate of the wavenumbers
kx,ky,kkx,kky,kk,Lx,Ly = get_kxky(ssh2per, dx, dy, verbose=verbose)
# estimate of the velocities from the spectrum of the ssh
ussh = -gof0*N.fft.ifft2( (kky*N.fft.fft2(ssh2per))*N.complex(0,1) ).real
vssh = gof0*N.fft.ifft2( (kkx*N.fft.fft2(ssh2per))*N.complex(0,1) ).real
#if options.dispfig: plot.contour_xy(ussh,ussh.shape[0],ussh.shape[1],'ussh (from double periodic field spectral method) ')
#if options.dispfig: plot.contour_xy(vssh,vssh.shape[0],vssh.shape[1],'vssh (from double periodic field and spectral method) ')
ssh2per_shape = ssh2per.shape
del ssh2per
#######################################################
# wavenumber co-spectrum of the geostrophic velocities (eke)
#######################################################
# estimate of the co-spectrum
uv2per_cospec,eke_k,dk = get_cospec(ussh, vssh, dx=dx, dy=dy, verbose=verbose, fft=True)
# estimate of the eke to further check
uv2per_rmsk = N.sqrt( ((uv2per_cospec*dk)/2.).sum() )
if verbose: print "eke over the region of interest (from physical field) : %s \n" %uv2per_rmsk
# make sure the FFT is correct : we must have the same rms either from physics or from wavenumbers
#ssh2per_rmsk = N.sqrt( (ssh2per_spec*dk).mean() )
#if verbose: print "eke over the region of interest (from physical field) : %s \n" %sshbox_rms
#if verbose: print "rms of the ssh over the region of interest (from wave number fields) : %s \n" %ssh2per_rmsk
#if verbose: print "rms difference (wavenumber - physics) : %s \n" %(ssh2per_rmsk-ssh2per_rms)
# figure
if dispfig:
plot_loglog_kspec(uv2per_cospec, eke_k, savefig='eke2per_spec',
title='SPECTRUM OF EKE', subtitle=str(ctime),
xlabel='Wavenumber (cycles/km)', ylabel='EKE [(m/s)^2/(cycle/km)]')
####################################################
# 3RD PART ESTIMATE OF THE FLUXES OF ENERGY
# -------------------------------------------------
####################################################
###########################################
# estimate of the production of eke
###########################################
# geostrophic velocities in the spectral space are non zero in the bottom-left corner
ug = ussh.copy() ; del ussh
ug[ssh2per_shape[0]/2:ssh2per_shape[0],::] = 0.
ug[::,ssh2per_shape[1]/2:] = 0.
vg = vssh.copy() ; del vssh
vg[ssh2per_shape[0]/2:ssh2per_shape[0],::] = 0.
vg[::,ssh2per_shape[1]/2:] = 0.
#if options.dispfig: plot.contour_xy(ug,ug.shape[0],ug.shape[1],'ug (from the spectrum of the periodic ssh)')
#if options.dispfig: plot.contour_xy(vg,vg.shape[0],vg.shape[1],'vg (from the spectrum of the periodic ssh)')
u = N.fft.fft2(ug)
v = N.fft.fft2(vg)
ugx = N.fft.ifft2( (u*kkx)*N.complex(0,1) ).real
ugy = N.fft.ifft2( (u*kky)*1j ).real
vgx = N.fft.ifft2( (v*kkx)*1j ).real
vgy = N.fft.ifft2( (v*kky)*1j ).real
term_u = N.fft.fft2(ug*ugx + vg*ugy)
term_v = N.fft.fft2(ug*vgx + vg*vgy)
eke_prod = - ( u.conj()*term_u + v.conj()*term_v ).real
eke_prod_phys1=N.fft.ifft2(eke_prod).real
#if options.dispfig: plot.contour_xy(eke_prod_phys1,eke_prod_phys1.shape[0],eke_prod_phys1.shape[1],'eke_prod_phys1')
term_u = 1j*kkx*N.fft.fft2(ug*ug) + 1j*kky*N.fft.fft2(vg*ug)
term_v = 1j*kkx*N.fft.fft2(ug*vg) + 1j*kky*N.fft.fft2(vg*vg)
eke_prod = - ( u.conj()*term_u + v.conj()*term_v ).real
eke_prod_phys2=N.fft.ifft2(eke_prod).real
#if options.dispfig: plot.contour_xy(eke_prod_phys2,eke_prod_phys2.shape[0],eke_prod_phys2.shape[1],'eke_prod_phys2')
#if options.dispfig: plot.contour_xy(eke_prod_phys1-eke_prod_phys2,eke_prod_phys2.shape[0],eke_prod_phys2.shape[1],'diff_eke_prod')
phi = eke_prod_phys1-eke_prod_phys2
# estimate of the total energy flux of the momentum equation
gg_spec, gg_k, dk = get_spec(eke_prod, dx=dx, dy=dy, verbose=verbose, fft=False) # phi in Fourier space
# choose kmax1 such that it corresponds to gg_k(kmax1) < 1/(3*dx)*1000 [km/m] (2dx Shanon + take into account diffusion)
kmax1 = min( N.where(gg_k>= 1/(3.0*dx)*1000.)[0][0] - 1 ,gg_spec.shape[0])
if verbose: print "Shanon criterium kmax1 = %s, gg_k(kmax1) = %s, gg_k(kmax1+1) = %s, 1/(3.0*dx)*1000. = %s" %(kmax1,gg_k[kmax1],gg_k[kmax1+1],1/(3.0*dx)*1000.)
# integration from large scales to small ones (shift one large scales)
tpi = N.array( [sum(gg_spec[ i:kmax1 ]) for i in range(kmax1)] )
tpi[0]=0.
# figure
if dispfig:
plot_semilogx_kspec(tpi, gg_k[:kmax1], savefig='ssh_spec',
title='TOTAL ENERGY FLUX', subtitle=str(ctime),
xlabel='Wavenumber (cycles/km)', ylabel='Sum of EKE from small scales to k [(m/s)^2/(cycle/km)]')
if verbose: print "The end !"
del grav,f0,gof0,sshbox_rms,ssh2per_rms,dk,kx,ky,kkx,kky,kk,Lx,Ly,uv2per_rmsk,ug,vg,u,v,term_u,term_v,ugx,ugy,vgx,vgy,eke_prod,eke_prod_phys1,eke_prod_phys2,phi,gg_spec
gc.collect()
return ssh2per_spec, ssh2per_k, uv2per_cospec, eke_k, tpi, gg_k[:kmax1]