Skip to content
Snippets Groups Projects
Select Git revision
  • b6a2a7c94a2860f6dcc2e021bf9fb4020ae47435
  • master default protected
  • use_flight_altitude
  • distribute
4 results

lon_lat_grid.py

Blame
  • cumulantFunctionSimulate.py 57.71 KiB
    #!/usr/bin/env python
    # encoding: utf-8
    """
    cumulantFunctionSimulate.py
    
    
    Purpose: This program simulates multiple elevation and slope realisations with
        given spectra, introducing quadratic phase coupling at selected wavenumbers.
        Glint realisations are computed from the slope realisations. The average 
        elevation, slope and glint moments and cumulants, and moment and cumulant 
        functions are computed. The results are written to a HDF5 file.
        
    Input:
        N            : Data length                                
        NN           : Bispectrum data length                     
        delta_x      : Spatial increment in meters                
        N_r          : Number of realisations                     
        spectrumType : Form of the elevation power spectrum       
        specExp      : Elevation power spectrum is proportional to
                       k^{-specExp}                               
        nlSwitch     : Elevation phase coupling on/off            
    
    Output:
        Output of results are written to HDF5 file.               
    
    Details:
        * 
    
    Preconditions:
        * 
    
    Optional:
        * 
    
    Minimum commandline:
    
        python progname.py  --input_files=INPUTFILES
    
    where...
    
        INPUTFILES: The fully qualified path to the input files. May be a directory 
                    or a file glob.
    
    
    Created by Geoff Cureton on 2011-03-06.
    Copyright (c) 2011-2013 Geoff Cureton. All rights reserved.
    
        This program is free software: you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        the Free Software Foundation, either version 3 of the License, or
        (at your option) any later version.
    
        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.
    
        You should have received a copy of the GNU General Public License
        along with this program.  If not, see <http://www.gnu.org/licenses/>.
                                                              
    """
    
    file_Date = '$Date$'
    file_Revision = '$Revision$'
    file_Author = '$Author$'
    file_HeadURL = '$HeadURL$'
    file_Id = '$Id$'
    
    __author__ = 'G.P. Cureton <geoff.cureton@physics.org>'
    __version__ = '$Id$'
    __docformat__ = 'Epytext'
    
    import os, sys, logging, traceback
    from os import path,uname,environ
    
    import string, copy
    import numpy as np
    from numpy import pi,sin,cos,tan,sqrt,abs,exp
    from numpy.fft import fft,ifft
    from numpy import float64 as double
    
    from scipy import stats as stats
    import time
    from datetime import datetime,timedelta
    
    
    #import matplotlib
    #import matplotlib.cm as cm
    #from matplotlib.colors import ListedColormap
    #from matplotlib.figure import Figure
    
    #matplotlib.use('Agg')
    #from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
    
    # This must come *after* the backend is specified.
    #import matplotlib.pyplot as ppl
    
    import tables as pytables
    from tables import exceptions as pyEx
    import h5py
    
    # every module should have a LOG object
    sourcename= file_Id.split(" ")
    #LOG = logging.getLogger(sourcename[1])
    LOG = logging.getLogger(__file__)
    
    from elevPowerSpectrum import phillips_elev_spectrum
    from utility import bispectrumSymmetry, bicoherenceSymmetry, biCovarianceSymmetry
    
    
    # Initialise the scale structure
    class ScaleStruct :
        def __init__(self, N, NN, delta_x) :
            self.N = N
            self.delta_x = delta_x
            self.x_max = double(N-1)*delta_x
            self.x = np.arange(N)*delta_x
    
            self.k_max = 2.*pi/delta_x
            self.delta_k = self.k_max/double(N-1)
            self.k_N = double(N/2)*self.delta_k # Nyquist "wavenumber"
            self.k = np.arange(N)*self.delta_k
    
            self.N2 = N/2
    
            self.NN = NN
            self.NN2 = NN/2
            self.NN4 = NN/4
    
    class GeomStruct :
        def __init__(self, N_geoms, angleLo, angleHi) :
            self.N_geoms = N_geoms
            self.source_angle = np.arange(N_geoms)
            self.detector_angle = np.arange(N_geoms)
            self.xi_min = np.arange(N_geoms)
            self.xi_0 = np.arange(N_geoms)
            self.xi_max = np.arange(N_geoms)
    
            self.angleRange=(angleHi-angleLo)
    
            if ((N_geoms-1) == 0) :
                self.d_angle = 0
            else :
                self.d_angle = self.angleRange/(N_geoms-1)
    
            self.start_angle = angleLo
            d2r = pi/180.
            r2d = 180./pi
            beta = 0.68*d2r
    
            self.source_angle = ((self.start_angle + np.arange(N_geoms,dtype=double)*self.d_angle))*d2r
            #self.detector_angle = 0.0*d2r
            self.detector_angle = 0.0*self.detector_angle*d2r
            gamma = (self.source_angle - self.detector_angle)/2.
            self.xi_0 = tan(gamma)
            self.xi_min = self.xi_0 - (1.0 + self.xi_0**2.)*(beta/4.)
            self.xi_max = self.xi_0 + (1.0 + self.xi_0**2.)*(beta/4.)
            self.dxi = self.xi_max - self.xi_min
    
    class PowerStruct :
        def __init__(self,N,spectrumType) :
            self.spectrumType = spectrumType
            self.primaryPower = np.zeros(N,double)
            self.nlPower = np.zeros(N,double)
            self.totalPower = np.zeros(N,double)
    
    class NLCouplingStruct :
        def __init__(self,N) :
            self.Nbound = 0
            self.bound = np.zeros(N, np.long)
            self.free1 = np.zeros(N, np.long)
            self.free2 = np.zeros(N, np.long)
    
    class DataStatsStruct :
        def __init__(self,numMoments) :
            self.mean = 0.
            self.variance = 0.
            self.skewness = 0.
            self.moments = np.zeros(numMoments, double)
            self.cumulants = np.zeros(numMoments, double)
    
        def cumulantsFromMoments(self):
            self.cumulants[0] = self.moments[0]
            self.cumulants[1] = self.moments[1] - self.moments[0]**2.
            self.cumulants[2] = self.moments[2] - 3.*self.moments[0]*self.moments[1] + 2.*self.moments[0]**3.
            self.mean     = self.cumulants[0]
            self.variance = self.cumulants[1]
            self.skewness = self.cumulants[2]/((self.cumulants[1])**1.5)
    
    
    def cumulantFunctionSimulate(N,NN,delta_x,N_r,spectrumType,specExp,nlSwitch):
    
        # Determine the various scale parameters and populate the 
        # Scale class 
    
        Scale = ScaleStruct(N, NN, delta_x)
    
        LOG.info("Scale.N       = {:12d}".format(Scale.N))
        LOG.info("Scale.delta_x = {:12.6f} meters".format(Scale.delta_x))
        LOG.info("Scale.x_max   = {:12.6f} meters".format(Scale.x_max))
        LOG.info("Scale.k_max   = {:12.6f} meters^{{-1}}".format(Scale.k_max))
        LOG.info("Scale.delta_k = {:12.6f} meters^{{-1}}".format(Scale.delta_k))
        LOG.info("Scale.k_N     = {:12.6f} meters^{{-1}}".format(Scale.k_N))
    
        # Make local copies of Scale attributes
        x_max   = Scale.x_max
        x       = Scale.x
        k_max   = Scale.k_max
        delta_k = Scale.delta_k
        k_N     = Scale.k_N
        k       = Scale.k
    
        N2 = Scale.N2
    
        NN2 = Scale.NN2
        NN4 = Scale.NN4
    
        #   Populate the GEOM structure with angular quantities
    
        N_geoms = 5L
        angleLo=10.
        angleHi=30.
        Geom = GeomStruct(N_geoms,angleLo,angleHi)
    
        #   Populate the elevation power spectrum structures ElevPower, 
        #   and NLCoupling                                          
    
        ElevPower = PowerStruct(N,spectrumType)
    
        NLCoupling = NLCouplingStruct(N)
    
        phillips_elev_spectrum(Scale,ElevPower,NLCoupling,specExp)
    
        LOG.info("First component indicies for free waves: {} {}"
                .format(NLCoupling.free1,Scale.k[NLCoupling.free1]/Scale.k_N))
    
        LOG.info("Second component indicies for free waves: {} {}"
                .format(NLCoupling.free2,Scale.k[NLCoupling.free2]/Scale.k_N))
    
        LOG.info("Indicies for bound waves: {} {}"
                .format(NLCoupling.bound,Scale.k[NLCoupling.bound]/Scale.k_N))
    
        totalElevPower = ElevPower.totalPower
        primaryElevPower = ElevPower.primaryPower
        nlElevPower = ElevPower.nlPower
    
        LOG.info("Elevation stdev from power vector:    {:12.6f} meters "
                .format(sqrt(np.sum(totalElevPower)*delta_k)))
        LOG.info("Elevation variance from power vector: {:12.6f} meters^{{2}} "
                .format(np.sum(totalElevPower)*delta_k))
    
        LOG.info("Total elevation power at the bound wavenumbers: {:10}"
                .format(totalElevPower[NLCoupling.bound]))
        LOG.info("Free elevation power at the bound wavenumbers:  {:10}"
                .format(ElevPower.primaryPower[NLCoupling.bound]))
        LOG.info("Bound elevation power at the bound wavenumbers: {:10}"
                .format(ElevPower.nlPower[NLCoupling.bound]))
        LOG.info("Ratio of bound to free elevation power at the bound wavenumbers: {:10}"
                .format(ElevPower.nlPower[NLCoupling.bound]/totalElevPower[NLCoupling.bound]))
    
        # Initialise the slope power spectrum structure
        SlopePower = copy.deepcopy(ElevPower)
        SlopePower.primaryPower = k*k*ElevPower.primaryPower
        SlopePower.nlPower = k*k*ElevPower.nlPower
        SlopePower.totalPower = k*k*ElevPower.totalPower
    
        totalSlopePower = SlopePower.totalPower
        primarySlopePower = SlopePower.primaryPower
        nlSlopePower = SlopePower.nlPower
    
        LOG.info("Slope stdev from power vector:    {:12.6f} meters "
                .format(sqrt(np.sum(totalSlopePower)*delta_k)))
        LOG.info("Slope variance from power vector: {:12.6f} meters^{{2}} "
                .format(np.sum(totalSlopePower)*delta_k))
    
        LOG.info("Total slope power at the bound wavenumbers: {:10}"
                .format(totalSlopePower[NLCoupling.bound]))
        LOG.info("Free slope power at the bound wavenumbers:  {:10}"
                .format(SlopePower.primaryPower[NLCoupling.bound]))
        LOG.info("Bound slope power at the bound wavenumbers: {:10}"
                .format(SlopePower.nlPower[NLCoupling.bound]))
        LOG.info("Ratio of bound to free slope power at the bound wavenumbers: {:10}"
                .format(SlopePower.nlPower[NLCoupling.bound]/totalSlopePower[NLCoupling.bound]))
    
        # Initialise the curvature power spectrum structure
        #CurvaturePower = copy.deepcopy(ElevPower)
        #CurvaturePower.primaryPower = k*k*k*k*ElevPower.primaryPower
        #CurvaturePower.nlPower = k*k*k*k*ElevPower.nlPower
        #CurvaturePower.totalPower = k*k*k*k*ElevPower.totalPower
    
        #totalCurvaturePower = CurvaturePower.totalPower
        #primaryCurvaturePower = CurvaturePower.primaryPower
        #nlCurvaturePower = CurvaturePower.nlPower
    
        #LOG.info("Curvature stdev from power vector:    {:12.6f} meters^{{-1}} "
                #.format(sqrt(np.sum(totalCurvaturePower)*delta_k)))
        #LOG.info("Curvature variance from power vector: {:12.6f} meters^{{-2}} "
                #.format(np.sum(totalCurvaturePower)*delta_k))
    
        #LOG.info("Total curvature power at the bound wavenumbers: {:10}"
                #.format(totalCurvaturePower[NLCoupling.bound]))
        #LOG.info("Free curvature power at the bound wavenumbers:  {:10}"
                #.format(CurvaturePower.primaryPower[NLCoupling.bound]))
        #LOG.info("Bound curvature power at the bound wavenumbers: {:10}"
                #.format(CurvaturePower.nlPower[NLCoupling.bound]))
        #LOG.info("Ratio of bound to free curvature power at the bound wavenumbers: {:10}"
                #.format(CurvaturePower.nlPower[NLCoupling.bound]/totalCurvaturePower[NLCoupling.bound]))
    
        #   Compute the total elevation amplitude, phase and spectrum,
        #   and the second moment function 
    
        totalElevAmplitude = np.zeros(N,dtype=double)
        totalElevAmplitude = sqrt(0.5*totalElevPower*delta_k)
        totalElevAmplitude[N/2+1 :] = totalElevAmplitude[1L : N/2][::-1]
        totalElevSpectrum = np.zeros(N,dtype=np.complex64)
    
        primaryElevAmplitude = np.zeros(N,dtype=double)
        primaryElevAmplitude = sqrt(0.5*primaryElevPower*delta_k)
        primaryElevAmplitude[N/2+1 :] = primaryElevAmplitude[1 : N/2][::-1]
    
        nlElevAmplitude = np.zeros(N,dtype=double)
        nlElevAmplitude = sqrt(0.5*nlElevPower*delta_k)
        nlElevAmplitude[N/2+1 :] = nlElevAmplitude[1 : N/2][::-1]
    
        LOG.info("Elevation stdev from amplitude vector:    {:12.6f} meters"
                .format(sqrt(np.sum(totalElevAmplitude**2.))))
        LOG.info("Elevation variance from amplitude vector: {:12.6f} meters^{{2}}"
                .format(np.sum(totalElevAmplitude**2.)))
    
        testElevPhase = np.random.rand(N)*2.*pi - pi
        totalElevSpectrum = totalElevAmplitude*(cos(testElevPhase) + 1j*sin(testElevPhase))
        totalElevSpectrum[N/2+1 :] = np.conjugate(totalElevSpectrum[1 : N/2][::-1])
        totalElevSurface = fft(totalElevSpectrum)
    
        LOG.info("Elevation mean from surface:     {:12.6f} meters"
                .format(np.mean(totalElevSurface.real)))
        LOG.info("Elevation stdev from surface:    {:12.6f} meters"
                .format(np.std(totalElevSurface.real)))
        LOG.info("Elevation variance from surface: {:12.6f} meters^{{2}}"
                .format(np.var(totalElevSurface.real)))
    
        totalElevAvgPower = np.zeros(N,dtype=double)
        primaryElevAvgPower = np.zeros(N,dtype=double)
        nlElevAvgPower = np.zeros(N,dtype=double)
        elevBispectrum = np.zeros((NN,NN),dtype=np.complex)
        elevComponentPower = np.zeros((NN,NN),dtype=np.float)
        elevSumPower = np.zeros((NN,NN),dtype=np.float)
    
        #    Compute the total slope amplitude, phase and spectrum
    
        totalSlopeAmplitude = np.zeros(N,dtype=double)
        totalSlopeAmplitude = sqrt(0.5*totalSlopePower*delta_k)
        totalSlopeAmplitude[N/2+1 :] = totalSlopeAmplitude[1L : N/2][::-1]
        totalSlopeSpectrum = np.zeros(N,dtype=np.complex64)
        totalSlopeSurface = np.zeros(N,dtype=np.complex64)
    
        primarySlopeAmplitude = np.zeros(N,dtype=double)
        primarySlopeAmplitude = sqrt(0.5*primarySlopePower*delta_k)
        primarySlopeAmplitude[N/2+1 :] = primarySlopeAmplitude[1L : N/2][::-1]
        primarySlopeSpectrum = np.zeros(N,dtype=np.complex64)
        primarySlopeSurface = np.zeros(N,dtype=np.complex64)
    
        nlSlopeAmplitude = np.zeros(N,dtype=double)
        nlSlopeAmplitude = sqrt(0.5*nlSlopePower*delta_k)
        nlSlopeAmplitude[N/2+1 :] = nlSlopeAmplitude[1L : N/2][::-1]
        nlSlopeSpectrum = np.zeros(N,dtype=np.complex64)
        nlSlopeSurface = np.zeros(N,dtype=np.complex64)
    
        LOG.info("Slope stdev from amplitude vector:    {:12.6f}"
                .format(sqrt(np.sum(totalSlopeAmplitude**2.))))
        LOG.info("Slope variance from amplitude vector: {:12.6f}"
                .format(np.sum(totalSlopeAmplitude**2.)))
    
        totalSlopeSpectrum = totalSlopeAmplitude*(+sin(testElevPhase) - 1j*cos(testElevPhase))
        totalSlopeSpectrum[N/2+1 :] = np.conjugate(totalSlopeSpectrum[1L : N/2][::-1])
        totalSlopeSurface = fft(totalSlopeSpectrum)
    
        LOG.info("Slope mean from surface:     {:12.6f}"
                .format(np.mean(totalSlopeSurface.real)))
        LOG.info("Slope stdev from surface:    {:12.6f}"
                .format(np.std(totalSlopeSurface.real)))
        LOG.info("Slope variance from surface: {:12.6f}"
                .format(np.var(totalSlopeSurface.real)))
    
        totalSlopeAvgPower = np.zeros(N,dtype=double)
        primarySlopeAvgPower = np.zeros(N,dtype=double)
        nlSlopeAvgPower = np.zeros(N,dtype=double)
    
        slopeBispectrum = np.zeros((NN,NN),dtype=np.complex)
        slopeComponentPower = np.zeros((NN,NN),dtype=np.float)
        slopeSumPower = np.zeros((NN,NN),dtype=np.float)
    
        #    Compute the total curvature amplitude, phase and spectrum
    
        #totalCurvatureAmplitude = np.zeros(N,dtype=double)
        #totalCurvatureAmplitude = sqrt(0.5*totalCurvaturePower*delta_k)
        #totalCurvatureAmplitude[N/2+1 :] = totalCurvatureAmplitude[1L : N/2][::-1]
        #totalCurvatureSpectrum = np.zeros(N,dtype=np.complex64)
        #totalCurvatureSurface = np.zeros(N,dtype=np.complex64)
    
        #primaryCurvatureAmplitude = np.zeros(N,dtype=double)
        #primaryCurvatureAmplitude = sqrt(0.5*primaryCurvaturePower*delta_k)
        #primaryCurvatureAmplitude[N/2+1 :] = primaryCurvatureAmplitude[1L : N/2][::-1]
        #primaryCurvatureSpectrum = np.zeros(N,dtype=np.complex64)
        #primaryCurvatureSurface = np.zeros(N,dtype=np.complex64)
    
        #nlCurvatureAmplitude = np.zeros(N,dtype=double)
        #nlCurvatureAmplitude = sqrt(0.5*nlCurvaturePower*delta_k)
        #nlCurvatureAmplitude[N/2+1 :] = nlCurvatureAmplitude[1L : N/2][::-1]
        #nlCurvatureSpectrum = np.zeros(N,dtype=np.complex64)
        #nlCurvatureSurface = np.zeros(N,dtype=np.complex64)
    
        #LOG.info("Curvature stdev from amplitude vector:    {:12.6f} meters^{{-1}}"
                #.format(sqrt(np.sum(totalCurvatureAmplitude**2.))))
        #LOG.info("Curvature variance from amplitude vector: {:12.6f} meters^{{-2}}"
                #.format(np.sum(totalCurvatureAmplitude**2.)))
    
        #totalCurvatureSpectrum = totalCurvatureAmplitude*(-cos(testElevPhase) - 1j*sin(testElevPhase))
        #totalCurvatureSpectrum[N/2+1 :] = np.conjugate(totalCurvatureSpectrum[1L : N/2][::-1])
        #totalCurvatureSurface = fft(totalCurvatureSpectrum)
    
        #LOG.info("Curvature mean from surface:     {:12.6f} meters^{{-1}}"
                #.format(np.mean(totalCurvatureSurface.real)))
        #LOG.info("Curvature stdev from surface:    {:12.6f} meters^{{-1}}"
                #.format(np.std(totalCurvatureSurface.real)))
        #LOG.info("Curvature variance from surface: {:12.6f} meters^{{-2}}"
                #.format(np.var(totalCurvatureSurface.real)))
    
        #totalCurvatureAvgPower = np.zeros(N,dtype=double)
        #primaryCurvatureAvgPower = np.zeros(N,dtype=double)
        #nlCurvatureAvgPower = np.zeros(N,dtype=double)
    
        #curvatureBispectrum = np.zeros((NN,NN),dtype=np.complex)
        #curvatureComponentPower = np.zeros((NN,NN),dtype=np.float)
        #curvatureSumPower = np.zeros((NN,NN),dtype=np.float)
    
    
        #   Define the glint, glint spectrum and glint power
        glint               = np.zeros(N,dtype=double)
        glintSpectrum       = np.zeros(N,dtype=np.complex)
        totalGlintAvgPower  = np.zeros((N_geoms,N),dtype=double)
        glintBispectrum     = np.zeros((N_geoms,NN,NN),dtype=np.complex)
        glintComponentPower = np.zeros((N_geoms,NN,NN),dtype=np.float)
        glintSumPower       = np.zeros((N_geoms,NN,NN),dtype=np.float)
    
        #   Define the various point estimators for the elevation,
        #   slope and glint
    
        numMoments = 3
    
        elevStats = DataStatsStruct(numMoments)
        slopeStats = DataStatsStruct(numMoments)
        #curvatureStats = DataStatsStruct(numMoments)
        glintStats = [DataStatsStruct(numMoments) for geoms in np.arange(N_geoms) ]
    
        #   Loop through the surface realisations for the quadratically
        #   coupled oscillations
    
        seed = 30
        N_r_cum = 0
        geom_runs = np.zeros(N_geoms,np.long)
        geom_runsCum = np.zeros(N_geoms,np.long)
    
        #time.sleep(3.)
        t1 = time.time()
    
        while (geom_runs.sum() < N_r * N_geoms) :
    
            N_r_cum += 1
    
            t2 = time.time()
            #print "Elapsed time = ",t2-t1
            if ((t2-t1) > 0.5):
                LOG.info(">>>>>>>>>>>>>>>>>>>>>")
                LOG.info("Computing realisation: %d at time %f" % (N_r_cum,(t2-t1)))
                t1 = time.time()
    
    
            ### Compute the independent phases for this realisation
            primaryElevPhase = np.random.rand(N)*2.*pi - pi
            nlElevPhase      = np.random.rand(N)*2.*pi - pi
    
            ### Apply the phase correlations between the free and bound wavenumbers for the nonlinear
            ### component, if (nlSwitch==1)
            if (nlSwitch==1) :
                nlElevPhase[NLCoupling.bound] = primaryElevPhase[NLCoupling.free1] + \
                        primaryElevPhase[NLCoupling.free2]
    
            ##################################################################
            # Compute the elevation realisations from the elevation spectra  #
            # and the synthesised phases                                     #
            ##################################################################
            
            ### Calculate the elevation spectrum for the free waves
            primaryElevSpectrum = primaryElevAmplitude*(cos(primaryElevPhase) + 1j*sin(primaryElevPhase))
            primaryElevSpectrum[N/2+1 :] = np.conjugate(primaryElevSpectrum[1 : N/2][::-1])
    
            ### Calculate the elevation spectrum for the bound waves
            nlElevSpectrum = nlElevAmplitude*(cos(nlElevPhase) + 1j*sin(nlElevPhase))
            nlElevSpectrum[N/2+1 :] = np.conjugate(nlElevSpectrum[1 : N/2][::-1])
    
            ### Compute specific realisation of the free and bound waves. Nonlinear elevation
            ### (totalElevSurface) is sum of free and bound waves.
            primaryElevSurface = fft(primaryElevSpectrum)                ### Free waves
            nlElevSurface      = fft(nlElevSpectrum)                     ### Bound waves
            totalElevSurface   = primaryElevSurface + nlElevSurface      ### Total surface
     
            ### Compute the average power spectrum for free, bound and total elevation waves
            primaryElevAvgPower += abs(ifft(primaryElevSurface))**2.
            nlElevAvgPower      += abs(ifft(nlElevSurface))**2.
            totalElevAvgPower   += abs(ifft(totalElevSurface))**2.
    
            ### Compute the elevation estimators for this realisation
            elevStats.mean     += np.mean(totalElevSurface.real)
            elevStats.variance += np.var(totalElevSurface.real)
            elevStats.skewness += stats.skew(totalElevSurface.real)
    
            elevStats.moments += [ np.sum(totalElevSurface.real    )/double(N), \
                                   np.sum(totalElevSurface.real**2.)/double(N), \
                                   np.sum(totalElevSurface.real**3.)/double(N)]
    
            # Compute the Fourier spectrum of the total surface
            totalElevSpectrum = ifft(totalElevSurface)
    
            # Calculate the elevation bispectrum (reduced domain) for this realisation
            for j in np.arange(NN4+1):
                for i in np.arange(j,NN2-j+1):
                    elevBispectrum[i,j] += totalElevSpectrum[i]*totalElevSpectrum[j] \
                            * np.conjugate(totalElevSpectrum[i+j])
    
            # Calculate the elevation component power spectrum (reduced domain) for this realisation
            for j in np.arange(NN4+1):
                for i in np.arange(j,NN2-j+1):
                    elevComponentPower[i,j] += (abs(totalElevSpectrum[i]*totalElevSpectrum[j]))**2.
    
            # Calculate the elevation sum power spectrum (reduced domain) for this realisation
            for j in np.arange(NN4+1):
                for i in np.arange(j,NN2-j+1):
                    elevSumPower[i,j] += (abs(totalElevSpectrum[i+j]))**2.
    
            #########################################################
            # Compute the slope realisations from the slope spectra #
            # and the synthesised phases                            #
            #########################################################
            
            ### Calculate the slope spectrum for the free waves
            primarySlopeSpectrum = primarySlopeAmplitude*(sin(primaryElevPhase) - 1j*cos(primaryElevPhase))
            primarySlopeSpectrum[N/2+1 :] = np.conjugate(primarySlopeSpectrum[1 : N/2][::-1])
    
            ### Calculate the slope spectrum for the bound waves
            nlSlopeSpectrum = nlSlopeAmplitude*(sin(nlElevPhase) - 1j*cos(nlElevPhase))
            nlSlopeSpectrum[N/2+1 :] = np.conjugate(nlSlopeSpectrum[1 : N/2][::-1])
    
            ### Compute specific realisation of the free and bound waves. Nonlinear slope
            ### (totalSlopeSurface) is sum of free and bound waves.
            primarySlopeSurface = fft(primarySlopeSpectrum)             ### Free waves
            nlSlopeSurface = fft(nlSlopeSpectrum)                       ### Bound waves
            totalSlopeSurface = primarySlopeSurface + nlSlopeSurface    ### Total surface
    
            ### Compute the average power spectrum for free, bound and total elevation waves
            primarySlopeAvgPower += abs(ifft(primarySlopeSurface))**2.
            nlSlopeAvgPower += abs(ifft(nlSlopeSurface))**2.
            totalSlopeAvgPower += abs(ifft(totalSlopeSurface))**2.
    
            ### Compute the slope estimators
            slopeStats.mean     += np.mean(totalSlopeSurface.real)
            slopeStats.variance += np.var(totalSlopeSurface.real)
            slopeStats.skewness += stats.skew(totalSlopeSurface.real)
    
            slopeStats.moments += [ np.sum(totalSlopeSurface    )/double(N), \
                                    np.sum(totalSlopeSurface**2.)/double(N), \
                                    np.sum(totalSlopeSurface**3.)/double(N) ]
    
            # Compute the Fourier spectrum of the total surface
            totalSlopeSpectrum = ifft(totalSlopeSurface)
    
            # Calculate the slope bispectrum (reduced domain) for this realisation
            for j in np.arange(NN4+1):
                for i in np.arange(j,NN2-j+1):
                    slopeBispectrum[i,j] += totalSlopeSpectrum[i]*totalSlopeSpectrum[j] \
                            * np.conjugate(totalSlopeSpectrum[i+j])
    
            # Calculate the slope component power spectrum (reduced domain) for this realisation
            for j in np.arange(NN4+1):
                for i in np.arange(j,NN2-j+1):
                    slopeComponentPower[i,j] += (abs(totalSlopeSpectrum[i]*totalSlopeSpectrum[j]))**2.
    
            # Calculate the slope sum power spectrum (reduced domain) for this realisation
            for j in np.arange(NN4+1):
                for i in np.arange(j,NN2-j+1):
                    slopeSumPower[i,j] += (abs(totalSlopeSpectrum[i+j]))**2.
    
            #################################################################
            # Compute the curvature realisations from the curvature spectra #
            # and the synthesised phases                                    #
            #################################################################
            
            ### Calculate the curvature spectrum for the free waves
            #primaryCurvatureSpectrum = primaryCurvatureAmplitude*(-cos(primaryElevPhase) - 1j*sin(primaryElevPhase))
            #primaryCurvatureSpectrum[N/2+1 :] = np.conjugate(primaryCurvatureSpectrum[1 : N/2][::-1])
    
            ### Calculate the curvature spectrum for the bound waves
            #nlCurvatureSpectrum = nlCurvatureAmplitude*(-cos(nlElevPhase) - 1j*sin(nlElevPhase))
            #nlCurvatureSpectrum[N/2+1 :] = np.conjugate(nlCurvatureSpectrum[1 :N/2][::-1])
    
            ### Compute specific realisation of the free and bound waves. Nonlinear curvature
            ### (totalCurvatureSurface) is sum of free and bound waves.
            #primaryCurvatureSurface = fft(primaryCurvatureSpectrum).real   ### Free waves
            #primaryCurvatureSurface *= 1./((1. + primarySlopeSurface**2.)**1.5)
    
            #nlCurvatureSurface = fft(nlCurvatureSpectrum).real             ### Bound waves
            #nlCurvatureSurface *= 1./((1. + nlSlopeSurface**2.)**1.5)
    
            #totalCurvatureSurface = primaryCurvatureSurface + nlCurvatureSurface   ### Total surface
    
            #totalCurvatureSurface *= 1./(    (1. + totalSlopeSurface**2.)**1.5)
            #totalCurvatureSurface *= 1./(sqrt(1. + totalSlopeSurface**2.)**3.)    ### Total surface
    
            ### Compute the average power spectrum for free, bound and total elevation waves
            #primaryCurvatureAvgPower += abs(ifft(primaryCurvatureSurface))**2.
            #nlCurvatureAvgPower += abs(ifft(nlCurvatureSurface))**2.
            #totalCurvatureAvgPower += abs(ifft(totalCurvatureSurface))**2.
    
            #print "\n\tCurvature stdev from power vector:    %10.6e meters" % \
                #(sqrt(np.sum(abs(ifft(totalCurvatureSurface.real))**2.)))
            #print "\tCurvature variance from power vector: %10.6e meters^{2}\n" % \
                #(np.sum(abs(ifft(totalCurvatureSurface.real))**2.))
    
            # Compute the curvature estimators
            #curvatureStats.mean     += np.mean(totalCurvatureSurface.real)
            #curvatureStats.variance += np.var(totalCurvatureSurface.real)
            #curvatureStats.skewness += stats.skew(totalCurvatureSurface.real)
    
            #curvatureStats.moments += [ np.sum(totalCurvatureSurface    )/double(N), \
                                        #np.sum(totalCurvatureSurface**2.)/double(N), \
                                        #np.sum(totalCurvatureSurface**3.)/double(N) ]
    
            # Compute the Fourier spectrum of the total surface
            #totalCurvatureSpectrum = ifft(totalCurvatureSurface)
    
            # Calculate the curvature bispectrum (reduced domain) for this realisation
            #for j in np.arange(NN4+1):
                #for i in np.arange(j,NN2-j+1):
                    #curvatureBispectrum[i,j] += totalCurvatureSpectrum[i]*totalCurvatureSpectrum[j] \
                            #* np.conjugate(totalCurvatureSpectrum[i+j])
    
            # Calculate the curvature component power spectrum (reduced domain) for this realisation
            #for j in np.arange(NN4+1):
                #for i in np.arange(j,NN2-j+1):
                    #curvatureComponentPower[i,j] += (abs(totalCurvatureSpectrum[i]*totalCurvatureSpectrum[j]))**2.
    
            # Calculate the curvature sum power spectrum (reduced domain) for this realisation
            #for j in np.arange(NN4+1):
                #for i in np.arange(j,NN2-j+1):
                    #curvatureSumPower[i,j] += (abs(totalCurvatureSpectrum[i+j]))**2.
    
    
    
            #####################################################
            # Loop through the geometries in the GEOM structure #
            #####################################################
    
            for geom in np.arange(N_geoms) :
    
                # Check if we have finished processing for this
                # geom.
    
                if (geom_runs[geom] < N_r) :
    
                    #print "\n\tProcessing geom ",geom," for run ",geom_runs[geom]+1, \
                    #" --> attempt ",geom_runsCum[geom]+1
    
                    #   Compute the glint realisation from the slope
                    #   realisations
    
                    slopeMin = Geom.xi_min[geom]
                    slopeMax = Geom.xi_max[geom]
    
                    glint = np.double(totalSlopeSurface.real > slopeMin) * np.double(totalSlopeSurface.real < slopeMax)
    
                    ### Check if all glint elements vanish
                    result = np.where(glint)
    
                    if (np.shape(np.squeeze(np.where(glint))) == (0,)) :
    
                        #print "\tZero-glint realisation geom ",geom, \
                        #" for run ",geom_runs[geom]+1, \
                        #" --> attempt ",geom_runsCum[geom]+1
    
                        ### There are no glints, add to attempts count
                        ### If this geom fails and there are no glints, then steeper 
                        ### geoms will fail also, so break out of the geom loop and 
                        ### proceed to the next realisation...
    
                        geom_runsCum[geom:N_geoms] += 1
                        break
    
                    else :
    
                        #print "\tSuccessful realisation geom ",geom, \
                        #" for run ",geom_runs[geom] + 1, \
                        #" --> attempt ",geom_runsCum[geom]+1
    
                        geom_runs[geom] += 1
                        geom_runsCum[geom] += 1
    
                        ### Compute the glint moments
    
                        glintStats[geom].mean     += np.mean(glint.real)
                        glintStats[geom].variance += np.var( glint.real)
                        glintStats[geom].skewness += stats.skew(glint.real)
    
                        glintStats[geom].moments += [ np.sum(glint.real    )/double(N), \
                                                       np.sum(glint.real**2.)/double(N), \
                                                       np.sum(glint.real**3.)/double(N) ]
    
                        ### Compute the Fourier spectrum of this glint realisation (using ifft 
                        ### which has same normalisation as IDL FFT routine).
                        glintSpectrum = ifft(glint)
    
                        # Compute the average glint power spectrum
                        # TODO: incorporate windowing to minimise aliasing
                        totalGlintAvgPower[geom] += abs(glintSpectrum)**2.
    
                        # Calculate the glint bispectrum (reduced domain) for this realisation
                        for j in np.arange(NN4+1):
                            for i in np.arange(j,NN2-j+1):
                                glintBispectrum[geom,i,j] += glintSpectrum[i]*glintSpectrum[j] \
                                        * np.conjugate(glintSpectrum[i+j])
    
                        # Calculate the glint component power spectrum (reduced domain) for this realisation
                        for j in np.arange(NN4+1):
                            for i in np.arange(j,NN2-j+1):
                                glintComponentPower[geom,i,j] += (abs(glintSpectrum[i]*glintSpectrum[j]))**2.
    
                        # Calculate the glint sum power spectrum (reduced domain) for this realisation
                        for j in np.arange(NN4+1):
                            for i in np.arange(j,NN2-j+1):
                                glintSumPower[geom,i,j] += (abs(glintSpectrum[i+j]))**2.
    
    
                    ### End checking for zero-glint of this geom
    
                ### End checking for completion of this geom
    
            ### End geom loop
    
        ### End realisation loop
        
        print ""
        print "geom_runs:    ",geom_runs," ... for total of ", \
            int(np.sum(geom_runs))," / ",N_r * N_geoms
        print "geom_runsCum: ",geom_runsCum
        
        N_runs = N_r
        N_r = N_r_cum
        print  "Final N_r_cum = ",N_r_cum
    
        #    Have a look at the scipy calculated stats
    
        ########################################
        #   Compute the elevation estimators   #
        ########################################
    
        # Normalise the estimators
        elevStats.mean     /= double(N_runs)
        elevStats.variance /= double(N_runs)
        elevStats.skewness /= double(N_runs)
        elevStats.moments /= double(N_runs)
    
        # Compute the cumulants
        elevStats.cumulantsFromMoments()
    
        # Compute the average elevation moment and cumulant functions.
        elevSecondMomentFunction =  fft(totalElevAvgPower)
        elevSecondMomentFunction /= elevSecondMomentFunction.real[0]
    
        LOG.info("elevSecondMomentFunction = {}".format(elevSecondMomentFunction.real[:N2]))
    
        # Compute the second order elev cumulant function
        elevSecondCumulantFunction = (elevStats.moments[1]*elevSecondMomentFunction - 
                elevStats.moments[0]**2.)/elevStats.cumulants[1]
    
        LOG.info("elevSecondCumulantFunction = {}".format(elevSecondCumulantFunction.real[:N2]))
    
        # Compute the bispectrum estimators
        elevBispectrum /= float(N_r)
        elevComponentPower /= float(N_r)
        elevSumPower /= float(N_r)
    
        # Compute the bicoherence
        elevBicoherence = np.zeros((NN,NN),dtype=np.float)
        for j in np.arange(NN4+1):
            for i in np.arange(j,NN2-j+1):
                if (sqrt(elevComponentPower[i,j])*sqrt(elevSumPower[i,j]) > 10.**(-12.)):
                    elevBicoherence[i,j] = abs(elevBispectrum[i,j])/ \
                            (sqrt(elevComponentPower[i,j])*sqrt(elevSumPower[i,j]))
                else:
                    elevBicoherence[i,j] = 0.
    
        # Fill the rest of the bispectrum and bicoherence array
        bispectrumSymmetry(elevBispectrum,NN)
        bicoherenceSymmetry(elevBicoherence,NN)
    
        # Compute the elevation third moment function
        elevThirdMomentFunction = np.zeros((NN,NN),dtype=np.float)
        elevThirdMomentFunction =  fft(elevBispectrum).real
        elevThirdMomentFunction /= elevThirdMomentFunction[0,0]
    
        LOG.info("elevThirdMomentFunction = \n{}".format(elevThirdMomentFunction[:10,:10]))
    
        # Compute the elevation third cumulant function
        elevThirdCumulantFunction = np.zeros((NN,NN),dtype=np.float)
        for i in np.arange(0,NN/2+1):
            for j in np.arange(0,i+1):
                elevThirdCumulantFunction[i,j] = (elevStats.moments[2]*elevThirdMomentFunction[i,j] \
                        - elevStats.moments[0]*elevStats.moments[1] * \
                        (elevSecondMomentFunction[i] + \
                         elevSecondMomentFunction[j] + \
                         elevSecondMomentFunction[abs(j-i)]) \
                        + 2.*elevStats.moments[0]**3.)/elevStats.cumulants[2]
    
                elevThirdCumulantFunction[j,i] = elevThirdCumulantFunction[i,j]
    
        LOG.info("elevThirdCumulantFunction = \n{}".format(elevThirdCumulantFunction[:10,:10]))
    
    
        #return 0
    
        ########################################
        #     Compute the slope estimators     #
        ########################################
    
        # Normalise the estimators
        slopeStats.mean     /= double(N_runs)
        slopeStats.variance /= double(N_runs)
        slopeStats.skewness /= double(N_runs)
        slopeStats.moments /= double(N_runs)
    
        # Compute the cumulants
        slopeStats.cumulantsFromMoments()
    
        # compute the average slope moment and cumulant functions.
        slopeSecondMomentFunction =  fft(totalSlopeAvgPower)
        slopeSecondMomentFunction /= slopeSecondMomentFunction.real[0]
    
        # Compute the bispectrum estimators
        slopeBispectrum /= float(N_r)
        slopeComponentPower /= float(N_r)
        slopeSumPower /= float(N_r)
    
        # Compute the bicoherence
        slopeBicoherence = np.zeros((NN,NN),dtype=np.float)
        for j in np.arange(NN4+1):
            for i in np.arange(j,NN2-j+1):
                if (sqrt(slopeComponentPower[i,j])*sqrt(slopeSumPower[i,j]) > 10.**(-12.)):
    				slopeBicoherence[i,j] = abs(slopeBispectrum[i,j])/ \
                    (sqrt(slopeComponentPower[i,j])*sqrt(slopeSumPower[i,j]))
                else:
                    slopeBicoherence[i,j] = 0.
    
        # Fill the rest of the bispectrum and bicoherence array
        bispectrumSymmetry(slopeBispectrum,NN)
        bicoherenceSymmetry(slopeBicoherence,NN)
    
        # Compute the slope third moment function
        slopeThirdMomentFunction = np.zeros((NN,NN),dtype=np.float)
        slopeThirdMomentFunction =  fft(slopeBispectrum).real
        slopeThirdMomentFunction /= slopeThirdMomentFunction[0,0]
    
        # Compute the slope third cumulant function
        slopeThirdCumulantFunction = np.zeros((NN,NN),dtype=np.float)
        for i in np.arange(0,NN/2+1):
            for j in np.arange(0,i+1):
                slopeThirdCumulantFunction[i,j] = (slopeStats.moments[2]*slopeThirdMomentFunction[i,j] \
                        - slopeStats.moments[0]*slopeStats.moments[1] * \
                        (slopeSecondMomentFunction[i] + \
                         slopeSecondMomentFunction[j] + \
                         slopeSecondMomentFunction[abs(j-i)]) \
                        + 2.*slopeStats.moments[0]**3.)/slopeStats.cumulants[2]
    
                slopeThirdCumulantFunction[j,i] = slopeThirdCumulantFunction[i,j]
    
    
        ########################################
        #   Compute the curvature estimators   #
        ########################################
    
        # Normalise the estimators
        #curvatureStats.mean     /= double(N_runs)
        #curvatureStats.variance /= double(N_runs)
        #curvatureStats.skewness /= double(N_runs)
        #curvatureStats.moments /= double(N_runs)
    
        # Compute the cumulants
        #curvatureStats.cumulantsFromMoments()
    
        # compute the average curvature moment and cumulant functions.
        #curvatureSecondMomentFunction =  fft(totalCurvatureAvgPower)
        #curvatureSecondMomentFunction /= curvatureSecondMomentFunction.real[0]
    
    	# Compute the bispectrum estimators
        #curvatureBispectrum /= float(N_r)
        #curvatureComponentPower /= float(N_r)
        #curvatureSumPower /= float(N_r)
    
    	# Compute the bicoherence
        #curvatureBicoherence = np.zeros((NN,NN),dtype=np.float)
        #for j in np.arange(NN4+1):
            #for i in np.arange(j,NN2-j+1):
                #if (sqrt(curvatureComponentPower[i,j])*sqrt(curvatureSumPower[i,j]) > 10.**(-12.)):
    				#curvatureBicoherence[i,j] = abs(curvatureBispectrum[i,j])/ \
                            #(sqrt(curvatureComponentPower[i,j])*sqrt(curvatureSumPower[i,j]))
                #else:
    				#curvatureBicoherence[i,j] = 0.
    
    	# Fill the rest of the bispectrum and bicoherence array
    	#bispectrumSymmetry(curvatureBispectrum,NN)
    	#bicoherenceSymmetry(curvatureBicoherence,NN)
    
    	# Compute the curvature third moment function
        #curvatureThirdMomentFunction = np.zeros((NN,NN),dtype=np.float)
        #curvatureThirdMomentFunction =  fft(curvatureBispectrum).real
        #curvatureThirdMomentFunction /= curvatureThirdMomentFunction[0,0]
    
    	# Compute the curvature third cumulant function
        #curvatureThirdCumulantFunction = np.zeros((NN,NN),dtype=np.float)
        #for i in np.arange(0,NN/2+1):
            #for j in np.arange(0,i+1):
    			#curvatureThirdCumulantFunction[i,j] = (curvatureStats.moments[2]*curvatureThirdMomentFunction[i,j] \
    				#- curvatureStats.moments[0]*curvatureStats.moments[1] * \
    				#(curvatureSecondMomentFunction[i] + \
    				 #curvatureSecondMomentFunction[j] + \
    				 #curvatureSecondMomentFunction[abs(j-i)]) \
    				#+ 2.*curvatureStats.moments[0]**3.)/curvatureStats.cumulants[2]
    
    			#curvatureThirdCumulantFunction[j,i] = curvatureThirdCumulantFunction[i,j]
    
    
        ########################################
        #     Compute the glint estimators     #
        ########################################
    
        # Normalise the estimators
        for geom in np.arange(N_geoms) :
            glintStats[geom].mean     /= double(geom_runsCum[geom])
            glintStats[geom].variance /= double(geom_runsCum[geom])
            glintStats[geom].skewness /= double(geom_runsCum[geom])
            glintStats[geom].moments  /= double(geom_runsCum[geom])
    
        # Compute the cumulants
        for geom in np.arange(N_geoms) :
            glintStats[geom].cumulantsFromMoments()
    
        # compute the average glint moment and cumulant functions.
        glintSecondMomentFunction = np.zeros((N_geoms,N),dtype=double)
        for geom in np.arange(N_geoms) :
            glintSecondMomentFunction[geom] = fft(totalGlintAvgPower[geom]).real
            glintSecondMomentFunction[geom] /= glintSecondMomentFunction[geom][0]
    
        # Compute the bispectrum estimators
        glintBispectrum /= float(N_r)
        glintComponentPower /= float(N_r)
        glintSumPower /= float(N_r)
    
        # Compute the bicoherence
        glintBicoherence = np.zeros((N_geoms,NN,NN),dtype=np.float)
        for geom in np.arange(N_geoms) :
            for j in np.arange(NN4+1):
                for i in np.arange(j,NN2-j+1):
                    if (sqrt(glintComponentPower[geom,i,j])*sqrt(glintSumPower[geom,i,j]) > 10.**(-12.)):
                        glintBicoherence[geom,i,j] = \
                                abs(glintBispectrum[geom,i,j]) / (sqrt(glintComponentPower[geom,i,j]) * sqrt(glintSumPower[geom,i,j]))
                    else:
                        glintBicoherence[geom,i,j] = 0.
    
        # Fill the rest of the bispectrum and bicoherence array
        for geom in np.arange(N_geoms) :
            bispectrumSymmetry(glintBispectrum[geom],NN)
            bicoherenceSymmetry(glintBicoherence[geom],NN)
    
        # Compute the glint third moment function
        glintThirdMomentFunction = np.zeros((N_geoms,NN,NN),dtype=np.float)
        for geom in np.arange(N_geoms) :
            glintThirdMomentFunction[geom] =  fft(glintBispectrum[geom]).real
            glintThirdMomentFunction[geom] /= glintThirdMomentFunction[geom,0,0]
    
        # Compute the glint third cumulant function
        glintThirdCumulantFunction = np.zeros((N_geoms,NN,NN),dtype=np.float)
        for geom in np.arange(N_geoms) :
            for i in np.arange(0,NN/2+1):
                for j in np.arange(0,i+1):
                    glintThirdCumulantFunction[geom,i,j] = (glintStats[geom].moments[2]*glintThirdMomentFunction[geom,i,j] \
                        - glintStats[geom].moments[0]*glintStats[geom].moments[1] * \
                        (glintSecondMomentFunction[geom,i] + \
                         glintSecondMomentFunction[geom,j] + \
                         glintSecondMomentFunction[geom,abs(j-i)]) \
                        + 2.*glintStats[geom].moments[0]**3.)/glintStats[geom].cumulants[2]
    
                    glintThirdCumulantFunction[geom,j,i] = glintThirdCumulantFunction[geom,i,j]
    
    
        #################################################
        ###  The elevation and slope summary results  ###
        #################################################
    
        #strFormat = "{:30}: {:10.6e}"
        strFormat = "{:30}: {:15.12f}"
    
        print '\n####################################'
        print '            Elevation'
        print '####################################'
    
        print ""
        LOG.info(strFormat.format("Elevation first moment",elevStats.moments[0]))
        LOG.info(strFormat.format("Elevation second moment",elevStats.moments[1]))
        LOG.info(strFormat.format("Elevation third moment",elevStats.moments[2]))
    
        print ""
        LOG.info(strFormat.format("Elevation first cumulant",elevStats.cumulants[0]))
        LOG.info(strFormat.format("Elevation second cumulant",elevStats.cumulants[1]))
        LOG.info(strFormat.format("Elevation third cumulant",elevStats.cumulants[2]))
    
        print ""
        LOG.info(strFormat.format("Elevation mean",elevStats.mean))
        #LOG.info(strFormat.format("Elevation stdev",sqrt(elevStats.variance)))
        LOG.info(strFormat.format("Elevation variance",elevStats.variance))
        LOG.info(strFormat.format("Elevation skewness",elevStats.skewness))
    
        print ""
        LOG.info(strFormat.format("Python Elevation mean",elevStats.mean))
        #LOG.info(strFormat.format("Python Elevation stdev",sqrt(elevStats.variance)))
        LOG.info(strFormat.format("Python Elevation variance",elevStats.variance))
        LOG.info(strFormat.format("Python Elevation skewness",elevStats.skewness))
    
        print '\n####################################'
        print '               Slope'
        print '####################################'
    
        print ""
        LOG.info(strFormat.format("Slope first moment",slopeStats.moments[0]))
        LOG.info(strFormat.format("Slope second moment",slopeStats.moments[1]))
        LOG.info(strFormat.format("Slope third moment",slopeStats.moments[2]))
    
        print ""
        LOG.info(strFormat.format("Slope first cumulant",slopeStats.cumulants[0]))
        LOG.info(strFormat.format("Slope second cumulant",slopeStats.cumulants[1]))
        LOG.info(strFormat.format("Slope third cumulant",slopeStats.cumulants[2]))
    
        print ""
        LOG.info(strFormat.format("Slope mean",slopeStats.mean))
        #LOG.info(strFormat.format("Slope stdev",sqrt(slopeStats.variance)))
        LOG.info(strFormat.format("Slope variance",slopeStats.variance))
        LOG.info(strFormat.format("Slope skewness",slopeStats.skewness))
    
        print ""
        LOG.info(strFormat.format("Python Slope mean",slopeStats.mean))
        #LOG.info(strFormat.format("Python Slope stdev",sqrt(slopeStats.variance)))
        LOG.info(strFormat.format("Python Slope variance",slopeStats.variance))
        LOG.info(strFormat.format("Python Slope skewness",slopeStats.skewness))
    
        #print '\n####################################'
        #print '              Curvature'
        #print '####################################'
    
        #print ""
        #LOG.info(strFormat.format("Curvature first moment",curvatureStats.moments[0]))
        #LOG.info(strFormat.format("Curvature second moment",curvatureStats.moments[1]))
        #LOG.info(strFormat.format("Curvature third moment",curvatureStats.moments[2]))
    
        #print ""
        #LOG.info(strFormat.format("Curvature first cumulant",curvatureStats.cumulants[0]))
        #LOG.info(strFormat.format("Curvature second cumulant",curvatureStats.cumulants[1]))
        #LOG.info(strFormat.format("Curvature third cumulant",curvatureStats.cumulants[2]))
    
        #print ""
        #LOG.info(strFormat.format("Curvature mean",curvatureStats.mean))
        ##LOG.info(strFormat.format("Curvature stdev",sqrt(curvatureStats.variance)))
        #LOG.info(strFormat.format("Curvature variance",curvatureStats.variance))
        #LOG.info(strFormat.format("Curvature skewness",curvatureStats.skewness))
    
        #print ""
        #LOG.info(strFormat.format("Python Curvature mean",curvatureStats.mean))
        ##LOG.info(strFormat.format("Python Curvature stdev",sqrt(curvatureStats.variance)))
        #LOG.info(strFormat.format("Python Curvature variance",curvatureStats.variance))
        #LOG.info(strFormat.format("Python Curvature skewness",curvatureStats.skewness))
    
        print '\n####################################'
        print '              Glint'
        print '####################################'
    
        print "Glint moments ...\n"
        for geom in np.arange(Geom.N_geoms) :
            print "\tGeometry %1d:\t\t%10.6e\t%10.6e\t%10.6e" % (geom,\
                glintStats[geom].moments[0],\
                glintStats[geom].moments[1],\
                glintStats[geom].moments[2])
    
        print "\nGlint cumulants ...\n"
        for geom in np.arange(Geom.N_geoms) :
            print "\tGeometry %1d:\t\t%10.6e\t%10.6e\t%10.6e" % (geom,\
                glintStats[geom].cumulants[0],\
                glintStats[geom].cumulants[1],\
                glintStats[geom].cumulants[2])
    
        print "\nPython Glint mean, variance and skewness ...\n"
        for geom in np.arange(Geom.N_geoms) :
            print "\tGeometry %1d:\t\t%10.6e\t%10.6e\t%10.6e" % (geom,\
                glintStats[geom].mean,\
                glintStats[geom].variance,\
                glintStats[geom].skewness)
    
    
        ##############################################
        ###   Write data to the output HDF5 file   ###
        ##############################################
    
        fileName = 'GlintSim_{}_{}_{}_{}_{}'.format(
                    N,
                    NN,
                    int(100.*delta_x),
                    N_runs,
                    spectrumType
                )
    
        if (nlSwitch == 1):
            fileName = '{}_nl.h5'.format(fileName)
        else:
            fileName = '{}.h5'.format(fileName)
    
        fileName = string.replace(fileName,' ','')
    
        LOG.info("Open output filename: {}".format(fileName))
    
        f = h5py.File(fileName,'w')
    
    
        ### Add some attributes to the file
    
        f.attrs['Author'] = "Geoff Cureton"
        f.attrs['email'] = "<geoff.cureton@physics.org>"
        f.attrs['repo:'] = 'https://bitbucket.org/gcureton/cumulant_function_simulate'
        f.attrs['revision'] = 'undefined'
        f.attrs['date'] = datetime.strftime(datetime.utcnow(),"%Y-%m-%d %H:%M:%S Z")
    
        # Add the Geometry group
    
        grp = f.create_group("Geometry")
    
        grp['N_geoms'] = Geom.N_geoms
        grp['N_geoms'].attrs['label'] = 'Number of geometries'
    
        grp['source_angle'] = Geom.source_angle
        grp['source_angle'].attrs['unit'] = 'radians'
        grp['source_angle'].attrs['label'] = 'Solar Zenith Angles'
    
        grp['detector_angle'] = Geom.detector_angle
        grp['detector_angle'].attrs['unit'] = 'radians'
        grp['detector_angle'].attrs['label'] = 'Sensor Zenith Angles'
     
        grp['xi_min'] = Geom.xi_min
        grp['xi_min'].attrs['unit'] = 'dimensionless'
        grp['xi_min'].attrs['label'] = 'Minimum Slopes'
    
        grp['xi_max'] = Geom.xi_max
        grp['xi_max'].attrs['unit'] = 'dimensionless'
        grp['xi_max'].attrs['label'] = 'Maximum Slopes'
    
        grp['xi_0'] = Geom.xi_0
        grp['xi_0'].attrs['unit'] = 'dimensionless'
        grp['xi_0'].attrs['label'] = 'Specular Slopes'
    
        # Add the Scale group
    
        grp = f.create_group("Scale")
    
        grp['N'] = Scale.N
        grp['N'].attrs['label'] = '1D Data Length'
        grp['N2'] = Scale.N2
        grp['N2'].attrs['label'] = 'N/2'
        grp['NN'] = Scale.NN
        grp['NN'].attrs['label'] = '2D data length'
        grp['NN2'] = Scale.NN2
        grp['NN2'].attrs['label'] = 'NN/2'
        grp['NN4'] = Scale.NN4
        grp['NN4'].attrs['label'] = 'NN/4'
    
        grp['delta_x'] = Scale.delta_x
        grp['delta_x'].attrs['label'] = 'Spatial Increment'
        grp['delta_x'].attrs['units'] = 'meters'
        grp['x_max'] = Scale.x_max
        grp['x_max'].attrs['label'] = '1D Spatial Maximum'
        grp['x_max'].attrs['units'] = 'meters'
        grp['x'] = Scale.x
        grp['x'].attrs['label'] = '1D spatial length scale'
        grp['x'].attrs['units'] = 'meters'
        grp['k_max'] = Scale.k_max
        grp['k_max'].attrs['label'] = '1D wavenumber maximum'
        grp['k_max'].attrs['units'] = 'meters^{-1}'
        grp['delta_k'] = Scale.delta_k
        grp['delta_k'].attrs['label'] = 'Wavenumber increment'
        grp['delta_k'].attrs['units'] = 'meters^{-1}'
        grp['k_N'] = Scale.k_N
        grp['k_N'].attrs['label'] = 'Nyquist wavenumber'
        grp['k_N'].attrs['units'] = 'meters^{-1}'
        grp['k'] = Scale.k
        grp['k'].attrs['label'] = '1D wavenumber scale'
        grp['k'].attrs['units'] = 'meters^{-1}'
    
        # Add the Elevation group
    
        grp = f.create_group("/Data/Elevation")
    
        grp['elevation_moments'] = elevStats.moments
        grp['elevation_moments'].attrs['label'] = 'Elevation Moments'
    
        grp['elevation_cumulants'] = elevStats.cumulants
        grp['elevation_cumulants'].attrs['label'] = 'Elevation cumulants'
    
        grp['elevation_stats'] = [elevStats.mean,elevStats.variance,elevStats.skewness]
        grp['elevation_stats'].attrs['label'] = 'Elevation Point Statistics (numpy)'
    
        # Add the Slope group
    
        grp = f.create_group("/Data/Slope")
    
        grp['slope_moments'] = slopeStats.moments
        grp['slope_moments'].attrs['label'] = 'Slope Moments'
    
        grp['slope_cumulants'] = slopeStats.cumulants
        grp['slope_cumulants'].attrs['label'] = 'Slope cumulants'
    
        grp['slope_stats'] = [slopeStats.mean,slopeStats.variance,slopeStats.skewness]
        grp['slope_stats'].attrs['label'] = 'Slope Point Statistics (numpy)'
    
        # Add the Glint group
    
        grp = f.create_group("/Data/Glint")
    
        grp['glint_moments'] = np.squeeze([np.vstack((glintStats[geom].moments)) 
            for geom in np.arange(Geom.N_geoms)])
        grp['glint_moments'].attrs['label'] = 'Glint Moments'
    
        grp['glint_cumulants'] = np.squeeze([np.vstack((glintStats[geom].cumulants)) 
            for geom in np.arange(Geom.N_geoms)])
        grp['glint_cumulants'].attrs['label'] = 'Glint cumulants'
    
        grp['glint_stats'] = np.squeeze([np.vstack((
            [glintStats[geom].mean,glintStats[geom].variance,glintStats[geom].skewness]
            )) 
            for geom in np.arange(Geom.N_geoms)])
        grp['glint_stats'].attrs['label'] = 'Glint Point Statistics (numpy)'
    
    
    
        # Close the output file
    
        LOG.info("Closing HDF5 file")
    
        f.close()
    
    
    
    
    
    def _argparse():
    
        import argparse as argparse
    
        spectrumChoices=['phillips_3','phillips_4','gaussian']
    
        defaults = {'N':1024,
                    'NN':64,
                    'delta_x':0.02,
                    'N_r':100,
                    'spectrumType':'phillips_3',
                    'nlSwitch':False,
                    'outputFile':"outSimulatedGlint.h5",
                    }
    
        description = "This is a brief description of %prog"
        usage = "usage: %prog [mandatory args] [optional args]"
        version = __version__
    
        parser = argparse.ArgumentParser()
    
        # Positional arguments...
        #parser.add_argument("x", type=int, help="the base")
        #parser.add_argument("y", type=int, help="the exponent")
    
        # Mandatory arguments...
        parser.add_argument('-n','--numdatapoints',
                          action="store",
                          dest="N" ,
                          default=defaults['N'],
                          type=int,
                          help='''Number of points of the glint dataset. Must be an 
                          integer power of 2. [default: {}]'''.format(defaults['N']),
                          metavar="N")
    
        parser.add_argument('-N','--num2Dpoints',
                          action="store",
                          dest="NN" ,
                          default=defaults['NN'],
                          type=int,
                          help='''Number of points of the bispectrum array side. 
                          Must be an integer power of 2. [default: {}]'''.format(defaults['NN']),
                          metavar="NN")
    
        parser.add_argument('-d','--deltax',
                          action="store",
                          dest="delta_x",
                          default=defaults['delta_x'],
                          type=float,
                          help="The spatial increment in meters. [default: {}]".format(defaults['delta_x']),
                          metavar="DELTA_X")
    
        parser.add_argument('-r','--num_realisations',
                          action="store",
                          dest="N_r" ,
                          default=defaults['N_r'],
                          type=int,
                          help="Number of realisations. [default: {}]".format(defaults['N_r']),
                          metavar="NUMREALS")
    
        parser.add_argument('-S','--spectrum_type',
                          action="store",
                          dest="spectrumType",
                          default=defaults['spectrumType'],
                          type=str,
                          help='''Form of the elevation power spectrum.\n\n
                                                       Possible values are...
                                                       {}. [default: '{}']
                                                       '''.format(spectrumChoices.__str__()[1:-1],defaults['spectrumType']),
                          metavar="SPECTRUMTYPE")
    
        # Optional arguments
        parser.add_argument('-l','--nonlinear_modes',
                          action="store_true",
                          dest="nlSwitch",
                          default=defaults['nlSwitch'],
                          help="Switch on nonlinear modes. [default: {}]".format(defaults['nlSwitch']))
    
        parser.add_argument('-o','--output_file',
                          action="store",
                          dest="outputFile",
                          default=defaults['outputFile'],
                          type=str,
                          help='''The full path of the output HDF5 file. 
                          [default: '{}']'''.format(defaults['outputFile']),
                          metavar="OUTFILE")
    
        parser.add_argument("-v", "--verbose",
                          dest='verbosity',
                          action="count", 
                          default=0,
                          help='''each occurrence increases verbosity 1 level 
                          from ERROR: -v=WARNING -vv=INFO -vvv=DEBUG''')
    
        args = parser.parse_args()
    
        print args
    
        # Check that all of the mandatory args are given. If one or more 
        # are missing, print error message and exit...
        #mandatories = ['N', 'NN','delta_x','N_r', 'spectrumType','nlSwitch']
        #mand_errors = ["Missing mandatory argument [-n N            | --numdatapoints=N]",
                       #"Missing mandatory argument [-N NN           | --num2Dpoints=NN]",
                       #"Missing mandatory argument [-d delta_x      | --deltax=delta_x]",
                       #"Missing mandatory argument [-r N_r          | --num_realisations=N_r]",
                       #"Missing mandatory argument [-S spectrumType | --spectrum_type=spectrumType]"
                      #]
        #isMissingMand = False
        #for m,m_err in zip(mandatories,mand_errors):
            #if not args.__dict__[m]:
                #isMissingMand = True
                #print m_err
        #if isMissingMand :
            #parser.error("Incomplete mandatory arguments, aborting...")
    
    
        # Set up the logging
        #console_logFormat = '%(asctime)s : %(name)-12s: %(levelname)-8s %(message)s'
        #console_logFormat = '%(levelname)s:%(name)s:%(msg)s') # [%(filename)s:%(lineno)d]'
    
        console_logFormat = '%(asctime)s : %(funcName)s:%(lineno)d:  %(message)s'
        #console_logFormat = '%(asctime)s : (%(levelname)s):%(filename)s:%(funcName)s:%(lineno)d:  %(message)s'
        dateFormat = '%Y-%m-%d %H:%M:%S'
        levels = [logging.ERROR, logging.WARN, logging.INFO, logging.DEBUG]
        logging.basicConfig(level = levels[args.verbosity], 
                format = console_logFormat, 
                datefmt = dateFormat)
    
        return args
    
    
    ###################################################
    #                  Main Function                  #
    ###################################################
    
    def main():
    
        args = _argparse()
    
        N            = args.N
        NN           = args.NN
        delta_x      = args.delta_x
        N_r          = args.N_r
        spectrumType = args.spectrumType
        specExp      = 4 if args.spectrumType=='phillips_4' else 3
        nlSwitch     = args.nlSwitch
    
        cumulantFunctionSimulate(N,NN,delta_x,N_r,spectrumType,specExp,nlSwitch)
    
        LOG.info("Exiting...")
        sys.exit(0)
    
    
    if __name__ == '__main__':
        main()