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 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) # 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)) if not (rsd_temp < 20 and rsd_ucmp < 20 and rsd_vcmp < 20): return [] # 定义正弦波模型函数 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