##----------------------------------------------------------------------------
## Name:         surf_is.py - create animations and plots
## Purpose:      Compute the JONSWAP Wave Spectra, then compute a breaking
##               wave height spectrum, a bubble cloud size spectrum, and
##               finally an acoustic radiation spectrum from the initial
##               bubble oscillation and collective bubble oscillations
## Author:       J Park
##
## Created:      11/23/06
##
## Modified:     
##----------------------------------------------------------------------------

import os, sys, string, getopt, time

# Python array module and numpy/scipy are NOT compatible as standalone modules.
# numpy/scipy requires that the array constructor must be passed an object
# that is either an array, or list: a = array([]) will create an empty array.
#
# With the python array module, one can define an array object as:
# a = array('d'), and then call a.append(1.2) to build the array as
# needed. In numpy/scipy, you have to first create an array object a,
# then call b = append(a, 1.2) to return an array b that appends the
# value 1.2 to the existing array object a. 
# the only reason to use numpy/scipy is for matplotlib graphics
try:
    from numpy import *  # www.numpy.org numpy.scipy.org
except ImportError: 
    print "Failed to import numpy."
    
try:
    from pylab import *  # matplotlib.sourceforge.net
except ImportError: 
    print "Failed to import pylab for matplotlib."
    
from scipy.interpolate.fitpack2 import UnivariateSpline

Version = sys.argv[0]+" : "+str( time.asctime( ( time.gmtime(time.time()) ) ) )
DEBUG = False

##----------------------------------------------------------------------------
# Global variables
g            = 9.82
SWH          = 0.
peakWaveFreq = 0.

# Default Parameters
plotPolihaleRawData = False
writeOutput         = False  # override with -z
trainANNData        = False  # override with -k
runANNData          = False  # override with -r
createAnimation     = False  # override with -m
useSphericalCloud   = False  # override with -a
useInitialBubble    = True   # override with -j
additionalSpectrum  = False  # override with -g
windSpeedIncrement  = 1.     # override with -e
N                   = 1200   # points in JONSWAP spectrum -n
omegaIncrement      = 2.25/N # radian frequency increment of N point wave spectrum
                             # 2.25 gives a band of 0.25 - 2.5 rad/s (0.04 - 0.4 Hz)
isFreqIncrement     = 0.5    # bin width in Hz of IS summed spectrum -q
SpectrumFile        = "spect.dat"
InfrasoundFile      = "is_surf.dat"
windspeed_knots     = 18.     # -w input from command line in knots
windspeed           = 18.     # converted from knots to m/s
fetch_in            = 300.    # -f input from command line in naut miles
fetch               = 300.    # converted from naut mile to meters
voidFraction        = 0.35    # -v
initialBubbleFreqFactor = 1.1  # frequency multiplier for initial pure air bubble -i
bubbleCylinderFactor    = 0.38 # semicylindrical plume radius = fraction wave height -b
bubbleSphereFactor      = 0.38 # spherical plume radius = fraction wave height -c

# Global Array's, created with numpy array
JONSWAP_RMK            = array([])
waveFreq               = array([])
wavelength             = array([])
breakHeight            = array([])
cylindricalPlumeRadius = array([])
sphericalPlumeRadius   = array([])
initialBubbleFreq      = array([])
cylinderCloudFreq      = array([])
sphereCloudFreq        = array([])
isFreq                 = array([])
isInitBubbleAmp        = array([])
isCylinderAmp          = array([])
isSphereAmp            = array([])
infrasound             = array([])
infrasound_dB          = array([])

cloudSound_p1T = array([])
cloudSound_p2T = array([])
cloudSound_p3T = array([])
cloudSound_p4T = array([])
cloudSound_p5T = array([])

# global list for ANNTrainData
ANNTrainData = []
ANNRunData   = []

# global plotting variables
plotFiles  = []
plotSeries = 0
nPlot      = 0

# Polihale data
ph_freq = array( [ 2,    4 ,   6,    8,    10,   12,   14,   16,   18  ] )
ph_1_68 = array( [ 5,    7.3,  6.8,  5.4,  4,    3,    2.2,  1.5,  1.1 ] )
ph_1_82 = array( [ 7,    9,    7.8,  5.7,  4.2,  3,    2.5,  1.7,  0.9 ] )
ph_1_66 = array( [ 0.1,  1.2,  2.3,  1.9,  1.4,  1.2,  1,    0.7,  0.4 ] )
ph_2_14 = array( [ 7.6,  9.8,  8.8,  6.4,  5.3,  4.3,  3.8,  3,    2.2 ] )
ph_2_09 = array( [ 5.3,  8.3,  7.8,  5.7,  4.5,  3.5,  2.6,  1.8,  1.3 ] )
ph_2_00 = array( [ 1.5,  3.4,  4,    3,    2.3,  1.7,  1.7,  1.4,  1.1 ] )
ph_2_59 = array( [ 7.5,  9.3,  9.1,  6.8,  5.5,  4.8,  4.5,  3.7,  2.9 ] )
ph_2_97 = array( [ 8.8,  10.5, 9.5,  7.3,  5.5,  3.9,  3.,   2.2,  1.3 ] )
ph_3_74 = array( [ 15.5, 15.9, 13.9, 11.4, 10.3, 9.2,  8.6,  8,    7.3 ] )
ph_3_83 = array( [ 16,   15.7, 14,   11,   9.5,  8.4,  8,    6.9,  6.2 ] )
ph_4_61 = array( [ 18.1, 17.8, 15.3, 12.8, 11.5, 10.7, 10,   9.4,  8.4 ] )
ph_4_65 = array( [ 17.3, 16.8, 14.2, 11.8, 10.3, 9.4,  8.8,  7.9,  7   ] )
ph_5_33 = array( [ 23,   20.8, 18.1, 15.6, 14.2, 13.2, 12.5, 11.5, 10.5] )
ph_6_15 = array( [ 21,   19,   16.4, 13.8, 12.2, 11.2, 10.5, 9.4,  8.4 ] )

# ordinate arrays for smoothed (spline) Polihale data
y_1_68 = array([])
y_1_82 = array([])
y_1_66 = array([])
y_2_14 = array([])
y_2_09 = array([])
y_2_00 = array([])
y_2_59 = array([])
y_2_97 = array([])
y_3_74 = array([])
y_3_83 = array([])
y_4_61 = array([])
y_4_65 = array([])
y_5_33 = array([])
y_6_15 = array([])

# bubble plume radius increase data: Lamarre & Melville, JASA 95(3) 
# the radius at t/T would be R ~ sqrt(A*)/2, with relative values:
# t/T = [0,   .1,  .2,   .3,   .4,   .5,   .6,   .7,   .8,   .9,   1]
# A*  = [4,    6,   9,   11,   12,   12,   12, 11.5,   11, 10.5,  10]
# R   = [1, 1.22, 1.5, 1.66, 1.73, 1.73, 1.73,  1.7, 1.66, 1.62, 1.58]
plumeRadiusIncrease_t_T = array([0.0, 0.1,  0.2, 0.3,  0.4,  0.5,  0.6,  0.7, 0.8,  0.9,  1.0 ])
plumeRadiusIncrease     = array([1.0, 1.22, 1.5, 1.66, 1.73, 1.73, 1.73, 1.7, 1.66, 1.62, 1.58])
plumeRadiusSpline       = UnivariateSpline( plumeRadiusIncrease_t_T, plumeRadiusIncrease )

##----------------------------------------------------------------------------
## Main module
def main():
    
    print Version
    
    ParseCmdLine()

    global windspeed_knots

    SplineData()

    if createAnimation:
        os.system("rm -f *.png")
        # spectrums from 1 to 40 knots, increasing
        for wspeed in arange (1., 41., windSpeedIncrement):
            windspeed_knots = wspeed
            GenerateSpectrums( "a" )
            # clear all the data arrays for the next plot
            ResetGlobalArrrays()

        # spectrums from 40 to 1 knot, decreasing
        CopySpectrums( "b" )

        # use mplayer mencoder to create an animation in mpg format
        os.system("E:\mplayer\mencoder "
                  "mf://*.png -mf type=png:fps=3 -ovc "
                  "lavc -lavcopts vcodec=wmv2 -oac copy -o output.mpg")
    
        # delete the plot files 
        for fname in plotFiles: os.remove(fname)

    elif trainANNData or runANNData:
        # spectrums from 1 to 40 knots, increasing
        for wspeed in arange (1., 41., windSpeedIncrement):
            windspeed_knots = wspeed
            GenerateSpectrums( None )
            UpdateANNData()
            # clear all the data arrays for the next iteration
            ResetGlobalArrrays()

        WriteANNFile()

    else:
        # single spectrum
        GenerateSpectrums( "c" )
        show()
        
        if writeOutput:
            WriteOutputFiles() 

    print "Normal Exit"
## Main module
##----------------------------------------------------------------------------

##----------------------------------------------------------------------------
def GenerateSpectrums( figLabelHeader ):

    global cloudSound_p1T, cloudSound_p2T, cloudSound_p3T, cloudSound_p4T, cloudSound_p5T
    global voidFraction
    
    WaveSpectrum()           # compute: JONSWAP_RMK waveFreq wavelength

    WaveBreakSpectrum()      # compute: breakHeight

    WaveBubbleSpectrum()     # compute: cylindricalPlumeRadius sphericalPlumeRadius

    AcousticBubbleSpectrum() # compute: cylinderCloudFreq sphereCloudFreq

    if additionalSpectrum:
        cloudSound_p1T = AdditionalBubbleSpectrum( 0.1 )
        cloudSound_p2T = AdditionalBubbleSpectrum( 0.2 )
        cloudSound_p3T = AdditionalBubbleSpectrum( 0.3 )
        cloudSound_p4T = AdditionalBubbleSpectrum( 0.4 )
        cloudSound_p5T = AdditionalBubbleSpectrum( 0.5 )

    SumAcousticSpectrum()    # compute: isFreq infrasound

    if figLabelHeader:
        CreateGraphics( figLabelHeader )

    
##----------------------------------------------------------------------------
def CreateGraphics( figLabelHeader ):

    global plotFiles, plotSeries, nPlot

    # create graphics using pylab (matplotlib)
    
    #-----------------------------------------------------------------
    # ocean wave spectra & acoustic radiation freq vs. ocean wave freq
    clf()
    subplot(2,1,1)
    #figure(1)
    plot( waveFreq, sqrt( JONSWAP_RMK ), 
          waveFreq, breakHeight, 
          waveFreq, initialBubbleFreq, 
          waveFreq, cylinderCloudFreq, linewidth=2 )

    title ( "Wave Spectrum & Acoustic Radiation" )
    ylabel( r'$\rm{ S(m)  :  H_b(m)  :  f_a(Hz) }$' ) # r - 'raw' string for LaTex
    xlabel( "freq (Hz)" )
    axis( [ 0, 0.3, 0, 20 ] )
    windSpeedLabel = "Wind %.1f knots" % windspeed_knots
    SWHLabel       = "SWH %.1f m" % SWH
    fetchLabel     = "Fetch " + str( int(fetch_in) ) + " nm"
    figtext( 0.65, 0.75, windSpeedLabel )
    figtext( 0.65, 0.72, SWHLabel )
    figtext( 0.65, 0.69, fetchLabel )
    legend( ("Wave Spectrum","Break Height","Initial Bubble","Collective Bubble") )
    
    #-----------------------------------------------------------------
    # acoustic amplitude vs. frequency
    subplot(2,1,2)
    # figure(2)
    # clf()
    # line plot of linear summed spectrum
    # plot( isFreq, infrasound, 
    #       isFreq, isInitBubbleAmp, 
    #       isFreq, isCylinderAmp, linewidth=2 )
    # bar chart (histogram) of linear summed spectrum
    # b1 = bar( isFreq, infrasound, width=0.2 )

    # bar chart (histogram) of summed spectrum in dB
    b1 = bar( isFreq, infrasound_dB, width=isFreqIncrement )
    
    # line plots of polihale data
    # If you make multiple lines with one plot command, the kwargs apply
    # to all those lines, i.e. linewidth=2, color="r"
    yAxisMax = max(infrasound_dB) + 3
    if SWH >= 6. and SWH < 10. :
        yAxisMax = max(yAxisMax, y_6_15.max())
        plot( x_spline, y_6_15, linewidth=2, color="r" )
        if plotPolihaleRawData:
            plot( ph_freq, ph_6_15, linewidth=2 )
    elif SWH >= 5. and SWH < 6. :
        yAxisMax = max(yAxisMax, y_5_33.max())
        plot( x_spline, y_5_33, linewidth=2, color="r" )
        if plotPolihaleRawData:
            plot( ph_freq, ph_5_33, linewidth=2 )
    elif SWH >= 4. and SWH < 5. :
        yAxisMax = max(yAxisMax, y_4_61.max(), y_4_65.max())
        plot( x_spline, y_4_61,
              x_spline, y_4_65, linewidth=2, color="r" )
        if plotPolihaleRawData:
            plot( ph_freq, ph_4_61,
                  ph_freq, ph_4_65, linewidth=2 )
    elif SWH >= 3.3 and SWH < 4. :
        yAxisMax = max(yAxisMax, y_3_74.max(), y_3_83.max())
        plot( x_spline, y_3_74,
              x_spline, y_3_83, linewidth=2, color="r" )
        if plotPolihaleRawData:
            plot( ph_freq, ph_3_74,
                  ph_freq, ph_3_83, linewidth=2 )
    elif SWH >= 2.5 and SWH < 3.3 :
        yAxisMax = max(yAxisMax, y_2_59.max(), y_2_97.max())
        plot( x_spline, y_2_59,
              x_spline, y_2_97, linewidth=2, color="r" )
        if plotPolihaleRawData:
            plot( ph_freq, ph_2_59,
                  ph_freq, ph_2_97, linewidth=2 )
    elif SWH >= 2.0 and SWH < 2.5 :
        yAxisMax = max(yAxisMax, y_2_00.max(), y_2_09.max())
        plot( x_spline, y_2_00,
              x_spline, y_2_09,
              x_spline, y_2_14, linewidth=2, color="r" )
        if plotPolihaleRawData:
            plot( ph_freq, ph_2_00,
                  ph_freq, ph_2_09,
                  ph_freq, ph_2_14, linewidth=2 )
    elif SWH >= 1.5 and SWH < 2.0 :
        yAxisMax = max(yAxisMax, y_1_66.max(), y_1_68.max())
        plot( x_spline, y_1_66,
              x_spline, y_1_68,
              x_spline, y_1_82, linewidth=2, color="r" )
        if plotPolihaleRawData:
            plot( ph_freq, ph_1_66,
                  ph_freq, ph_1_68,
                  ph_freq, ph_1_82, linewidth=2 )
    
    xlabel( "freq (Hz)" )
    ylabel( "amplitude (dB)" )
    if SWH >= 2.5 :
        legend( ("Polihale","Polihale","Model") )
    elif SWH >= 1.5:
        legend( ("Polihale","Polihale","Polihale","Model") )
        
    if additionalSpectrum :
        figtext( 0.45, 0.42, "Plume Evolution" )

    if createAnimation:
        yAxisMax = 20
        if SWH > 3.:
            yAxisMax = 30
        axis( [ 0, 20, 0, yAxisMax ] )
    else:
        axis( [ 0, 20, max( 0, infrasound_dB[-1] - 5 ), yAxisMax ] )
        
    xticks( [0,2,4,6,8,10,12,14,16,18,20] )

    #-----------------------------------------------------------------
    # create file name that will be read in order created when
    # mencoder is called to create the animation
    nPlot += 1
    if nPlot > 9 and nPlot < 20:
        plotSeries = 1
    elif nPlot > 19 and nPlot < 30:
        plotSeries = 2
    elif nPlot > 29 and nPlot < 40:
        plotSeries = 3
    elif nPlot > 39 and nPlot < 50:
        plotSeries = 4
    elif nPlot > 49 and nPlot < 60:
        plotSeries = 5
    elif nPlot > 59 and nPlot < 70:
        plotSeries = 6
    elif nPlot > 69 and nPlot < 80:
        plotSeries = 7
    elif nPlot > 79 and nPlot < 90:
        plotSeries = 8
    elif nPlot > 89 and nPlot < 100:
        plotSeries = 9
            
    plotName = "fig" + figLabelHeader + str(plotSeries) + "_" + str(nPlot) + ".png"
    plotFiles.append( plotName )
        
    # savefig will output to these formats:
    # eps, jpeg, pdf, png, ps, svg
    # the format is deduced by the filename extension (.png is default)
    savefig( plotName )

##----------------------------------------------------------------------------
def CopySpectrums( figLabelHeader ):

    global plotFiles, plotSeries, nPlot
    plotSeries = 0
    nPlot      = 0
    plotFiles2 = []

    for i in range ( len(plotFiles)-1, -1, -1 ) :
        nPlot += 1
        if nPlot > 9 and nPlot < 20:
            plotSeries = 1
        elif nPlot > 19 and nPlot < 30:
            plotSeries = 2
        elif nPlot > 29 and nPlot < 40:
            plotSeries = 3
        elif nPlot > 39 and nPlot < 50:
            plotSeries = 4
        elif nPlot > 49 and nPlot < 60:
            plotSeries = 5
        elif nPlot > 59 and nPlot < 70:
            plotSeries = 6
        elif nPlot > 69 and nPlot < 80:
            plotSeries = 7
        elif nPlot > 79 and nPlot < 90:
            plotSeries = 8
        elif nPlot > 89 and nPlot < 100:
            plotSeries = 9
        
        plotName = "fig" + figLabelHeader + str(plotSeries) + "_" + str(nPlot) + ".png"
        plotFiles2.append( plotName )
        os.system("cp %s %s" % (plotFiles[i], plotName) )
        
    for i in range ( len(plotFiles2) ) :
        plotFiles.append( plotFiles2[i] )

    if DEBUG:
        print plotFiles
        

##----------------------------------------------------------------------------
def SplineData():

    global x_spline, y_1_68, y_1_82, y_1_66, y_2_14, y_2_09, y_2_00
    global y_2_59, y_2_97, y_3_74, y_3_83, y_4_61, y_4_65, y_5_33, y_6_15 
    
    # spline smoothing for polihale data
    x_spline = linspace(2, 18, 200)
    spline = UnivariateSpline( ph_freq, ph_1_68, s=0.01 )
    y_1_68 = spline( x_spline )
    spline = UnivariateSpline( ph_freq, ph_1_82, s=0.01 )
    y_1_82 = spline( x_spline )
    spline = UnivariateSpline( ph_freq, ph_1_66, s=0.01 )
    y_1_66 = spline( x_spline )
    spline = UnivariateSpline( ph_freq, ph_2_14, s=0.01 )
    y_2_14 = spline( x_spline )
    spline = UnivariateSpline( ph_freq, ph_2_09, s=0.01 )
    y_2_09 = spline( x_spline )
    spline = UnivariateSpline( ph_freq, ph_2_00, s=0.01 )
    y_2_00 = spline( x_spline )
    spline = UnivariateSpline( ph_freq, ph_2_59, s=0.01 )
    y_2_59 = spline( x_spline )
    spline = UnivariateSpline( ph_freq, ph_2_97, s=0.01 )
    y_2_97 = spline( x_spline )
    spline = UnivariateSpline( ph_freq, ph_3_74, s=0.01 )
    y_3_74 = spline( x_spline )
    spline = UnivariateSpline( ph_freq, ph_3_83, s=0.01 )
    y_3_83 = spline( x_spline )
    spline = UnivariateSpline( ph_freq, ph_4_61, s=0.01 )
    y_4_61 = spline( x_spline )
    spline = UnivariateSpline( ph_freq, ph_4_65, s=0.01 )
    y_4_65 = spline( x_spline )
    spline = UnivariateSpline( ph_freq, ph_5_33, s=0.01 )
    y_5_33 = spline( x_spline )
    spline = UnivariateSpline( ph_freq, ph_6_15, s=0.01 )
    y_6_15 = spline( x_spline )


##----------------------------------------------------------------------------
def SumAcousticSpectrum():
    
    global isFreq, isSphereAmp, isCylinderAmp, isInitBubbleAmp
    global infrasound, infrasound_dB

    # this is the big unknown
    # how to assign spectral amplitudes to the acoustic radiation frequencies?

    # setup an acoustic radiation frequency 'x-axis'
    freqMin = 0.001
    freqMax = 20
    numFreq = int( ceil ( (freqMax - freqMin) / isFreqIncrement ) )
    freq    = freqMin

    # create zero'd arrays of the right length for accumulation
    for i in range( numFreq ):
        isFreq          = append( isFreq, freq )
        infrasound      = append( infrasound, 0 )
        infrasound_dB   = append( infrasound_dB, 0 )
        isSphereAmp     = append( isSphereAmp, 0 )
        isCylinderAmp   = append( isCylinderAmp, 0 )
        isInitBubbleAmp = append( isInitBubbleAmp, 0 )
        freq = freq + isFreqIncrement

    # 'integrate' amplitude contributions of acoustic radiation frequency
    # across the ocean wave frequency domain of single bubble and cloud
    # frequencies, sum both to represent the spectral shape of the infrasound
    # assume a 1/f amplitide scaling of the radiation frequencies, and
    # modulate this 1/f by a coefficient A
    # if we were adding 1 instead of A * ( 1 / isFreq[j] ) that would
    # simply bin (create histogram) of single bubble and cloud frequencies

    A = 2.
        
    for i in range(N):
        for j in range( numFreq ):
            if sphereCloudFreq[i] > (isFreq[j] - isFreqIncrement/2) and \
               sphereCloudFreq[i] < (isFreq[j] + isFreqIncrement/2)     :
                isSphereAmp[j] = isSphereAmp[j] + A / isFreq[j]
                if useSphericalCloud:
                    infrasound[j] = infrasound[j] + A / isFreq[j]
                break
        for j in range( numFreq ):
            if cylinderCloudFreq[i] > (isFreq[j] - isFreqIncrement/2) and \
               cylinderCloudFreq[i] < (isFreq[j] + isFreqIncrement/2)     :
                isCylinderAmp[j] = isCylinderAmp[j] + A / isFreq[j]
                if not useSphericalCloud:
                    infrasound[j] = infrasound[j] + A / isFreq[j]
                break
        if useInitialBubble:
            for j in range( numFreq ):
                if initialBubbleFreq[i] > (isFreq[j] - isFreqIncrement/2) and \
                   initialBubbleFreq[i] < (isFreq[j] + isFreqIncrement/2)     :
                    isInitBubbleAmp[j] = isInitBubbleAmp[j] + A / isFreq[j]
                    infrasound[j] = infrasound[j] + A / isFreq[j]
                    break

        if additionalSpectrum:
            for j in range( numFreq ):
                if cloudSound_p1T[i] > (isFreq[j] - isFreqIncrement/2) and \
                       cloudSound_p1T[i] < (isFreq[j] + isFreqIncrement/2) :
                    infrasound[j] = infrasound[j] + A / isFreq[j]
                    break
            for j in range( numFreq ):
                if cloudSound_p2T[i] > (isFreq[j] - isFreqIncrement/2) and \
                       cloudSound_p2T[i] < (isFreq[j] + isFreqIncrement/2) :
                    infrasound[j] = infrasound[j] + A / isFreq[j]
                    break
            for j in range( numFreq ):
                if cloudSound_p3T[i] > (isFreq[j] - isFreqIncrement/2) and \
                       cloudSound_p3T[i] < (isFreq[j] + isFreqIncrement/2) :
                    infrasound[j] = infrasound[j] + A / isFreq[j]
                    break
            for j in range( numFreq ):
                if cloudSound_p4T[i] > (isFreq[j] - isFreqIncrement/2) and \
                       cloudSound_p4T[i] < (isFreq[j] + isFreqIncrement/2) :
                    infrasound[j] = infrasound[j] + A / isFreq[j]
                    break
            for j in range( numFreq ):
                if cloudSound_p5T[i] > (isFreq[j] - isFreqIncrement/2) and \
                       cloudSound_p5T[i] < (isFreq[j] + isFreqIncrement/2) :
                    infrasound[j] = infrasound[j] + A / isFreq[j]
                    break

    # convert summed spectrum to log scale (dB)
    for i in range( numFreq ):
        if infrasound[i] < 1E-4:
            infrasound_dB[i] = 0
        else:
            infrasound_dB[i] = 10. * log10( infrasound[i] )

    # since the amplitudes are dependent on N, normalize to polihale data
    # use the 10 Hz value of the polihale data
    i10Hz = ph_freq.searchsorted( 10 )
    if i10Hz >= len( ph_freq ) :
        print "Error: SumAcousticSpectrum(): invalid index"
        sys.exit(1)
        
    polihaleAmp10Hz = -1. # SWH < 0.8
    if additionalSpectrum :
        polihaleAmp10Hz = -7. # SWH < 0.8
    if SWH >= 6. and SWH < 10. :
        polihaleAmp10Hz = ph_6_15[ i10Hz ]
    elif SWH >= 5. and SWH < 6. :
        polihaleAmp10Hz = ph_5_33[ i10Hz ]
    elif SWH >= 4. and SWH < 5. :
        polihaleAmp10Hz = max( ph_4_61[ i10Hz ], ph_4_65[ i10Hz ] )
    elif SWH >= 3.3 and SWH < 4. :
        polihaleAmp10Hz = max( ph_3_74[ i10Hz ], ph_3_83[ i10Hz ] )
    elif SWH >= 2.5 and SWH < 3.3 :
        polihaleAmp10Hz = max( ph_2_59[ i10Hz ], ph_2_97[ i10Hz ] )
    elif SWH >= 2.0 and SWH < 2.5 :
        polihaleAmp10Hz = max( ph_2_00[ i10Hz ], ph_2_09[ i10Hz ] )
    elif SWH >= 1.5 and SWH < 2.0 :
        polihaleAmp10Hz = max( ph_1_66[ i10Hz ], ph_1_68[ i10Hz ] )
    elif SWH >= 0.8 and SWH < 1.5 :
        polihaleAmp10Hz = max( ph_1_66[ i10Hz ], ph_1_68[ i10Hz ] )

    # difference between summed spectrum and polihale @ 10 Hz
    i10Hz = isFreq.searchsorted( 10 )
    delta_dB = polihaleAmp10Hz - infrasound_dB[i10Hz]
    #print "delta_dB = ", polihaleAmp10Hz, " - ", infrasound_dB[i10Hz], " = ", delta_dB
    for i in range( numFreq ):
        if infrasound_dB[i] > 0.1:
            infrasound_dB[i] = infrasound_dB[i] + delta_dB


##----------------------------------------------------------------------------
def AdditionalBubbleSpectrum( t_T ):
    
    # create additional contributions based on the reduction of void
    # fraction and increase of plume cross section as time progresses
    # input is t/T = fraction of wave period

    # create a new array returned by this function
    arrayIn = array([])
    
    for i in range( len( cylindricalPlumeRadius ) ):
        if cylindricalPlumeRadius[i] < 0.1 :
            arrayIn = append( arrayIn, 999 )
            continue

        # based on Lamarre & Melville, JASA 95(3) p1317, figure 6(c)
        # the void gradient is dv/dt = -0.56 for t/T < 0.5
        reduced_voidFraction = voidFraction - 0.56 * t_T
        if reduced_voidFraction < 0.01:
            reduced_voidFraction = 0.01
            
        # based on Lamarre & Melville, JASA 95(3) p1317, figure 6(b)
        dx = plumeRadiusIncrease_t_T[1] - plumeRadiusIncrease_t_T[0]
        interpolatePoint = array([t_T])
        r = plumeRadiusSpline( interpolatePoint )
        increasedPlumeRadius = cylindricalPlumeRadius[i] * r
            
        # Breathing-mode oscillation of semicylindrical bubble plume of
        # radius bubble[i], from: Lowen & Melville, JASA 95(3), p1329
        f_R = sqrt( 101325 / ( 1000 * reduced_voidFraction ) ) \
                / ( 2.5 * increasedPlumeRadius )
        arrayIn = append( arrayIn, f_R )

    return arrayIn

##----------------------------------------------------------------------------
def AcousticBubbleSpectrum():
    
    global cylinderCloudFreq, sphereCloudFreq, initialBubbleFreq

    for i in range( len( sphericalPlumeRadius ) ):
        if sphericalPlumeRadius[i] < 0.1 :
            sphereCloudFreq   = append( sphereCloudFreq,   999 )
            cylinderCloudFreq = append( cylinderCloudFreq, 999 )
            initialBubbleFreq = append( initialBubbleFreq, 999 )
            continue

        # Lowest Eigenfrequency of collective bubble oscillation for cloud
        # of radius sphericalPlumeRadius[i], from: Carey 1993, reported in Deane
        # JASA 102(5), 1997
        x = pow( 3 * 101325 / ( 1000 * voidFraction ), 0.5 )
        f_0 = (1 / (2 * pi * sphericalPlumeRadius[i])) * x
        sphereCloudFreq = append( sphereCloudFreq, f_0 )

        # Breathing-mode oscillation of semicylindrical bubble plume of
        # radius bubble[i], from: Lowen & Melville, JASA 95(3), p1329
        f_R = sqrt( 101325 / ( 1000 * voidFraction ) ) / ( 2.5 * cylindricalPlumeRadius[i] )
        cylinderCloudFreq = append( cylinderCloudFreq, f_R )

        # JP: analytical comparison shows f_R = 1.45 * f_0
        # However, the size range for this expression is 0.1 < R_c < 1 (m)
        # with void fractions less than 0.1
        # So we are at scales clearly outside their recommendation...
        # Nonetheless, observations show that the real-world plumes are
        # semicylindrical rather than spherical...
        # and therefore have a higher frequency than a spherical plume
        # will use cylinderCloudFreq as default, user can override with -a

        # volumetric pulsation of the cylinder of pure air with a slightly higher 
        # frequency than the plume... apply a simple scale factor
        initialBubbleFreq = append( initialBubbleFreq,
                                     initialBubbleFreqFactor * cylinderCloudFreq[-1] )

    
##----------------------------------------------------------------------------
def WaveBubbleSpectrum():
    
    global cylindricalPlumeRadius, sphericalPlumeRadius
    
    # convert the breaking wave height spectrum into a spectrum
    # of semicylindrical bubble and spherical plume radius based on
    # scale factors applied to the breaking wave height
    for i in range( len(breakHeight) ):
        if breakHeight[i] < 0.1 :
            cynBubbleRadius    = 0
            sphereBubbleRadius = 0
        else:
            cynBubbleRadius    = breakHeight[i] * bubbleCylinderFactor
            sphereBubbleRadius = breakHeight[i] * bubbleSphereFactor
            
        cylindricalPlumeRadius = append ( cylindricalPlumeRadius, cynBubbleRadius )
        sphericalPlumeRadius   = append ( sphericalPlumeRadius, sphereBubbleRadius )
        #print bubbleRadius,
    
##----------------------------------------------------------------------------
def WaveBreakSpectrum():
    
    global breakHeight
    
    # convert the deepwater wave spectrum into a breaking wave height
    # spectrum by application of the 'breaking index'
    for i in range( len(JONSWAP_RMK) ):
        deepwaterHeight = sqrt( JONSWAP_RMK[i] )
        WaveSteepness   = deepwaterHeight / wavelength[i]
        breakIndex      = WaveBreakIndex( WaveSteepness )
        breakHeight     = append( breakHeight, deepwaterHeight * breakIndex )
    
##----------------------------------------------------------------------------
def WaveBreakIndex( WaveSteepness ):

    # Compute an 'average' breaker height index from figure 2-65 of the SPM

    if WaveSteepness < 0.002:
        return 1.8
    if WaveSteepness > 0.1:
        return 1.

    # upper bound is for beach slope of m = 1/10 = 0.1
    hiIndex = 0.578422 * pow( WaveSteepness, -0.239253 )
    # lower bound is for beach slope of m = 1/50 = 0.02
    loIndex = 0.5403   * pow( WaveSteepness, -0.216176 )

    # since we don't have beach slope, take an average
    breakIndex = ( hiIndex + loIndex ) / 2

    # limit to the plunging breaker range of figure 2-65
    if   breakIndex < 1.2 : breakIndex = 1.2
    elif breakIndex > 1.8 : breakIndex = 1.8
    
    return breakIndex
    
##----------------------------------------------------------------------------
def WaveSpectrum():
    
    global fetch, SWH, peakWaveFreq
    global JONSWAP_RMK, waveFreq, wavelength
    
    omega = 0.1 # initial frequency (rad/s)

    windspeed = windspeed_knots / 1.9425 # convert knots to m/s
    fetch     = fetch_in * 1853          # convert nautical miles to m

    print "fetch=%6.0f (m) %4.0f (nm) windspeed=%5.2f (m/s) %5.2f (knots)" % \
          (fetch, fetch/1853, windspeed, windspeed_knots)

    # Parameters for JONSWAP_RMK spectrum from 'A Probalistic Description of Deepwater
    # Whitecaps', Robert M. Kennedy, PhD Dissertaion, Nova University, May 1978, p 5-8
    nd_fetch     = g * fetch / pow( windspeed, 2 ) # non-dimensional fetch
    Omega        = 22. * (g / windspeed) * pow( nd_fetch, -0.33 ) # max amplitude freq
    peakWaveFreq = Omega / (2*pi)
    epsilon      = 0.076 * pow( nd_fetch , -0.22 ) # Phillips constant (alpha)
    lambda_peak  = 2*pi * g / pow( Omega, 2 ) # max amplitude wavelength
    
    #print "peakWaveFreq=%6.5f nd_fetch=%6.0f epsilon=%7.6f lambda_peak=%4.0f" % \
    #      ( peakWaveFreq, nd_fetch, epsilon, lambda_peak )
    
    for i in range(N):
        f = omega/(2*pi)
        # Compute the JONSWAP_RMK spectrum for each frequency omega
        S1 = epsilon * pow( g, 2 ) * pow( omega, -5 ) 
        S2 = exp( -(5/4) * pow( omega/Omega, -4) ) 
        S3 = pow( 3.3, exp( -(pow(omega - Omega, 2)/(0.0128 * pow(Omega, 2))) ) )
        S_RMK = S1 * S2 * S3
        
        # save the spectral amplitudes in the arrays
        JONSWAP_RMK = append( JONSWAP_RMK, S_RMK )
        # save the wave frequency in Hz
        waveFreq = append( waveFreq, f )
        # save the wavelength
        wavelength = append( wavelength, ( g * pow(1/f,2) )/( 2*pi ) )
        # increment the frequency
            
        omega = omega + omegaIncrement

    if DEBUG:
        print "waveFreq: ",    waveFreq
        print "wavelength: ",  wavelength
        print "JONSWAP_RMK: ", JONSWAP_RMK
    
    # Compute significant wave height
    SWH = 0.
    for i in range(N):
        SWH = SWH + JONSWAP_RMK[i] * omegaIncrement

    SWH = SWH / (2*pi)
    SWH = 4 * sqrt( SWH )

    print "Significant wave height=%5.2f (m) %5.2f (ft)" % (SWH, SWH/.3041)


##----------------------------------------------------------------------------
def ResetGlobalArrrays():
    
    global JONSWAP_RMK, waveFreq, wavelength, breakHeight
    global cylindricalPlumeRadius, sphericalPlumeRadius, initialBubbleFreq
    global cylinderCloudFreq, sphereCloudFreq, isFreq
    global isInitBubbleAmp, isCylinderAmp
    global isSphereAmp, infrasound, infrasound_dB

    JONSWAP_RMK            = array([])
    waveFreq               = array([])
    wavelength             = array([])
    breakHeight            = array([])
    cylindricalPlumeRadius = array([])
    sphericalPlumeRadius   = array([])
    initialBubbleFreq      = array([])
    cylinderCloudFreq      = array([])
    sphereCloudFreq        = array([])
    isFreq                 = array([])
    isInitBubbleAmp        = array([])
    isCylinderAmp          = array([])
    isSphereAmp            = array([])
    infrasound             = array([])
    infrasound_dB          = array([])

##----------------------------------------------------------------------------
def WriteOutputFiles():
    ## 
    ## Open the output files
    if SpectrumFile and InfrasoundFile:
	fdSpectrum   = OpenFile(SpectrumFile,   'w')
	fdInfrasound = OpenFile(InfrasoundFile, 'w')
    else:
	print "Missing output file specification.\n"
	usage()
	return

    # Write the spectrums to the output file
    # Note the wave spectrum is reduced to units of (m) via sqrt()
    fdSpectrum.write("WaveFrequency(Hz) JONSWAP(m) breakHeight(m) "
                     "CylinderFrequency(Hz) InitBubbleFrequency(Hz) "
                     "PeakFrequency=%7.6f(Hz) SWH=%3.1f(m) void=%3.2f\n" % \
                     (peakWaveFreq, SWH, voidFraction))

    for i in range(N):
        # cylindricalK =    pow( (2 * pi * cylinderCloudFreq[i]), 2 ) / g
        # oceanWaveNumber = pow( (2 * pi * waveFreq[i]), 2 ) / g
        fdSpectrum.write("%.5f %.3f %.3f %.3f %.3f\n" % \
                         (waveFreq[i], sqrt(JONSWAP_RMK[i]), breakHeight[i], \
                          cylinderCloudFreq[i], initialBubbleFreq[i] ))
        
    # Write the infrasound to the output file
    fdInfrasound.write("Frequency(Hz) IS(dB) IS_Sphere IS_Cylinder IS_InitBubble "
                       "PeakFrequency=%7.6f(Hz) SWH=%3.1f(m) void=%3.2f\n" % \
                       (peakWaveFreq, SWH, voidFraction))

    for i in range( len(infrasound) ):
        fdInfrasound.write("%.4f %.2f %.0f %.0f %.0f\n" % \
                           ( isFreq[i], infrasound_dB[i], isSphereAmp[i], \
                             isCylinderAmp[i], isInitBubbleAmp[i] ))
    
    print "Wrote %d lines to %s" % (len(JONSWAP_RMK), SpectrumFile)
    print "Wrote %d lines to %s" % (len(infrasound_dB), InfrasoundFile)
    
    ## Close the files
    fdSpectrum.close()
    fdInfrasound.close()


##----------------------------------------------------------------------------
def UpdateANNData():
    global ANNTrainData, ANNRunData

    # ANNTrainData is a list of strings representing the input/output
    # pairs of infrasound_dB (B(wa)) and the breaking wave spectrum
    # breakHeight (Hb(w)). Each neuron will represent a frequency,
    # so if there are 20 values of infrasound_dB, then each value
    # will correspond to a specific acoustic frequency. The actual
    # frequency is not needed by the ANN, just the spectral amplitudes.
    # Two strings are added on each call, one Input, one Output.
    # The whole list is dumped to a file in WriteANNFiles()

    # down sample the infrasound_DB spectrum into
    # 20 points evenly spaced from 1-19 Hz with a cubic spline
    omega_output = linspace(1., 19., 20)
    spline       = UnivariateSpline( isFreq, infrasound_dB )
    IS_Spectrum  = spline( omega_output )
    
    # Create a string for the input data
    inputData = "I:w%2d:SWH%3.1f: " % (windspeed_knots, SWH)
    for i in range( len(IS_Spectrum) ):
        # take abs to avoid values such as -0.01 dB
        inputData += "%.2f " % abs( IS_Spectrum[i] )
        
    inputData += "\n"

    if trainANNData:
        # down sample the breakHeight spectrum from N points into
        # 20 points evenly spaced from 0.035 - 0.2 Hz with a cubic spline
        omega_output  = linspace(0.035, 0.2, 20)
        spline        = UnivariateSpline( waveFreq, breakHeight, s=0.05 )
        breakSpectrum = spline( omega_output ) 
        
        # Create a string for the output data
        outputData = "O:w%2d:SWH%3.1f: " % (windspeed_knots, SWH)
        for i in range( len( breakSpectrum ) ):
            # take abs to avoid values such as -0.01 dB
            outputData += "%.3f " % abs( breakSpectrum[i] )
        
        outputData += "\n"
        # Append this line to both the input and output
        ANNTrainData.append(inputData);
        ANNTrainData.append(outputData);
    elif runANNData:
        # Append only to the input, there is no output needed
        ANNRunData.append(inputData);
    else:
        print "Error: UpdateANNData() invalid state"
        sys.exit(1)
    
##----------------------------------------------------------------------------
def WriteANNFile():
    ## 
    if trainANNData:
        ANNFile = "surf_is_TrainANN.dat"
        ANNData = ANNTrainData
    elif runANNData:
        ANNFile = "surf_is_RunANN.dat"
        ANNData = ANNRunData
        
    ## Open the output file
    fdANN = OpenFile(ANNFile, 'w')

    for i in range( len(ANNData) ):
        fdANN.write( ANNData[i] )
    
    print "Wrote %d lines to %s" % (len(ANNData), ANNFile)

    ## Close the file
    fdANN.close()

##----------------------------------------------------------------------------
def usage():
    print "Usage: ", sys.argv[0]
    print "\t -h (help)"
    print "\t -d (debug)"
    print "\t -z (write output)"
    print "\t -w windspeed (knots)"
    print "\t -f fetch (nautmiles)"
    print "\t -o spectrumOut"
    print "\t -s infrasoundOut"
    print "\t -n Nspectrum"
    print "\t -b bubbleCylinderFactor"
    print "\t -c bubbleSphereFactor"
    print "\t -i initialBubbleFreqFactor"
    print "\t -v voidFraction"
    print "\t -a (use spherical bubble)"
    print "\t -j (don't use initial bubble)"
    print "\t -q isFreqIncrement"
    print "\t -e windSpeedIncrement"
    print "\t -g (additional Spectrum)"
    print "\t -k (write ANN train data)"
    print "\t -r (write ANN run data)"
    print "\t -m (create animation)"

##----------------------------------------------------------------------------
def ParseCmdLine():
    ## 
    global N, useSphericalCloud, isFreqIncrement, omegaIncrement
    global SpectrumFile, InfrasoundFile, windspeed_knots, fetch_in, voidFraction
    global bubbleCylinderFactor, bubbleSphereFactor, initialBubbleFreqFactor
    global DEBUG, writeOutput, windSpeedIncrement, additionalSpectrum
    global useInitialBubble, trainANNData, runANNData, createAnimation
    
    try:
	## Parse the command line arguments in sys.argv via getopt()
	##
	## getopt (args, options[, long_options])
	##   args is the argument list to be parsed,
	##   options is the string of option letters that the script wants to
	##   recognize, with options that require an argument followed by a colon.
	##   long_options, if specified, must be a list of strings with the names
	##   of the long options which should be supported.
	##   The return value consists of two lists: the first is a list of
	##   (option, value) tuple pairs; the second is the list of program
	##   arguments left after the option list was stripped.
	##
	opts, args = getopt.getopt(sys.argv[1:], "hdzw:f:o:s:n:b:c:i:v:ajq:e:gkmr")

    except getopt.GetoptError, error:
	print "ParseCmdLine(): GetoptError: ",error
	return

    if len(opts) == 0:
        usage()
        return

    if DEBUG:
	print "Options are:",opts,"Length:",len(opts)
	print "Arguments are:",args
	if len(opts) > 1:
	    print "opts[0][0]",opts[0][0]," [0][1]", opts[0][1],
	    print " [1][0]", opts[1][0], " [1][1]",opts[1][1]

    argCount = 0
    for optElement in opts:
        argCount = argCount + 1
        if optElement[0] == "-h":
            usage()
            sys.exit(1)
        if optElement[0] == "-d":
            DEBUG = True
        if optElement[0] == "-z":
            writeOutput = True
        if optElement[0] == "-e":
           windSpeedIncrement = float(optElement[1])
        if optElement[0] == "-g":
           additionalSpectrum = True
        if optElement[0] == "-k":
           trainANNData = True
        if optElement[0] == "-r":
           runANNData = True
        if optElement[0] == "-m":
           createAnimation = True
        if optElement[0] == "-o":
            SpectrumFile = optElement[1]
        if optElement[0] == "-s":
            InfrasoundFile = optElement[1]
        if optElement[0] == "-w":
            windspeed_knots = float(optElement[1])
        if optElement[0] == "-f":
            fetch_in = float(optElement[1])
        if optElement[0] == "-b":
            bubbleCylinderFactor = float(optElement[1])
        if optElement[0] == "-c":
           bubbleSphereFactor = float(optElement[1])
        if optElement[0] == "-i":
           initialBubbleFreqFactor = float(optElement[1])
        if optElement[0] == "-v":
           voidFraction = float(optElement[1])
        if optElement[0] == "-a":
           useSphericalCloud = True
        if optElement[0] == "-j":
           useInitialBubble = False
        if optElement[0] == "-n":
           N = int(optElement[1])
           omegaIncrement = 2.25/N
        if optElement[0] == "-q":
           isFreqIncrement = float(optElement[1])

    if trainANNData and createAnimation:
        print "Error: can't set both -k (trainANNData) and " \
              "-m (createAnimation)"
        sys.exit(1)

    if DEBUG:
        print "opts element %d:" % (argCount), optElement
        print "-o", SpectrumFile, "-s", InfrasoundFile, "-w", windspeed_knots, \
              "-f", fetch_in, "-b", bubbleCylinderFactor, "-c", bubbleSphereFactor, \
              "-i", initialBubbleFreqFactor, "-v", voidFraction, \
              "-a", useSphericalCloud, "-n", N, "-q", isFreqIncrement, \
              "-z", writeOutput, "-e", windSpeedIncrement, "-g", additionalSpectrum, \
              "-j", useInitialBubble, "-k", trainANNData, "-r", runANNData, \
              "-m", createAnimation
        
    return

##----------------------------------------------------------------------------
def OpenFile(file, mode):
    try:
        # open() will raise an IOError exception if it fails
        print "Opening file:", file
        fd = open(file,mode)

    except IOError, error:
        print "OpenFile(): I/O error:", error
        sys.exit("OpenFile()")

    except OSError, error:
        print "OpenFile(): OS error:", error
        sys.exit("OpenFile()")
        
    except:
        print "OpenFile(): Unexpected error:", sys.exc_info()[0]
        raise
        sys.exit("OpenFile()")

    else: # executed after the try if no exceptions
        return fd
    
##----------------------------------------------------------------------------
## Provide for cmd line invocation
if __name__ == "__main__":
    main()