489 lines
16 KiB
Python
489 lines
16 KiB
Python
import numpy as np
|
||
import pandas as pd
|
||
from scipy.optimize import curve_fit
|
||
from scipy.signal import lombscargle, hilbert
|
||
from sklearn.decomposition import PCA
|
||
import sympy as sp
|
||
from scipy.interpolate import interp1d
|
||
from scipy.signal import lombscargle
|
||
import os
|
||
import glob
|
||
from scipy.optimize import curve_fit
|
||
import math
|
||
from sklearn.decomposition import PCA
|
||
import sympy as sp
|
||
import matplotlib.pyplot as plt
|
||
import seaborn as sns
|
||
from matplotlib.patches import Patch
|
||
from matplotlib.lines import Line2D
|
||
from windrose import WindroseAxes
|
||
from scipy.signal import lombscargle, find_peaks
|
||
from itertools import permutations
|
||
from itertools import product
|
||
|
||
|
||
def get_top_peaks(pgram, ff, num_peaks=3):
|
||
peaks, _ = find_peaks(pgram)
|
||
peak_values = pgram[peaks]
|
||
peak_frequencies = ff[peaks]
|
||
sorted_indices = np.argsort(peak_values)[-num_peaks:][::-1] # 按照峰值从大到小排序
|
||
top_peaks_frequencies = peak_frequencies[sorted_indices]
|
||
top_peaks_values = peak_values[sorted_indices]
|
||
return top_peaks_frequencies, top_peaks_values
|
||
# 定义一个函数来计算 RSD
|
||
|
||
|
||
def calculate_rsd(x, y, z):
|
||
|
||
# todo: 由波数计算波长
|
||
# lamde 垂直波长,只不过单位由10e-3 rad/m变为/km,值差不多
|
||
λ1 = round(1 / ((x / 2.5) * 0.4), 2)
|
||
λ2 = round(1 / ((y / 2.5) * 0.4), 2)
|
||
λ3 = round(1 / ((z / 2.5) * 0.4), 2)
|
||
|
||
# 计算均值
|
||
mean = round(sum([λ1, λ2, λ3]) / 3, 2)
|
||
|
||
# 计算相对标准差
|
||
rsd1 = abs(round(((λ1 - mean) / λ1) * 100, 2))
|
||
rsd2 = abs(round(((λ2 - mean) / λ2) * 100, 2))
|
||
rsd3 = abs(round(((λ3 - mean) / λ3) * 100, 2))
|
||
|
||
# 判断是否满足 RSD 小于 20%并返回结果和均值
|
||
return rsd1 < 20 and rsd2 < 20 and rsd3 < 20, mean
|
||
|
||
|
||
def calculate_n(Av, Au, Bu, Bv):
|
||
# 计算 Fuv
|
||
Fuv = Av * Au * np.cos(Bu - Bv)
|
||
# 根据条件计算 n
|
||
if Au > Av:
|
||
n = 1
|
||
elif Au < Av:
|
||
if Fuv > 0:
|
||
n = 0
|
||
else: # Fuv <= 0
|
||
n = 2
|
||
else:
|
||
raise ValueError("Au 和 Av 不应相等")
|
||
return n
|
||
|
||
|
||
def calculate_seta(Av, Au, Bu, Bv):
|
||
# 计算 n
|
||
n = calculate_n(Av, Au, Bu, Bv)
|
||
# 计算 Fuv
|
||
Fuv = Av * Au * np.cos(Bu - Bv)
|
||
# 计算 arctan 部分
|
||
arctan_value = np.atan2(2 * Fuv, Av**2 - Au**2) # 更通用,返回的角度范围是 ([-π, π])
|
||
# arctan_value = np.atan(2 * Fuv / (Av ** 2 - Au ** 2)) # 返回的角度范围是 ([-π/2, π/2])
|
||
# 计算 seta
|
||
seta = round((1 / 2) * (n * np.pi + arctan_value), 2)
|
||
return seta # 返回结果为弧度
|
||
|
||
|
||
# 判断椭圆旋转方向
|
||
def analyze_direction(df1, df2, seta_degrees):
|
||
# 初始值
|
||
# a = 0
|
||
# b = 0
|
||
|
||
# 计算元素
|
||
element1 = df1.iloc[0, 1] * df1.iloc[1, 2] - \
|
||
df1.iloc[0, 2] * df1.iloc[1, 1]
|
||
element2 = df2.iloc[0, 1] * df2.iloc[1, 2] - \
|
||
df2.iloc[0, 2] * df2.iloc[1, 1]
|
||
|
||
# 判断 element1
|
||
if element1 < 0:
|
||
a = 1 # 表示顺时针,重力波向上传播
|
||
else:
|
||
a = -1
|
||
|
||
# 判断 element2
|
||
if element2 < 0:
|
||
b = seta_degrees # 表示顺时针,水平传播方向与假设方向同,seta
|
||
else:
|
||
b = seta_degrees + 180 # 逆时针,seta+180
|
||
return a, b
|
||
|
||
|
||
# 计算u_v椭圆长短轴之比(先标准化椭圆,再计算最远距离最近距离之比)
|
||
def calculate_axial_ratio(u_fit, v_fit):
|
||
# 将 ucmp 和 vcmp 组成一个数据矩阵
|
||
data_matrix = np.vstack((u_fit, v_fit)).T
|
||
|
||
# 使用 PCA 进行主成分分析
|
||
pca = PCA(n_components=2)
|
||
transformed_data = pca.fit_transform(data_matrix)
|
||
|
||
# 提取主成分
|
||
principal_components = pca.components_
|
||
|
||
# 计算每个数据点在标准椭圆上的坐标
|
||
scaling_factors = np.linalg.norm(principal_components, axis=1)
|
||
standardized_data = transformed_data / scaling_factors
|
||
|
||
# 创建 DataFrame
|
||
standardized_df = pd.DataFrame(standardized_data, columns=["PC1", "PC2"])
|
||
# 获取第一列和第二列的最大值和最小值
|
||
col1_max = standardized_df.iloc[:, 0].max()
|
||
col1_min = standardized_df.iloc[:, 0].min()
|
||
col2_max = standardized_df.iloc[:, 1].max()
|
||
col2_min = standardized_df.iloc[:, 1].min()
|
||
|
||
# 计算长短轴比值
|
||
axial_ratio = (col1_max - col1_min) / (col2_max - col2_min)
|
||
|
||
return axial_ratio
|
||
|
||
|
||
def calculate_wave(data: pd.DataFrame, lat: float, g: float):
|
||
height = data["alt"].values
|
||
temp = data["temp"].values
|
||
ucmp = data["u"].values
|
||
vcmp = data["v"].values
|
||
wcmp = data["wspeed"].values
|
||
press = (data["press"].values) * 100 # 单位Pa
|
||
|
||
coef_temp = np.polyfit(height, temp, 2)
|
||
poly_temp = np.poly1d(coef_temp)
|
||
coef_ucmp = np.polyfit(height, ucmp, 2)
|
||
poly_ucmp = np.poly1d(coef_ucmp)
|
||
coef_vcmp = np.polyfit(height, vcmp, 2)
|
||
poly_vcmp = np.poly1d(coef_vcmp)
|
||
coef_wcmp = np.polyfit(height, wcmp, 2)
|
||
poly_wcmp = np.poly1d(coef_wcmp)
|
||
|
||
residual_temp = temp - poly_temp(height)
|
||
residual_ucmp = ucmp - poly_ucmp(height)
|
||
residual_vcmp = vcmp - poly_vcmp(height)
|
||
residual_wcmp = wcmp - poly_wcmp(height)
|
||
|
||
ff = np.linspace(0.01, 25, 1000) # 横坐标范围
|
||
pgram_temp = lombscargle(height, residual_temp, ff)
|
||
pgram_ucmp = lombscargle(height, residual_ucmp, ff)
|
||
pgram_vcmp = lombscargle(height, residual_vcmp, ff)
|
||
|
||
# BEGIN CHANGE
|
||
|
||
# BACK
|
||
# todo: 我的频率等于垂直波数(单位10-3rad/m)?
|
||
# main_frequency_temp = round(ff[np.argmax(pgram_temp)], 2)
|
||
# main_frequency_ucmp = round(ff[np.argmax(pgram_ucmp)], 2)
|
||
# main_frequency_vcmp = round(ff[np.argmax(pgram_vcmp)], 2)
|
||
|
||
# # 由波数计算波长
|
||
# λ1 = round(1 / ((main_frequency_temp / 2.5) * 0.4), 2)
|
||
# λ2 = round(1 / ((main_frequency_ucmp / 2.5) * 0.4), 2)
|
||
# λ3 = round(1 / ((main_frequency_vcmp / 2.5) * 0.4), 2)
|
||
|
||
# mean = round(sum([λ1, λ2, λ3]) / 3, 2)
|
||
# rsd_temp = abs(round(((λ1 - mean) / λ1) * 100, 2))
|
||
# rsd_ucmp = abs(round(((λ2 - mean) / λ2) * 100, 2))
|
||
# rsd_vcmp = abs(round(((λ3 - mean) / λ3) * 100, 2))
|
||
# BACK
|
||
# if not (rsd_temp < 20 and rsd_ucmp < 20 and rsd_vcmp < 20):
|
||
# return []
|
||
|
||
# begin new
|
||
|
||
peaks_ucmp_frequencies, _ = get_top_peaks(pgram_ucmp, ff)
|
||
peaks_vcmp_frequencies, _ = get_top_peaks(pgram_vcmp, ff)
|
||
peaks_temp_frequencies, _ = get_top_peaks(pgram_temp, ff)
|
||
|
||
# 获取所有可能的频率组合(9 种组合)
|
||
combinations = list(product(peaks_ucmp_frequencies,
|
||
peaks_vcmp_frequencies,
|
||
peaks_temp_frequencies))
|
||
result = "0" # 初始结果为 "0"
|
||
for combo in combinations:
|
||
x, y, z = combo # 获取每个组合的三个频率
|
||
valid_rsd, mean = calculate_rsd(x, y, z) # 计算 RSD,并获取均值
|
||
if valid_rsd: # 如果有任何组合满足 RSD 小于 20%,设置 result 为 "1"
|
||
result = "1"
|
||
break # 找到一个符合条件的组合后,退出循环,避免继续检查其他组合
|
||
if result != "1":
|
||
return []
|
||
|
||
# end new
|
||
|
||
# END CHANGE
|
||
|
||
# 定义正弦波模型函数
|
||
|
||
def sine_wave(height, A, B, omega):
|
||
omega = 2 * np.pi / mean
|
||
return A * np.sin(omega * height + B)
|
||
|
||
def fit_sine_wave(height, data, mean):
|
||
# 提供初始猜测值,例如 A=1, B=0, omega=2 * np.pi / mean
|
||
initial_guess = [1, 0, 2 * np.pi / mean]
|
||
# 设置参数的边界:A的下限为0(确保A为正值),B和omega没有边界限制
|
||
bounds = ([0, -np.inf, -np.inf], [np.inf, np.inf, np.inf])
|
||
# curve_fit 函数通过调整 sine_wave 函数的参数(A、B 和 omega)来最小化模型预测值与实际数据 data 之间的残差平方和。
|
||
params, covariance = curve_fit(
|
||
sine_wave, height, data, p0=initial_guess, bounds=bounds)
|
||
return params
|
||
|
||
# 对 u 风、v 风、温度扰动量进行拟合
|
||
params_u = fit_sine_wave(height, residual_ucmp, mean) # 输出3个参数分别是振幅,相位,角频率
|
||
u_fit = sine_wave(height, *params_u)
|
||
|
||
params_v = fit_sine_wave(height, residual_vcmp, mean)
|
||
v_fit = sine_wave(height, *params_v)
|
||
|
||
params_T = fit_sine_wave(height, residual_temp, mean)
|
||
T_fit = sine_wave(height, *params_T)
|
||
|
||
params_w = fit_sine_wave(height, residual_wcmp, mean)
|
||
w_fit = sine_wave(height, *params_w)
|
||
|
||
# 将高度、u_fit、v_fit创建一个数据框df1
|
||
df1 = pd.DataFrame({"Height": height, "u_fit": u_fit, "v_fit": v_fit})
|
||
|
||
# 计算假设的水平传播方向
|
||
rad_seta = calculate_seta(
|
||
params_v[0], params_u[0], params_u[1], params_v[1]) # 输入、输出都为弧度
|
||
# 转换为角度并保留2位小数
|
||
seta_degrees = round(np.degrees(rad_seta), 2) # 38.41°
|
||
|
||
# 计算水平风
|
||
uh = u_fit * np.sin(seta_degrees * np.pi / 180) + \
|
||
v_fit * np.cos(seta_degrees * np.pi / 180) # 水平风
|
||
# temp = T_fit
|
||
# 将高度、水平风、温度创建一个数据框df2
|
||
df2 = pd.DataFrame({"Height": height, "uh": uh, "temp": T_fit})
|
||
a, b = analyze_direction(df1, df2, seta_degrees) # 计算垂直、水平传播方向
|
||
|
||
Axial_ratio = round(calculate_axial_ratio(u_fit, v_fit),
|
||
2) # 计算长短轴之比并保留2位小数,后续计算本征频率会用到
|
||
|
||
# 计算剩余几个参数
|
||
# 常量
|
||
# f = 1.32e-4 # rad/s
|
||
# N = 2.13e-2 # 浮力频数,单位 rad/s
|
||
|
||
# 根据站点纬度,计算惯性频率f
|
||
ou = 7.27e-5 # 单位rad/s,地球自转频率
|
||
# lat = 64.5
|
||
f = 2 * ou * np.sin(lat * np.pi / 180)
|
||
# print(f"f = {f:.3e} rad/s")
|
||
|
||
# 求浮力频率N
|
||
df3 = pd.DataFrame({"height": height, "temp": temp,
|
||
"temp_bar": poly_temp(height)})
|
||
|
||
Cp = 1005 # 单位:J/kgK
|
||
df3["temp_bar_derivative"] = np.gradient(
|
||
df3["temp_bar"], (df3["height"]) * 1000) # 求背景温度对高度的偏导
|
||
gen = (g / temp) * (df3["temp_bar_derivative"] + (g / Cp))
|
||
sqrt_gen = np.sqrt(np.abs(gen)) # np.sqrt()对列表求根号,np.sqrt()对数字求根号
|
||
N = sqrt_gen.mean() # 计算平均值,得到浮力频率的均值
|
||
|
||
# 1. 本征频率omega_upper
|
||
omega_upper = sp.symbols("omega_upper")
|
||
eq1 = sp.Eq(Axial_ratio, omega_upper / f)
|
||
omega_upper = (sp.solve(eq1, omega_upper))[0]
|
||
omega_upper = sp.N(omega_upper)
|
||
|
||
# 求本征频率与惯性频率的比值
|
||
w_f = omega_upper / f # todo:总是大于1
|
||
|
||
# 本征周期
|
||
zhou_qi = 2 * np.pi / omega_upper / 3600
|
||
|
||
# 2. 垂直波长λ_z、水平波长λ_h
|
||
|
||
# 垂直波数
|
||
λ_z = mean # 保留两位小数
|
||
# 在北半球,重力波向上传播,垂直波数k_z<0
|
||
if a == 1:
|
||
k_z = -(2 * np.pi / λ_z / 1000)
|
||
else:
|
||
k_z = 2 * np.pi / λ_z / 1000
|
||
|
||
# 水平波数
|
||
k_h = sp.symbols("k_h")
|
||
eq2 = sp.Eq(k_h**2, (omega_upper**2 - f**2) * (k_z**2) / N**2)
|
||
k_h = sp.solve(eq2, k_h)
|
||
k_h = [sol.evalf() for sol in k_h if sol.evalf() > 0][0]
|
||
k_h = sp.N(k_h) # 正值
|
||
|
||
# 在北半球,重力波向上传播,垂直波数k_h<0
|
||
if b > 180: # 说明水平传播方向与假定方向相反
|
||
k_h = -k_h
|
||
else:
|
||
k_h = k_h
|
||
|
||
λ_h = abs(round((2 * np.pi / k_h / 1000), 2)) # 水平波长,取绝对值,因为波数可能为负数
|
||
|
||
# 纬向、经向水平波长λ_x, λ_y
|
||
# λ_x = abs(λ_h * np.sin(b * np.pi / 180))
|
||
# λ_y = abs(λ_h * np.cos(b * np.pi / 180))
|
||
|
||
# 本征相速度
|
||
c_x = omega_upper / (abs(k_h) * np.sin(b * np.pi / 180))
|
||
c_y = omega_upper / (abs(k_h) * np.cos(b * np.pi / 180))
|
||
c_z = omega_upper / k_z
|
||
|
||
"""
|
||
# 群速度
|
||
C_gz = -((omega_upper ** 2 - f ** 2)) / (omega_upper * k_z)
|
||
C_gh = sp.symbols('C_gh')
|
||
eq3 = sp.Eq(C_gz / C_gh, sp.sqrt(omega_upper ** 2 - f ** 2) / N)
|
||
C_gh = (sp.solve(eq3, C_gh))[0]
|
||
C_gh = sp.N(C_gh) # 转成数值
|
||
"""
|
||
|
||
# 波动能E_k
|
||
u2_mean = np.mean(np.square(u_fit))
|
||
v2_mean = np.mean(np.square(v_fit))
|
||
Ek = (1 / 2) * (u2_mean + v2_mean) # 3.11 J/kg
|
||
|
||
# 势能E_p
|
||
T_normalized = T_fit / poly_temp(height) # 归一化处理
|
||
T2_mean = np.mean(np.square(T_normalized))
|
||
E_p = ((g**2) * T2_mean) / (2 * (N**2)) # 1.02J/kg
|
||
|
||
# 动量通量
|
||
# 经、纬向风动量通量
|
||
P = press # 单位Pa
|
||
R = 287 # 单位:J/(kgK),气体常量
|
||
T = temp # 温度已经是开尔文
|
||
densi = P / (R * T) # 向量
|
||
c = (omega_upper * g) / (N**2)
|
||
S = 1 - (f**2) / (omega_upper**2) # 常数
|
||
|
||
# 归一化温度90°相位变换
|
||
|
||
hilb = hilbert(T_normalized)
|
||
hilb1 = np.imag(hilb) # 获取虚部作为90°相位变换的结果
|
||
|
||
uT = u_fit * hilb1 # 向量
|
||
uT1 = np.mean(uT) # 均值
|
||
MFu = (np.mean(-densi * c * uT1 * S)) * 1000 # 单位mPa
|
||
|
||
vT = v_fit * hilb1
|
||
vT1 = np.mean(vT)
|
||
MFv = (np.mean(-densi * c * vT1 * S)) * 1000
|
||
|
||
# 调整动量通量符号与本征相速度符号一致
|
||
# 调整b的符号与a一致
|
||
MFu1 = abs(MFu) if c_x >= 0 else -abs(MFu)
|
||
MFv1 = abs(MFv) if c_y >= 0 else -abs(MFv)
|
||
|
||
omega_upper = float(omega_upper)
|
||
# make wf, c_x, c_y, c_z, MFu, MFv, zhouqi float
|
||
w_f = float(w_f)
|
||
c_x = float(c_x)
|
||
c_y = float(c_y)
|
||
c_z = float(c_z)
|
||
MFu1 = float(MFu1)
|
||
MFv1 = float(MFv1)
|
||
zhou_qi = float(zhou_qi)
|
||
|
||
return [
|
||
a,
|
||
b,
|
||
omega_upper,
|
||
w_f,
|
||
λ_z,
|
||
λ_h,
|
||
c_x,
|
||
c_y,
|
||
c_z,
|
||
Ek,
|
||
E_p,
|
||
MFu1,
|
||
MFv1,
|
||
params_u[0],
|
||
params_v[0],
|
||
params_T[0],
|
||
ucmp,
|
||
vcmp,
|
||
temp,
|
||
height,
|
||
poly_ucmp,
|
||
poly_vcmp,
|
||
poly_wcmp,
|
||
poly_temp,
|
||
residual_ucmp,
|
||
residual_vcmp,
|
||
residual_temp,
|
||
u_fit,
|
||
v_fit,
|
||
T_fit,
|
||
uh,
|
||
zhou_qi,
|
||
]
|
||
|
||
|
||
def extract_wave(data: pd.DataFrame, lat: float, g: float):
|
||
wave = calculate_wave(
|
||
data[(data["alt"] >= 15) & (data["alt"] <= 25)], lat, g)
|
||
if len(wave) == 0:
|
||
return []
|
||
return [
|
||
wave[0],
|
||
wave[1],
|
||
round(wave[2], 6),
|
||
round(wave[3], 2),
|
||
round(wave[4], 2),
|
||
round(wave[5], 2),
|
||
round(wave[6], 2),
|
||
round(wave[7], 2),
|
||
round(wave[8], 3),
|
||
round(wave[9], 4),
|
||
round(wave[10], 4),
|
||
round(wave[11], 4),
|
||
round(wave[12], 4),
|
||
round(wave[13], 2),
|
||
round(wave[14], 2),
|
||
round(wave[15], 2),
|
||
round(wave[31], 2),
|
||
]
|
||
|
||
|
||
def is_terrain_wave(data: pd.DataFrame, lat: float, g: float):
|
||
wave = calculate_wave(
|
||
data[(data["alt"] >= 2) & (data["alt"] <= 10)], lat, g)
|
||
if len(wave) == 0:
|
||
return False
|
||
|
||
# 计算观测u风v风的均值,为后边合成背景速度
|
||
mean_ucmp = np.mean(wave[16]) # 11.34
|
||
mean_vcmp = np.mean(wave[17]) # 0.38
|
||
|
||
x1, y1 = mean_ucmp, mean_vcmp # 背景风的分量
|
||
x2, y2 = float(wave[6]), float(wave[7]) # 相速度的分量
|
||
# wave[18](wave[19])
|
||
|
||
"""
|
||
# 二阶拟合的u风v风
|
||
mean_ucmp = np.mean(poly_ucmp(height))
|
||
mean_vcmp = np.mean(poly_vcmp(height))
|
||
|
||
x1, y1 = mean_ucmp, mean_vcmp # 背景风的分量
|
||
x2, y2 = float(c_x), float(c_y) # 相速度的分量
|
||
"""
|
||
# 计算两个风的方向
|
||
direction1 = np.arctan2(y1, x1)
|
||
direction2 = np.arctan2(y2, x2)
|
||
# 转化为角度(该角度是以x轴逆时针旋转),取特殊值用于验证。
|
||
# direction1_degrees = np.degrees(direction1)
|
||
# direction2_degrees = np.degrees(direction2)
|
||
|
||
# 计算夹角(弧度)
|
||
angle_radians = np.abs(direction1 - direction2)
|
||
# 确保夹角在0到π之间
|
||
if angle_radians > np.pi:
|
||
angle_radians = 2 * np.pi - angle_radians
|
||
# 将夹角转换为度
|
||
angle_degrees = np.degrees(angle_radians)
|
||
|
||
# 判断是否超过150,或者165
|
||
if round(angle_degrees, 2) > 150:
|
||
return True
|
||
return False
|