How to add boolean expression in for loop python instead of pasting computation lines under every elif
Question:
I am trying to convert a MATLAB script to Python. I have a working code, but it is quite clunky and I have pasted the computation lines under each if and elif lines to make it work. I am hoping to get any advice to make this code more elegant
The script is supposed to:
- Iterate through each value in an array, f.
- If f[j] is larger than 0.09, the variable, sigma = 0.09.
- If f[j] is smaller than 0.07, the variable, sigma = 0.07.
- Then, use the variable sigma to compute a value S[j].
My current code is here below.
(Edited) I put in my actual code after getting suggestions. I didn’t post it earlier as there was a problem with my formula to compute the wave spectrum S (Yes, it is the JONSWAP spectrum), My main focus is still on whether I can make my loop more elegant.
c,d and G and z are intermediate steps taken before computing S, to make things easier for me to compute. The formula in question:
# Part c : Assuming generated waves follows a JONSWAP spectrum with shape parameter = 3
#Plot spectrum and wave time series
import numpy as np
import matplotlib.pyplot as plt
U0 = 24 #m/s, wind speed @10m above water level
Fe = 400 #km,fetch
g = 9.81 #ms^-2, gravity
fp = 1/10.2 #peak frequency (Hz)
T1 = 10 #min duration for wave generation
shape = 3 #shape parameter
f = np.arange(0,180,1)/(T1*60) #array of frequencies
w = 2*np.pi*f
en = 0 + 2*np.pi*np.random.rand(181,1); #generating random phase numbers
#Calculating JONSWAP wave spectral density
a = 0.076*(Fe*1000*g/(U0**2))**-0.22
c = []
d = []
G = []
S = np.empty(max(f.shape),dtype=object)
for j in range(1,max(f.shape)):
if f[j]<fp:
sigma = 0.07
c = -(f[j]-fp)**2
d = 2*(sigma**2)*f[j]**2
G = shape**np.exp(c/d) #shape function
S[j-1] = a*g**2*G/((2*np.pi)**4*(f[j]**5)*np.exp((5/4)*(f[j]/fp)**-4))
elif f[j]>fp:
sigma = 0.09
c = -(f[j]-fp)**2
d = 2*(sigma**2)*f[j]**2
G = shape**np.exp(c/d) #shape function
S[j-1] = a*g**2*G/((2*np.pi)**4*(f[j]**5)*np.exp((5/4)*(f[j]/fp)**-4)) #Wave spectral density
#Plot wave spectrum
plt.plot(f,S,color = 'red', linestyle = 'solid',linewidth = 3, label = 'Fetch = 400km, shape parameter = 3, U0 = 24m/s')
plt.xlim(0,0.3)
plt.ylim(0,70)
plt.xlabel('Frequency (Hz)');plt.ylabel('Wave Spectral Density (m^2/Hz)');
plt.legend(loc='upper right')
plt.title('Wave Spectra of sea')
plt.show()
Answers:
Here you go (using np.where()).
SJ ends up as a normalised JONSWAP energy spectrum (i.e., area underneath = 1.0). Multiply it by (1/16)(rho.g)Hm02/fp if you want the absolute energy spectrum. (Hm0 is significant wave height; fp is peak frequency.)
import numpy as np
import matplotlib.pyplot as plt
nx = 400000
df = 0.0001
fmax = nx * df
ffp = np.linspace( df, fmax, nx ) # frequencies (as f/f_p)
#########################################################################
# The following spectra are normalised (to area = 1.0 ) #
# Multiply by (1/16) (rho.g) H_m0^2 / fp to get absolute energy density #
#########################################################################
# Bretschneider spectrum (normalised)
SB = 5.0 / ffp ** 5 * np.exp( -1.25 / ffp ** 4 )
# JONSWAP spectrum (narrow-band version of above)
gamma = 3.3
sigma = np.where( ffp < 1.0, 0.07, 0.09 )
SJ = SB * gamma ** np.exp( -0.5 * ( ( ffp - 1 ) / sigma ) ** 2 )
SJ /= SJ.sum() * df # Normalise to area 1
fs = 14
fig, ax = plt.subplots( figsize=(8,5) )
ax.set_xlim( 0.0, 3.0 )
ax.set_ylim( 0.0, 3.5 )
ax.set_xlabel( r"$f / f_p$" , fontsize=fs )
ax.set_ylabel( r"$S (normalised)$", fontsize=fs )
ax.xaxis.set_tick_params( labelsize=fs )
ax.yaxis.set_tick_params( labelsize=fs )
ax.plot( ffp, SB, "b-" , label="Bretschneider" )
ax.plot( ffp, SJ, "r-.", label="JONSWAP" )
ax.legend()
plt.show()
I am trying to convert a MATLAB script to Python. I have a working code, but it is quite clunky and I have pasted the computation lines under each if and elif lines to make it work. I am hoping to get any advice to make this code more elegant
The script is supposed to:
- Iterate through each value in an array, f.
- If f[j] is larger than 0.09, the variable, sigma = 0.09.
- If f[j] is smaller than 0.07, the variable, sigma = 0.07.
- Then, use the variable sigma to compute a value S[j].
My current code is here below.
(Edited) I put in my actual code after getting suggestions. I didn’t post it earlier as there was a problem with my formula to compute the wave spectrum S (Yes, it is the JONSWAP spectrum), My main focus is still on whether I can make my loop more elegant.
c,d and G and z are intermediate steps taken before computing S, to make things easier for me to compute. The formula in question:
# Part c : Assuming generated waves follows a JONSWAP spectrum with shape parameter = 3
#Plot spectrum and wave time series
import numpy as np
import matplotlib.pyplot as plt
U0 = 24 #m/s, wind speed @10m above water level
Fe = 400 #km,fetch
g = 9.81 #ms^-2, gravity
fp = 1/10.2 #peak frequency (Hz)
T1 = 10 #min duration for wave generation
shape = 3 #shape parameter
f = np.arange(0,180,1)/(T1*60) #array of frequencies
w = 2*np.pi*f
en = 0 + 2*np.pi*np.random.rand(181,1); #generating random phase numbers
#Calculating JONSWAP wave spectral density
a = 0.076*(Fe*1000*g/(U0**2))**-0.22
c = []
d = []
G = []
S = np.empty(max(f.shape),dtype=object)
for j in range(1,max(f.shape)):
if f[j]<fp:
sigma = 0.07
c = -(f[j]-fp)**2
d = 2*(sigma**2)*f[j]**2
G = shape**np.exp(c/d) #shape function
S[j-1] = a*g**2*G/((2*np.pi)**4*(f[j]**5)*np.exp((5/4)*(f[j]/fp)**-4))
elif f[j]>fp:
sigma = 0.09
c = -(f[j]-fp)**2
d = 2*(sigma**2)*f[j]**2
G = shape**np.exp(c/d) #shape function
S[j-1] = a*g**2*G/((2*np.pi)**4*(f[j]**5)*np.exp((5/4)*(f[j]/fp)**-4)) #Wave spectral density
#Plot wave spectrum
plt.plot(f,S,color = 'red', linestyle = 'solid',linewidth = 3, label = 'Fetch = 400km, shape parameter = 3, U0 = 24m/s')
plt.xlim(0,0.3)
plt.ylim(0,70)
plt.xlabel('Frequency (Hz)');plt.ylabel('Wave Spectral Density (m^2/Hz)');
plt.legend(loc='upper right')
plt.title('Wave Spectra of sea')
plt.show()
Here you go (using np.where()).
SJ ends up as a normalised JONSWAP energy spectrum (i.e., area underneath = 1.0). Multiply it by (1/16)(rho.g)Hm02/fp if you want the absolute energy spectrum. (Hm0 is significant wave height; fp is peak frequency.)
import numpy as np
import matplotlib.pyplot as plt
nx = 400000
df = 0.0001
fmax = nx * df
ffp = np.linspace( df, fmax, nx ) # frequencies (as f/f_p)
#########################################################################
# The following spectra are normalised (to area = 1.0 ) #
# Multiply by (1/16) (rho.g) H_m0^2 / fp to get absolute energy density #
#########################################################################
# Bretschneider spectrum (normalised)
SB = 5.0 / ffp ** 5 * np.exp( -1.25 / ffp ** 4 )
# JONSWAP spectrum (narrow-band version of above)
gamma = 3.3
sigma = np.where( ffp < 1.0, 0.07, 0.09 )
SJ = SB * gamma ** np.exp( -0.5 * ( ( ffp - 1 ) / sigma ) ** 2 )
SJ /= SJ.sum() * df # Normalise to area 1
fs = 14
fig, ax = plt.subplots( figsize=(8,5) )
ax.set_xlim( 0.0, 3.0 )
ax.set_ylim( 0.0, 3.5 )
ax.set_xlabel( r"$f / f_p$" , fontsize=fs )
ax.set_ylabel( r"$S (normalised)$", fontsize=fs )
ax.xaxis.set_tick_params( labelsize=fs )
ax.yaxis.set_tick_params( labelsize=fs )
ax.plot( ffp, SB, "b-" , label="Bretschneider" )
ax.plot( ffp, SJ, "r-.", label="JONSWAP" )
ax.legend()
plt.show()