zephyr-backend/modules/balloon/extract_wave.py
2025-03-05 11:40:19 +08:00

489 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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_z0
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_h0
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