def helicity(prof, lower, upper, stu=0, stv=0, dp=-1, exact=True): ''' Calculates the relative helicity (m2/s2) of a layer from lower to upper. If storm-motion vector is supplied, storm-relative helicity, both positve and negative, is returned. Parameters ---------- prof : profile object Profile Object lower : number Bottom level of layer (m, AGL) upper : number Top level of layer (m, AGL) stu : number (optional; default = 0) U-component of storm-motion stv : number (optional; default = 0) V-component of storm-motion dp : negative integer (optional; default -1) The pressure increment for the interpolated sounding exact : bool (optional; default = True) Switch to choose between using the exact data (slower) or using interpolated sounding at 'dp' pressure levels (faster) Returns ------- phel+nhel : number Combined Helicity (m2/s2) phel : number Positive Helicity (m2/s2) nhel : number Negative Helicity (m2/s2) ''' if lower != upper: lower = interp.to_msl(prof, lower) upper = interp.to_msl(prof, upper) plower = interp.pres(prof, lower) pupper = interp.pres(prof, upper) if exact: ind1 = np.where(plower >= prof.pres)[0].min() ind2 = np.where(pupper <= prof.pres)[0].max() u1, v1 = interp.components(prof, plower) u2, v2 = interp.components(prof, pupper) u = np.concatenate([[u1], prof.u[ind1:ind2 + 1].compressed(), [u2]]) v = np.concatenate([[v1], prof.v[ind1:ind2 + 1].compressed(), [v2]]) else: ps = np.arange(plower, pupper + dp, dp) u, v = interp.components(prof, ps) sru = utils.KTS2MS(u - stu) srv = utils.KTS2MS(v - stv) layers = (sru[1:] * srv[:-1]) - (sru[:-1] * srv[1:]) phel = layers[layers > 0].sum() nhel = layers[layers < 0].sum() else: phel = nhel = 0 return phel + nhel, phel, nhel
def wind_shear(prof, pbot=850, ptop=250): ''' Calculates the shear between the wind at (pbot) and (ptop). Parameters ---------- prof: profile object Profile object pbot : number (optional; default 850 hPa) Pressure of the bottom level (hPa) ptop : number (optional; default 250 hPa) Pressure of the top level (hPa) Returns ------- shu : number U-component (kts) shv : number V-component (kts) ''' if prof.wdir.count() == 0 or not utils.QC(ptop) or not utils.QC(pbot): return ma.masked, ma.masked ubot, vbot = interp.components(prof, pbot) utop, vtop = interp.components(prof, ptop) shu = utop - ubot shv = vtop - vbot return shu, shv
def wind_shear(prof, pbot=850, ptop=250): ''' Calculates the shear between the wind at (pbot) and (ptop). Parameters ---------- prof: profile object Profile object pbot : number (optional; default 850 hPa) Pressure of the bottom level (hPa) ptop : number (optional; default 250 hPa) Pressure of the top level (hPa) Returns ------- shu : number U-component shv : number V-component ''' ubot, vbot = interp.components(prof, pbot) utop, vtop = interp.components(prof, ptop) shu = utop - ubot shv = vtop - vbot return shu, shv
def helicity(prof, lower, upper, stu=0, stv=0, dp=-1, exact=True): ''' Calculates the relative helicity (m2/s2) of a layer from lower to upper. If storm-motion vector is supplied, storm-relative helicity, both positve and negative, is returned. Parameters ---------- prof : profile object Profile Object lower : number Bottom level of layer (m, AGL) upper : number Top level of layer (m, AGL) stu : number (optional; default = 0) U-component of storm-motion stv : number (optional; default = 0) V-component of storm-motion dp : negative integer (optional; default -1) The pressure increment for the interpolated sounding exact : bool (optional; default = True) Switch to choose between using the exact data (slower) or using interpolated sounding at 'dp' pressure levels (faster) Returns ------- phel+nhel : number Combined Helicity (m2/s2) phel : number Positive Helicity (m2/s2) nhel : number Negative Helicity (m2/s2) ''' if lower != upper: lower = interp.to_msl(prof, lower) upper = interp.to_msl(prof, upper) plower = interp.pres(prof, lower) pupper = interp.pres(prof, upper) if np.isnan(plower) or np.isnan(pupper): return np.ma.masked, np.ma.masked, np.ma.masked if exact: ind1 = np.where(plower >= prof.pres)[0].min() ind2 = np.where(pupper <= prof.pres)[0].max() u1, v1 = interp.components(prof, plower) u2, v2 = interp.components(prof, pupper) u = np.concatenate([[u1], prof.u[ind1:ind2+1].compressed(), [u2]]) v = np.concatenate([[v1], prof.v[ind1:ind2+1].compressed(), [v2]]) else: ps = np.arange(plower, pupper+dp, dp) u, v = interp.components(prof, ps) sru = utils.KTS2MS(u - stu) srv = utils.KTS2MS(v - stv) layers = (sru[1:] * srv[:-1]) - (sru[:-1] * srv[1:]) phel = layers[layers > 0].sum() nhel = layers[layers < 0].sum() else: phel = nhel = 0 return phel+nhel, phel, nhel
def max_wind(lower, upper, prof): ''' Finds the maximum wind speed of the layer given by lower and upper levels. In the event of the maximum wind speed occurring at multiple levels, the lowest level it occurs is returned. Inputs ------ lower (float) Bottom level of layer (m, AGL) upper (float) Top level of layer (m, AGL) prof (profile object) Profile Object Returns ------- p (float) Pressure level (hPa) of max wind speed maxu (float) Maximum U-component maxv (float) Maximum V-component ''' if lower == -1: lower = prof.gSndg[prof.sfc][prof.pind] if upper == -1: upper = prof.gSndg[prof.gNumLevels - 1][prof.pind] # Find lower and upper ind bounds for looping i = 0 while prof.gSndg[i][prof.pind] > lower: i += 1 lptr = i while prof.gSndg[i][prof.pind] > upper: i += 1 uptr = i # Start with interpolated bottom level maxu, maxv = interp.components(lower, prof) maxspd = vector.comp2vec(maxu, maxv)[1] p = lower # Loop through all levels in layer for i in range(lptr, uptr + 1): if QC(prof.gSndg[i][prof.pind]) and QC(prof.gSndg[i][prof.uind]) and \ QC(prof.gSndg[i][prof.vind]): spd = vector.comp2vec(prof.gSndg[i][prof.uind], prof.gSndg[i][prof.vind])[1] if spd > maxspd: maxspd = spd maxu = prof.gSndg[i][prof.uind] maxv = prof.gSndg[i][prof.vind] p = prof.gSndg[i][prof.pind] # Finish with interpolated top level tmpu, tmpv = interp.components(upper, prof) tmpspd = vector.comp2vec(tmpu, tmpv)[1] if tmpspd > maxspd: maxu = tmpu maxv = tmpv maxspd = tmpspd p = upper return p, maxu, maxv
def max_wind(lower, upper, prof): ''' Finds the maximum wind speed of the layer given by lower and upper levels. In the event of the maximum wind speed occurring at multiple levels, the lowest level it occurs is returned. Inputs ------ lower (float) Bottom level of layer (m, AGL) upper (float) Top level of layer (m, AGL) prof (profile object) Profile Object Returns ------- p (float) Pressure level (hPa) of max wind speed maxu (float) Maximum U-component maxv (float) Maximum V-component ''' if lower == -1: lower = prof.gSndg[prof.sfc][prof.pind] if upper == -1: upper = prof.gSndg[prof.gNumLevels-1][prof.pind] # Find lower and upper ind bounds for looping i = 0 while prof.gSndg[i][prof.pind] > lower: i+=1 lptr = i while prof.gSndg[i][prof.pind] > upper: i+=1 uptr = i # Start with interpolated bottom level maxu, maxv = interp.components(lower, prof) maxspd = vector.comp2vec(maxu, maxv)[1] p = lower # Loop through all levels in layer for i in range(lptr, uptr+1): if QC(prof.gSndg[i][prof.pind]) and QC(prof.gSndg[i][prof.uind]) and \ QC(prof.gSndg[i][prof.vind]): spd = vector.comp2vec(prof.gSndg[i][prof.uind], prof.gSndg[i][prof.vind])[1] if spd > maxspd: maxspd = spd maxu = prof.gSndg[i][prof.uind] maxv = prof.gSndg[i][prof.vind] p = prof.gSndg[i][prof.pind] # Finish with interpolated top level tmpu, tmpv = interp.components(upper, prof) tmpspd = vector.comp2vec(tmpu, tmpv)[1] if tmpspd > maxspd: maxu = tmpu maxv = tmpv maxspd = tmpspd p = upper return p, maxu, maxv
def test_components(): input_p = 900 correct_u, correct_v = -5.53976475, 20.6746835 correct = [correct_u, correct_v] returned = interp.components(prof, input_p) npt.assert_almost_equal(returned, correct) input_p = [900, 800, 600, 400] correct_u = np.asarray([-5.53976475, 5.95267234, 23.10783339, 42.]) correct_v = np.asarray([20.6746835, 15.54170573, -9.37502817, 0]) correct = [correct_u, correct_v] returned = interp.components(prof, input_p) npt.assert_almost_equal(returned, correct)
def mean_wind_npw(prof, pbot=850., ptop=250., dp=-1, stu=0, stv=0): ''' Calculates a non-pressure-weighted mean wind through a layer. The default layer is 850 to 200 hPa. Parameters ---------- prof: profile object Profile object pbot : number (optional; default 850 hPa) Pressure of the bottom level (hPa) ptop : number (optional; default 250 hPa) Pressure of the top level (hPa) dp : negative integer (optional; default -1) The pressure increment for the interpolated sounding stu : number (optional; default 0) U-component of storm-motion vector stv : number (optional; default 0) V-component of storm-motion vector Returns ------- mnu : number U-component mnv : number V-component ''' if dp > 0: dp = -dp ps = np.arange(pbot, ptop+dp, dp) u, v = interp.components(prof, ps) # u -= stu; v -= stv return u.mean()-stu, v.mean()-stv
def mean_wind_npw(prof, pbot=850., ptop=250., dp=-1, stu=0, stv=0): ''' Calculates a non-pressure-weighted mean wind through a layer. The default layer is 850 to 200 hPa. Parameters ---------- prof: profile object Profile object pbot : number (optional; default 850 hPa) Pressure of the bottom level (hPa) ptop : number (optional; default 250 hPa) Pressure of the top level (hPa) dp : negative integer (optional; default -1) The pressure increment for the interpolated sounding stu : number (optional; default 0) U-component of storm-motion vector stv : number (optional; default 0) V-component of storm-motion vector Returns ------- mnu : number U-component mnv : number V-component ''' if dp > 0: dp = -dp ps = np.arange(pbot, ptop + dp, dp) u, v = interp.components(prof, ps) # u -= stu; v -= stv return u.mean() - stu, v.mean() - stv
def mean_wind_npw(pbot, ptop, prof, psteps=20, stu=0, stv=0): ''' Calculates a pressure-weighted mean wind through a layer. The default layer is 850 to 200 hPa. Inputs ------ pbot (float) Pressure of the bottom level (hPa) ptop (float) Pressure of the top level (hPa) prof (profile object) Profile Object psteps (int; optional) Number of steps to loop through (int) stu (float; optional) U-component of storm-motion vector stv (float; optional) V-component of storm-motion vector Returns ------- mnu (float) U-component mnv (float) V-component ''' if pbot == -1: lower = 850. if ptop == -1: upper = 200. pinc = int((pbot - ptop) / psteps) if pinc < 1: u1, v1 = interp.components(pbot, prof) u2, v2 = interp.components(ptop, prof) u1 = (u1 - stu) * pbot v1 = (v1 - stv) * pbot u2 = (u2 - stu) * ptop v2 = (v2 - stv) * ptop usum = u1 + u2 vsum = v1 + v2 wgt = 2 else: wgt = 0 usum = 0 vsum = 0 for p in range(int(pbot), int(ptop), -pinc): utmp, vtmp = interp.components(p, prof) usum += (utmp - stu) vsum += (vtmp - stv) wgt += 1 return float(usum / wgt), float(vsum / wgt)
def wind_shear(pbot, ptop, prof): ''' Calculates the shear between the wind at (pbot) and (ptop). Inputs ------ pbot (float) Pressure of the bottom level (hPa) ptop (float) Pressure of the top level (hPa) prof (profile object) Profile Object Returns ------- shu (float) U-component shv (float) V-component ''' ubot, vbot = interp.components(pbot, prof) utop, vtop = interp.components(ptop, prof) shu = utop - ubot shv = vtop - vbot return shu, shv
def critical_angle(prof, stu=0, stv=0): ''' Calculates the critical angle (degrees) as specified by Esterheld and Giuliano (2008). If the critical angle is 90 degrees, this indicates that the lowest 500 meters of the storm is experiencing pure streamwise vorticity. Parameters ---------- prof : profile object Profile Object stu : number (optional; default = 0) U-component of storm-motion (kts) stv : number (optional; default = 0) V-component of storm-motion (kts) Returns ------- angle : number Critical Angle (degrees) ''' if prof.wdir.count() == 0: return ma.masked if not utils.QC(stu) or not utils.QC(stv): return ma.masked pres_500m = interp.pres(prof, interp.to_msl(prof, 500)) u500, v500 = interp.components(prof, pres_500m) sfc_u, sfc_v = interp.components(prof, prof.pres[prof.sfc]) vec1_u, vec1_v = u500 - sfc_u, v500 - sfc_v vec2_u, vec2_v = stu - sfc_u, stv - sfc_v vec_1_mag = np.sqrt(np.power(vec1_u, 2) + np.power(vec1_v, 2)) vec_2_mag = np.sqrt(np.power(vec2_u, 2) + np.power(vec2_v, 2)) dot = vec1_u * vec2_u + vec1_v * vec2_v angle = np.degrees(np.arccos(dot / (vec_1_mag * vec_2_mag))) return angle
def critical_angle(prof, stu=0, stv=0): ''' Calculates the critical angle (degrees) as specified by Esterheld and Giuliano (2008). If the critical angle is 90 degrees, this indicates that the lowest 500 meters of the storm is experiencing pure streamwise vorticity. Parameters ---------- prof : profile object Profile Object stu : number (optional; default = 0) U-component of storm-motion stv : number (optional; default = 0) V-component of storm-motion Returns ------- angle : number Critical Angle (degrees) ''' if not utils.QC(stu) or not utils.QC(stv): return ma.masked pres_500m = interp.pres(prof, interp.to_msl(prof, 500)) u500, v500 = interp.components(prof, pres_500m) sfc_u, sfc_v = interp.components(prof, prof.pres[prof.sfc]) vec1_u, vec1_v = u500 - sfc_u, v500 - sfc_v vec2_u, vec2_v = stu - sfc_u, stv - sfc_v vec_1_mag = np.sqrt(np.power(vec1_u, 2) + np.power(vec1_v, 2)) vec_2_mag = np.sqrt(np.power(vec2_u, 2) + np.power(vec2_v, 2)) dot = vec1_u * vec2_u + vec1_v * vec2_v angle = np.degrees(np.arccos(dot / (vec_1_mag * vec_2_mag))) return angle
def interp(self, dp=-25): """ Interpolate the profile object to a specific pressure level spacing. """ if self.isEnsemble(): raise ValueError("Cannot interpolate the ensemble profiles.") prof = self._profs[self._highlight][self._prof_idx] # Save original, if one hasn't already been saved if self._prof_idx not in self._orig_profs: self._orig_profs[self._prof_idx] = prof cls = type(prof) # Copy the tmpc, dwpc, etc. profiles to be inteprolated keys = ['tmpc', 'dwpc', 'hght', 'wspd', 'wdir', 'omeg'] prof_vars = { 'pres': np.arange(prof.pres[prof.sfc], prof.pres[prof.top], dp) } prof_vars['tmpc'] = interp.temp(prof, prof_vars['pres']) prof_vars['dwpc'] = interp.dwpt(prof, prof_vars['pres']) prof_vars['hght'] = interp.hght(prof, prof_vars['pres']) if prof.omeg.all() is not np.ma.masked: prof_vars['omeg'] = interp.omeg(prof, prof_vars['pres']) else: prof_vars['omeg'] = np.ma.masked_array(prof_vars['pres'], mask=np.ones(len( prof_vars['pres']), dtype=int)) u, v = interp.components(prof, prof_vars['pres']) prof_vars['u'] = u prof_vars['v'] = v interp_prof = cls.copy(prof, **prof_vars) self._profs[self._highlight][self._prof_idx] = interp_prof # Save the original like in modify() if self._prof_idx not in self._interp_profs: self._interp_profs[self._prof_idx] = interp_prof # Update bookkeeping self._interp[self._prof_idx] = True
def interp(self, dp=-25): """ Interpolate the profile object to a specific pressure level spacing. """ if self.isEnsemble(): raise ValueError("Cannot interpolate the ensemble profiles.") prof = self._profs[self._highlight][self._prof_idx] # Save original, if one hasn't already been saved if self._prof_idx not in self._orig_profs: self._orig_profs[self._prof_idx] = prof cls = type(prof) # Copy the tmpc, dwpc, etc. profiles to be inteprolated keys = ['tmpc', 'dwpc', 'hght', 'wspd', 'wdir', 'omeg'] prof_vars = {'pres': np.arange(prof.pres[prof.sfc], prof.pres[prof.top], dp)} prof_vars['tmpc'] = interp.temp(prof, prof_vars['pres']) prof_vars['dwpc'] = interp.dwpt(prof, prof_vars['pres']) prof_vars['hght'] = interp.hght(prof, prof_vars['pres']) if prof.omeg.all() is not np.ma.masked: prof_vars['omeg'] = interp.omeg(prof, prof_vars['pres']) else: prof_vars['omeg'] = np.ma.masked_array(prof_vars['pres'], mask=np.ones(len(prof_vars['pres']), dtype=int)) u, v = interp.components(prof, prof_vars['pres']) prof_vars['u'] = u prof_vars['v'] = v interp_prof = cls.copy(prof, **prof_vars) self._profs[self._highlight][self._prof_idx] = interp_prof # Save the original like in modify() if self._prof_idx not in self._interp_profs: self._interp_profs[self._prof_idx] = interp_prof # Update bookkeeping self._interp[self._prof_idx] = True
def mean_wind(prof, pbot=850, ptop=250, dp=-1, stu=0, stv=0): ''' Calculates a pressure-weighted mean wind through a layer. The default layer is 850 to 200 hPa. Parameters ---------- prof: profile object Profile object pbot : number (optional; default 850 hPa) Pressure of the bottom level (hPa) ptop : number (optional; default 250 hPa) Pressure of the top level (hPa) dp : negative integer (optional; default -1) The pressure increment for the interpolated sounding stu : number (optional; default 0) U-component of storm-motion vector (kts) stv : number (optional; default 0) V-component of storm-motion vector (kts) Returns ------- mnu : number U-component (kts) mnv : number V-component (kts) ''' if dp > 0: dp = -dp if not utils.QC(pbot) or not utils.QC(ptop): return ma.masked, ma.masked if prof.wdir.count() == 0: return ma.masked, ma.masked ps = np.arange(pbot, ptop + dp, dp) u, v = interp.components(prof, ps) # u -= stu; v -= stv return ma.average(u, weights=ps) - stu, ma.average(v, weights=ps) - stv
def helicity(lower, upper, prof, stu=0, stv=0): ''' Calculates the relative helicity (m2/s2) of a layer from lower to upper. If storm-motion vector is supplied, storm-relative helicity, both positve and negative, is returned. Inputs ------ lower (float) Bottom level of layer (m, AGL) upper (float) Top level of layer (m, AGL) prof (profile object) Profile Object stu (float; optional) U-component of storm-motion stv (float; optional) V-component of storm-motion Returns ------- phel+nhel (float) Combined Helicity (m2/s2) phel (float) Positive Helicity (m2/s2) nhel (float) Negative Helicity (m2/s2) ''' lower = interp.msl(lower, prof) upper = interp.msl(upper, prof) plower = interp.pres(lower, prof) pupper = interp.pres(upper, prof) phel = 0 nhel = 0 # Find lower and upper ind bounds for looping i = 0 while interp.msl(prof.gSndg[i][prof.zind], prof) < lower: i+=1 lptr = i if interp.msl(prof.gSndg[i][prof.zind], prof) == lower: lptr+=1 while interp.msl(prof.gSndg[i][prof.zind], prof) <= upper: i+=1 uptr = i if interp.msl(prof.gSndg[i][prof.zind], prof) == upper: uptr-=1 # Integrate from interpolated bottom level to iptr level sru1, srv1 = interp.components(plower, prof) sru1 = KTS2MS(sru1 - stu) srv1 = KTS2MS(srv1 - stv) # Loop through levels for i in range(lptr, uptr+1): if QC(prof.gSndg[i][prof.uind]) and QC(prof.gSndg[i][prof.vind]): sru2, srv2 = interp.components(prof.gSndg[i][prof.pind], prof) sru2 = KTS2MS(sru2 - stu) srv2 = KTS2MS(srv2 - stv) lyrh = (sru2 * srv1) - (sru1 * srv2) if lyrh > 0: phel += lyrh else: nhel += lyrh sru1 = sru2 srv1 = srv2 # Integrate from tptr level to interpolated top level sru2, srv2 = interp.components(pupper, prof) sru2 = KTS2MS(sru2 - stu) srv2 = KTS2MS(srv2 - stv) lyrh = (sru2 * srv1) - (sru1 * srv2) if lyrh > 0: phel += lyrh else: nhel += lyrh return phel+nhel, phel, nhel
def total_shear(prof, pbot=850, ptop=250, dp=-1, exact=True): ''' Calculates the total shear (also known as the hodograph length) between the wind at (pbot) and (ptop). Parameters ---------- prof: profile object Profile object pbot : number (optional; default 850 hPa) Pressure of the bottom level (hPa) ptop : number (optional; default 250 hPa) Pressure of the top level (hPa) dp : negative integer (optional; default -1) The pressure increment for the interpolated sounding exact : bool (optional; default = True) Switch to choose between using the exact data (slower) or using interpolated sounding at 'dp' pressure levels (faster) Returns ------- tot_pos_shr : number Total positive shear tot_neg_shr : number Total negative shear tot_net_shr : number Total net shear (subtracting negative shear from positive shear) tot_abs_shr : number Total absolute shear (adding positive and negative shear together) ''' if np.isnan(pbot) or np.isnan(ptop) or \ type(pbot) == type(ma.masked) or type(ptop) == type(ma.masked): print np.ma.masked, np.ma.masked, np.ma.masked, np.ma.masked if exact: ind1 = np.where(pbot > prof.pres)[0].min() ind2 = np.where(ptop < prof.pres)[0].max() u1, v1 = interp.components(prof, pbot) u2, v2 = interp.components(prof, ptop) u = np.concatenate([[u1], prof.u[ind1:ind2+1].compressed(), [u2]]) v = np.concatenate([[v1], prof.v[ind1:ind2+1].compressed(), [v2]]) else: ps = np.arange(pbot, ptop+dp, dp) u, v = interp.components(prof, ps) shu = u[1:] - u[:-1] shv = v[1:] - v[:-1] shr = ( ( np.power(shu, 2) + np.power(shv, 2) ) ** 0.5 ) wdr, wsp = utils.comp2vec(u,v) t_wdr = wdr[1:] mod = 180 - wdr[:-1] t_wdr = t_wdr + mod idx1 = ma.where(t_wdr < 0)[0] idx2 = ma.where(t_wdr >= 360)[0] t_wdr[idx1] = t_wdr[idx1] + 360 t_wdr[idx2] = t_wdr[idx2] - 360 d_wdr = t_wdr - 180 d_wsp = wsp[1:] - wsp[:-1] idxdp = ma.where(d_wdr > 0)[0] idxdn = ma.where(d_wdr < 0)[0] idxsp = ma.where(np.logical_and(d_wdr == 0, d_wsp > 0) == True)[0] idxsn = ma.where(np.logical_and(d_wdr == 0, d_wsp < 0) == True)[0] if not utils.QC(idxdp): pos_dir_shr = ma.masked else: pos_dir_shr = shr[idxdp].sum() if not utils.QC(idxdn): neg_dir_shr = ma.masked else: neg_dir_shr = shr[idxdn].sum() if not utils.QC(idxsp): pos_spd_shr = ma.masked else: pos_spd_shr = shr[idxsp].sum() if not utils.QC(idxsn): neg_spd_shr = ma.masked else: neg_spd_shr = shr[idxsn].sum() tot_pos_shr = pos_dir_shr + pos_spd_shr tot_neg_shr = neg_dir_shr + neg_spd_shr tot_net_shr = tot_pos_shr - tot_neg_shr tot_abs_shr = tot_pos_shr + tot_neg_shr return tot_pos_shr, tot_neg_shr, tot_net_shr, tot_abs_shr
''' Create the Sounding (Profile) Object '''
def helicity(lower, upper, prof, stu=0, stv=0): ''' Calculates the relative helicity (m2/s2) of a layer from lower to upper. If storm-motion vector is supplied, storm-relative helicity, both positve and negative, is returned. Inputs ------ lower (float) Bottom level of layer (m, AGL) upper (float) Top level of layer (m, AGL) prof (profile object) Profile Object stu (float; optional) U-component of storm-motion stv (float; optional) V-component of storm-motion Returns ------- phel+nhel (float) Combined Helicity (m2/s2) phel (float) Positive Helicity (m2/s2) nhel (float) Negative Helicity (m2/s2) ''' lower = interp.msl(lower, prof) upper = interp.msl(upper, prof) plower = interp.pres(lower, prof) pupper = interp.pres(upper, prof) phel = 0 nhel = 0 # Find lower and upper ind bounds for looping i = 0 while interp.msl(prof.gSndg[i][prof.zind], prof) < lower: i += 1 lptr = i if interp.msl(prof.gSndg[i][prof.zind], prof) == lower: lptr += 1 while interp.msl(prof.gSndg[i][prof.zind], prof) <= upper: i += 1 uptr = i if interp.msl(prof.gSndg[i][prof.zind], prof) == upper: uptr -= 1 # Integrate from interpolated bottom level to iptr level sru1, srv1 = interp.components(plower, prof) sru1 = KTS2MS(sru1 - stu) srv1 = KTS2MS(srv1 - stv) # Loop through levels for i in range(lptr, uptr + 1): if QC(prof.gSndg[i][prof.uind]) and QC(prof.gSndg[i][prof.vind]): sru2, srv2 = interp.components(prof.gSndg[i][prof.pind], prof) sru2 = KTS2MS(sru2 - stu) srv2 = KTS2MS(srv2 - stv) lyrh = (sru2 * srv1) - (sru1 * srv2) if lyrh > 0: phel += lyrh else: nhel += lyrh sru1 = sru2 srv1 = srv2 # Integrate from tptr level to interpolated top level sru2, srv2 = interp.components(pupper, prof) sru2 = KTS2MS(sru2 - stu) srv2 = KTS2MS(srv2 - stv) lyrh = (sru2 * srv1) - (sru1 * srv2) if lyrh > 0: phel += lyrh else: nhel += lyrh return phel + nhel, phel, nhel