{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Tuple, Union\n",
    "from collections import deque\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "from stable_baselines3 import PPO\n",
    "import pandas as pd\n",
    "import pickle\n",
    "import sys\n",
    "import os\n",
    "\n",
    "current_dir = os.path.dirname(os.path.abspath(\"__file__\"))\n",
    "parent_dir = os.path.abspath(os.path.join(current_dir, os.pardir))\n",
    "sys.path.append(parent_dir)\n",
    "\n",
    "from gym_env_250326_erase import CustomEnv\n",
    "# from gym_env_240904 import CustomEnv\n",
    "\n",
    "vec_env = CustomEnv(device_id=2)\n",
    "# model = PPO.load(os.path.join(parent_dir, \"models\", \"250329\", \"d1a3o12_250329_100.zip\"), env=vec_env, device=\"cuda:1\")\n",
    "model = PPO.load(os.path.join(parent_dir, \"models\", \"250729\", \"d1a3o14_erase_250830_20D_05D_3_63delay.zip\"), env=vec_env, device=\"cuda:2\")\n",
    "# model = PPO.load(os.path.join(parent_dir, \"models\", \"d1a3o12_re50.zip\"), env=vec_env, device=\"cuda:3\")\n",
    "model_env = model.get_env()\n",
    "\n",
    "L0 = 20\n",
    "U0 = 0.01\n",
    "NY = 512"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Best seed:  19\n"
     ]
    }
   ],
   "source": [
    "best_seed = 0\n",
    "max_reward = 0\n",
    "for seed in range(20):\n",
    "    model.set_random_seed(seed=seed)\n",
    "    model_env = model.get_env()\n",
    "    obs = model_env.reset()\n",
    "    reward_list = []\n",
    "    for _ in range(500):\n",
    "        action, _states = model.predict(observation=obs, deterministic=True)\n",
    "        obs, rewards, dones, info = model_env.step(action)\n",
    "        reward_list.append(rewards)\n",
    "    if np.mean(reward_list[-100:]) > max_reward:\n",
    "        max_reward = np.mean(np.array(reward_list))\n",
    "        best_seed = seed\n",
    "print(\"Best seed: \", best_seed)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.set_random_seed(seed=19)\n",
    "states_list = []\n",
    "reward_list = []\n",
    "action_list = []\n",
    "r_u_list, r_v_list, r_sim_list = [], [], []\n",
    "obs = model_env.reset()\n",
    "force_norm_fact = model_env.envs[0].force_norm_fact.copy()\n",
    "for i in range(500):\n",
    "    action, _states = model.predict(observation=obs, deterministic=True)\n",
    "    obs, rewards, dones, info = model_env.step(action)\n",
    "    if i > 400:\n",
    "        states_list.append(model_env.envs[0].flow_field.obs.copy())\n",
    "        reward_list.append(rewards)\n",
    "        action_list.append(action)\n",
    "        r_u_list.append(model_env.envs[0].reward_u.copy())\n",
    "        r_v_list.append(model_env.envs[0].reward_v.copy())\n",
    "        r_sim_list.append(model_env.envs[0].reward_sim.copy())\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"target u&v:\", model_env.envs[0].target_sensors)\n",
    "print(\"force normalization factor:\", force_norm_fact)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 376168 characters]",
      "text/plain": [
       "<Figure size 1000x1000 with 8 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "sens_norm_fact = model_env.envs[0].sens_norm_fact.copy()\n",
    "sens_deviation = model_env.envs[0].sens_deviation.copy()\n",
    "# sens = (np.array(states_list)[:, 0:6] - sens_deviation) / sens_norm_fact\n",
    "sens = np.array(states_list)[:, 0:6]\n",
    "# sen_u = (np.array(states_list)[:, [0, 2, 4]] - sens_deviation) / sens_norm_fact\n",
    "# sen_v = (np.array(states_list)[:, [1, 3, 5]] - sens_deviation) / sens_norm_fact\n",
    "sen_u = sens[:, [0, 2, 4]]\n",
    "sen_v = sens[:, [1, 3, 5]]\n",
    "force_cyl = np.array(states_list)[:, 6:8]\n",
    "force_pin = np.array(states_list)[:, 8:14] / force_norm_fact\n",
    "force_x = np.mean(force_pin[:, [0, 2, 4]], axis=1)\n",
    "force_y = np.mean(force_pin[:, [1, 3, 5]], axis=1)\n",
    "plt.figure(figsize=(10, 10))\n",
    "plt.subplot(4, 2, 1)\n",
    "plt.plot(sen_u, label=\"sensor_u\")\n",
    "plt.subplot(4, 2, 2)\n",
    "plt.plot(sen_v, label=\"sensor_v\")\n",
    "plt.subplot(4, 2, 3)\n",
    "plt.plot(np.array(r_u_list), label=\"reward_u\")\n",
    "plt.subplot(4, 2, 4)\n",
    "plt.plot(np.array(r_v_list), label=\"reward_v\")\n",
    "plt.subplot(4, 2, 5)\n",
    "plt.plot(np.array(force_cyl), label=\"force_cyl\")\n",
    "plt.plot(np.array(force_x), label=\"force_x\")\n",
    "plt.plot(np.array(force_y), label=\"force_y\")\n",
    "plt.legend()\n",
    "plt.subplot(4, 2, 6)\n",
    "plt.plot(np.array(force_pin), label=\"force_pin\")\n",
    "plt.subplot(4, 2, 7)\n",
    "plt.plot(np.array(r_sim_list), label=\"reward_sim\")\n",
    "plt.legend()\n",
    "plt.subplot(4, 2, 8)\n",
    "plt.plot(np.array(action_list)[:,0,:], label=\"action\")\n",
    "plt.legend()\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# model.set_random_seed(seed=best_seed)\n",
    "model.set_random_seed(seed=19)\n",
    "states_list = []\n",
    "reward_list = []\n",
    "action_list = []\n",
    "obs_list = []\n",
    "r_cl_list, r_cd_list, r_sim_list = [], [], []\n",
    "obs = model_env.reset()\n",
    "\n",
    "# sens_norm_fact = model_env.envs[0].sens_norm_fact.copy()[3]\n",
    "# sens_deviation = model_env.envs[0].sens_deviation.copy()[3]\n",
    "sens_last = 0\n",
    "flag_max_arm = False\n",
    "T_count = 0\n",
    "flag_ave_start = False\n",
    "reward_ave = 0\n",
    "sim_ave = 0\n",
    "for i in range(500):\n",
    "    if i < 200:\n",
    "        action = np.array([0.0, 1.0, -1.0], dtype=np.float32)[np.newaxis, :]\n",
    "    else:\n",
    "        action, _states = model.predict(observation=obs, deterministic=True)\n",
    "    obs, rewards, dones, info = model_env.step(action)\n",
    "    states = model_env.envs[0].flow_field.obs.copy()\n",
    "\n",
    "    # if i == 100:\n",
    "    #     center: Tuple[float, float, float] = (15 * L0, (NY - 1) / 2, 0)\n",
    "    #     # model_env.envs[0].flow_field.add_vortex(center, L0 * 2, 0.5*U0, 0, \"lamb\")\n",
    "    #     model_env.envs[0].flow_field.add_vortex(center, L0 * 2, 0.03*U0, 0, \"taylor\")\n",
    "\n",
    "    if i % 2 == 0 and i >= 100 and i < 400:\n",
    "    # if i == 599:\n",
    "    # if i % 100 == 0:\n",
    "        file_index = (i-100) // 2 + 1\n",
    "        file_name = f\"field.dat.{file_index:03d}\"\n",
    "        model_env.envs[0].save_field(os.path.join(parent_dir, \"output\", \"250729\", \"anim\", file_name))\n",
    "\n",
    "\n",
    "    # if i > 500:\n",
    "    #     reward = model_env.envs[0].reward_sim.copy()\n",
    "    #     sim_ave = sim_ave + np.log(reward)/10+1\n",
    "    #     reward_ave = reward_ave + reward\n",
    "\n",
    "    # sens_now = (obs[0, 9] * sens_norm_fact + sens_deviation) / 78 / U0\n",
    "    # sens_now = states[1]\n",
    "\n",
    "    # if i >= 50:\n",
    "    #     reward_list.append(rewards)\n",
    "    #     action_list.append(action[0,:])\n",
    "    #     states_list.append(np.array(states))\n",
    "    #     obs_list.append(obs[0,:])\n",
    "        # r_cl_list.append(model_env.envs[0].reward_cl.copy())\n",
    "        # r_cd_list.append(model_env.envs[0].reward_cd.copy())\n",
    "        # r_sim_list.append(model_env.envs[0].reward_sim.copy())\n",
    "\n",
    "#         # if sens_last > 0 and sens_now <= 0:\n",
    "#             # T_count += 1\n",
    "#             # flag_ave_start = True\n",
    "#         if sens_now > sens_last and not flag_max_arm:\n",
    "#             flag_max_arm = True\n",
    "#         if sens_now < sens_last and flag_max_arm:\n",
    "#             flag_max_arm = False\n",
    "#         # if T_count <= 20 and flag_ave_start:\n",
    "        # if i % 2 == 0:\n",
    "        #     model_env.envs[0].average_field(mode = \"add\")\n",
    "#         sens_last = sens_now\n",
    "\n",
    "# model_env.envs[0].average_field(mode=\"save\", filename = os.path.join(parent_dir, \"output\", \"250729\", \"plot\", \"target.dat\"))\n",
    "# model_env.envs[0].average_field(mode=\"clear\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = {}\n",
    "# for i in range(3):\n",
    "#     data[\"action\" + str(i+1)] = np.array(action_list)[:, i]\n",
    "data[\"reward\"] = np.array(reward_list)[:, 0]\n",
    "for i in range(14):\n",
    "    data[\"states\" + str(i+1)] = np.array(states_list)[:, i]\n",
    "\n",
    "df = pd.DataFrame(data)\n",
    "df.to_csv(os.path.join(parent_dir, \"output\", \"250729\", \"data\", \"time_ref.csv\"), index=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# print(\"Average sim: \", np.mean(sim_ave/500))\n",
    "# print(\"Average reward: \", np.mean(reward_ave/500))\n",
    "print(\"Mod_sim:\", np.exp(-50*(0.646-1)**2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 77288 characters]",
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# obs = model_env.reset()\n",
    "# model_env = model.get_env()\n",
    "model_env.envs[0].render()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.plot(np.array(model_env.envs[0].fifo_states))\n",
    "plt.plot(np.array(model_env.envs[0].target_states))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "offset = np.array([0, -8, +8], dtype=np.float32)\n",
    "action_offset = [action*8+offset for action in action_list]\n",
    "# target_states = model_env.envs[0].target_states.copy()\n",
    "sense_list = np.array(obs_list)[:, 6:12]\n",
    "for i in range(6):\n",
    "    norm_factor = model_env.envs[0].sens_norm_fact[i]\n",
    "    diviation = model_env.envs[0].sens_deviation[i]\n",
    "    # target_states[:, i] = (target_states[:, i] * norm_factor + diviation) / 78 / U0\n",
    "    sense_list[:, i] = (sense_list[:, i] * norm_factor + diviation) / 78 / U0\n",
    "force_list = np.array(obs_list)[:, 0:6] * model_env.envs[0].force_norm_fact / (L0*U0*U0)\n",
    "# force_list = np.array(obs_list)[:, 0:6]\n",
    "force_list = np.array(force_list)\n",
    "drag = force_list[:, 0] + force_list[:, 2] + force_list[:, 4]\n",
    "lift = force_list[:, 1] + force_list[:, 3] + force_list[:, 5]\n",
    "plt.figure(figsize=(15, 10))\n",
    "plt.subplot(2, 3, 1)\n",
    "plt.plot(np.array(sense_list))\n",
    "# plt.plot(np.array(model_env.envs[0].target_states))\n",
    "# plt.plot(np.array(obs_list)[:, 6:12])\n",
    "plt.title(\"Sensors\")\n",
    "plt.subplot(2, 3, 2)\n",
    "plt.plot(drag, label=\"Drag\")\n",
    "plt.plot(lift, label=\"Lift\")\n",
    "plt.title(\"Forces\")\n",
    "plt.subplot(2, 3, 3)\n",
    "plt.plot(np.array(action_offset))\n",
    "plt.title(\"Actions\")\n",
    "plt.subplot(2, 3, 4)\n",
    "# plt.plot(reward_list)\n",
    "plt.plot(np.array(model_env.envs[0].target_states)[:, 0:6] / 78 / U0)\n",
    "plt.title(\"Target Sensors\")\n",
    "plt.subplot(2, 3, 5)\n",
    "# plt.plot(np.array(r_sim_list))\n",
    "plt.plot(np.array(model_env.envs[0].target_states)[:, 0:2] / model_env.envs[0].force_norm_fact)\n",
    "plt.title(\"Target Forces\")\n",
    "plt.subplot(2, 3, 6)\n",
    "# plt.plot(np.array(model_env.envs[0].target_states)[:, 2:14] / 78 / U0)\n",
    "# plt.plot(reward_list)\n",
    "# plt.title(\"Rewards\")\n",
    "# plt.subplot(3, 3, 7)\n",
    "plt.plot(np.array(r_cl_list), label=\"r_cl\")\n",
    "plt.plot(np.array(r_cd_list), label=\"r_cd\")\n",
    "plt.plot(np.array(r_sim_list), label=\"r_sim\")\n",
    "plt.title(\"Rewards Breakdown\")\n",
    "plt.tight_layout()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy.interpolate import interp1d\n",
    "from scipy.fft import rfft, rfftfreq\n",
    "\n",
    "def interpolate_highres(data, upsample=10, kind='cubic'):\n",
    "\n",
    "    N = data.shape[0]\n",
    "    t = np.arange(N)\n",
    "    t_high = np.arange(0, N-1+1e-8, 1/upsample)\n",
    "    f_interp = interp1d(t, data, axis=0, kind=kind)\n",
    "    data_high = f_interp(t_high)\n",
    "    return t_high, data_high\n",
    "\n",
    "def detect_period(data_high, dt_high):\n",
    "\n",
    "    N = data_high.shape[0]\n",
    "    xf = rfftfreq(N, dt_high)\n",
    "    yf = np.abs(rfft(data_high - np.mean(data_high)))\n",
    "    main_freq = xf[1:][np.argmax(yf[1:])]\n",
    "    period = 1 / main_freq\n",
    "    return period\n",
    "\n",
    "def phase_average(data_high, t_high, period, n_phase=100):\n",
    "\n",
    "    period_starts = np.arange(t_high[0], t_high[-1] - period, period)\n",
    "    all_periods = []\n",
    "    for ps in period_starts:\n",
    "        mask = (t_high >= ps) & (t_high < ps + period)\n",
    "        t_seg = t_high[mask]\n",
    "        if len(t_seg) < 2:\n",
    "            continue\n",
    "        phase = (t_seg - ps) / period\n",
    "        phase_new = np.linspace(0, 1, n_phase)\n",
    "        segs = []\n",
    "        for i in range(data_high.shape[1]):\n",
    "            data_seg = data_high[mask, i]\n",
    "            f = interp1d(phase, data_seg, kind='linear', fill_value='extrapolate')\n",
    "            data_interp = f(phase_new)\n",
    "            segs.append(data_interp)\n",
    "        segs = np.stack(segs, axis=1)  # (n_phase, M)\n",
    "        all_periods.append(segs)\n",
    "    all_periods = np.array(all_periods)\n",
    "    smooth_cycle = np.mean(all_periods, axis=0)\n",
    "    return smooth_cycle\n",
    "\n",
    "def simple_fft(data, dt=1.0):\n",
    "\n",
    "    data = np.array(data)\n",
    "    if data.ndim == 1:\n",
    "        data = data.reshape(-1, 1)\n",
    "    \n",
    "    N = data.shape[0]\n",
    "    freqs = rfftfreq(N, dt)\n",
    "    amplitudes = np.abs(rfft(data - np.mean(data, axis=0), axis=0))\n",
    "    \n",
    "    amplitudes = amplitudes / N * 2\n",
    "    amplitudes[0] = amplitudes[0] / 2\n",
    "    \n",
    "    if data.shape[1] == 1:\n",
    "        amplitudes = amplitudes.flatten()\n",
    "    \n",
    "    return freqs, amplitudes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "t_high, action_offset_high = interpolate_highres(np.array(action_offset))\n",
    "_, sense_list_high = interpolate_highres(np.array(sense_list))\n",
    "# _, sense_list_high = interpolate_highres(np.array(states_list)[:, 2:8])\n",
    "_, force_list_high = interpolate_highres(np.array(force_list))\n",
    "\n",
    "period = detect_period(sense_list_high[:, 1], dt_high = t_high[1] - t_high[0])\n",
    "print(f\"检测到主周期约为 {period:.3f}\")\n",
    "\n",
    "action_cycle = phase_average(action_offset_high, t_high, 3*period)\n",
    "sense_cycle = phase_average(sense_list_high, t_high, 3*period)\n",
    "force_cycle = phase_average(force_list_high, t_high, 3*period)\n",
    "\n",
    "plt.figure(figsize=(15, 10))\n",
    "plt.subplot(3, 3, 1)\n",
    "plt.plot(action_cycle)\n",
    "plt.title(\"Action Cycle\")\n",
    "plt.subplot(3, 3, 2)\n",
    "plt.plot(sense_cycle)\n",
    "plt.title(\"Sensor Cycle\")\n",
    "plt.subplot(3, 3, 3)\n",
    "plt.plot(force_cycle)\n",
    "plt.title(\"Force Cycle\")\n",
    "data = {}\n",
    "for i in range(3):\n",
    "    data[\"action\" + str(i+1)] = np.array(action_cycle)[:, i]\n",
    "for i in range(3):\n",
    "    data[\"cd\" + str(i+1)] = np.array(force_cycle)[:, 2*i]\n",
    "    data[\"cl\" + str(i+1)] = np.array(force_cycle)[:, 2*i+1]\n",
    "for i in range(6):\n",
    "    data[\"sensor\" + str(i+1)] = np.array(sense_cycle)[:, i]\n",
    "    # data[\"target\" + str(i+1)] = np.array(model_env.envs[0].target_states)[:, i] / 78 / U0\n",
    "# data[\"reward\"] = np.array(reward_list)[:, 0]\n",
    "# data[\"sim\"] = np.array(r_sim_list)\n",
    "\n",
    "df = pd.DataFrame(data)\n",
    "df.to_csv(os.path.join(parent_dir, \"output\", \"250729\", \"data\", \"period_illusion.csv\"), index=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_nondim_freq(lift, dt, L0, U0):\n",
    "    lift = np.array(lift)\n",
    "    lift = lift - np.mean(lift)  # 去均值\n",
    "    N = len(lift)\n",
    "    fft = np.fft.fft(lift)\n",
    "    freqs = np.fft.fftfreq(N, d=dt)\n",
    "    idx = np.argmax(np.abs(fft[1:N//2])) + 1  # 跳过直流分量\n",
    "    f = np.abs(freqs[idx])\n",
    "    St = f * L0 / U0  # Strouhal数\n",
    "    return St, f\n",
    "\n",
    "dt = 400/1000\n",
    "target_lift = np.array(model_env.envs[0].target_states)[:, 1] / model_env.envs[0].force_norm_fact\n",
    "St1, f1 = get_nondim_freq(lift, dt, L0, U0)\n",
    "St2, f2 = get_nondim_freq(target_lift, dt, L0, U0)  # lift2为另一个序列\n",
    "\n",
    "rel_err = np.abs(St1 - St2) / St2\n",
    "print(f\"St1={St1:.4f}, St2={St2:.4f}, 相对误差={rel_err:.2%}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.linear_model import LinearRegression\n",
    "import pickle\n",
    "\n",
    "X = np.array(obs_list)  # (N, 12)\n",
    "Y = np.array(action_list)  # (N, 3)\n",
    "N = X.shape[0]\n",
    "\n",
    "# 设定目标值（setpoint），可以用目标状态的均值或某个常量\n",
    "# 这里假设目标为0向量（如需自定义请替换）\n",
    "setpoint = np.zeros(12)\n",
    "\n",
    "# 误差\n",
    "errors = setpoint - X           # (N, 12)\n",
    "# 积分项\n",
    "i_errors = np.cumsum(errors, axis=0)  # (N, 12)\n",
    "# 微分项\n",
    "d_errors = np.diff(errors, axis=0, prepend=errors[0:1, :])  # (N, 12)\n",
    "\n",
    "# 拼接特征 [P, I, D]\n",
    "features = np.concatenate([errors, i_errors, d_errors], axis=1)  # (N, 36)\n",
    "\n",
    "pid_regs = []\n",
    "for i in range(3):\n",
    "    reg = LinearRegression()\n",
    "    reg.fit(features, Y[:, i])\n",
    "    pid_regs.append(reg)\n",
    "    print(f\"第{i+1}个输出的PID系数 shape:\", reg.coef_.shape)  # (36,)\n",
    "    print(f\"第{i+1}个输出的PID系数:\", reg.coef_)\n",
    "    print(f\"第{i+1}个输出的偏置:\", reg.intercept_)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.linear_model import SGDRegressor\n",
    "import numpy as np\n",
    "\n",
    "# 1. 先用历史数据全量拟合\n",
    "X = np.array(obs_list)  # (N, 12)\n",
    "Y = np.array(action_list)  # (N, 3)\n",
    "setpoint = np.zeros(12)\n",
    "errors = setpoint - X\n",
    "i_errors = np.cumsum(errors, axis=0)\n",
    "d_errors = np.diff(errors, axis=0, prepend=errors[0:1, :])\n",
    "features = np.concatenate([errors, i_errors, d_errors], axis=1)  # (N, 36)\n",
    "# 先用线性回归全量拟合\n",
    "pid_regs = []\n",
    "for i in range(3):\n",
    "    reg = LinearRegression()\n",
    "    reg.fit(features, Y[:, i])\n",
    "    pid_regs.append(reg)\n",
    "# 用线性回归参数初始化SGDRegressor\n",
    "sgd_regs = []\n",
    "for i in range(3):\n",
    "    sgd = SGDRegressor(max_iter=1, learning_rate='invscaling', eta0=0.001, tol=1e-3)\n",
    "    # 先用一小批数据fit一次，建立coef_和intercept_\n",
    "    sgd.partial_fit(features[:10], Y[:10, i])\n",
    "    # 再赋值\n",
    "    sgd.coef_ = pid_regs[i].coef_.copy()\n",
    "    sgd.intercept_ = np.array([pid_regs[i].intercept_])\n",
    "    sgd_regs.append(sgd)\n",
    "\n",
    "# 2. 在线微调（交替控制）\n",
    "errors_hist = []\n",
    "obs = model_env.reset()\n",
    "setpoint = obs[0, :]\n",
    "\n",
    "for epoch in range(200):\n",
    "    # 1. DRL控制\n",
    "    drl_obs, drl_act = [], []\n",
    "    for i in range(100):\n",
    "        action, _ = model.predict(observation=obs, deterministic=True)\n",
    "        drl_obs.append(obs[0, :])\n",
    "        drl_act.append(action[0, :])\n",
    "        obs, _, _, _ = model_env.step(action)\n",
    "    drl_obs = np.array(drl_obs)\n",
    "    drl_act = np.array(drl_act)\n",
    "    \n",
    "    # 2. 用DRL数据微调PID\n",
    "    errors = setpoint - drl_obs\n",
    "    i_errors = np.cumsum(errors, axis=0)\n",
    "    d_errors = np.diff(errors, axis=0, prepend=errors[0:1, :])\n",
    "    features = np.concatenate([errors, i_errors, d_errors], axis=1).astype(np.float64)\n",
    "    for j in range(3):\n",
    "        sgd_regs[j].partial_fit(features, drl_act[:, j].astype(np.float64))\n",
    "    \n",
    "    # 3. PID控制\n",
    "    for i in range(epoch):\n",
    "        error = setpoint - obs[0, :]\n",
    "        errors_hist.append(error)\n",
    "        errors_hist_arr = np.array(errors_hist[-100:])  # 只保留最近100步\n",
    "        i_error = np.sum(errors_hist_arr, axis=0)\n",
    "        d_error = error - errors_hist_arr[-2] if len(errors_hist_arr) > 1 else error\n",
    "        feature = np.concatenate([error, i_error, d_error]).reshape(1, -1)\n",
    "        action = np.clip(np.array([sgd_regs[j].predict(feature)[0] for j in range(3)], dtype=np.float32), -0.6, 0.6)[np.newaxis, :]\n",
    "        obs, _, _, _ = model_env.step(action)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def pid_predict(obs_new, errors_hist, setpoint, pid_regs):\n",
    "    # obs_new: (12,)\n",
    "    # errors_hist: (M, 12)，历史误差序列，M为历史步数\n",
    "    error = setpoint - obs_new\n",
    "    i_error = np.sum(errors_hist, axis=0)\n",
    "    d_error = error - errors_hist[-1]\n",
    "    feature = np.concatenate([error, i_error, d_error]).reshape(1, -1)  # (1, 36)\n",
    "    actions = np.array([reg.predict(feature)[0] for reg in pid_regs])  # (3,)\n",
    "    return actions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 构造历史误差序列\n",
    "errors_hist = []\n",
    "\n",
    "states_pid_list = []\n",
    "reward_pid_list = []\n",
    "action_pid_list = []\n",
    "obs_pid_list = []\n",
    "obs = model_env.reset()\n",
    "setpoint = obs[0, :]\n",
    "for i in range(300):\n",
    "    error = setpoint - obs[0, :]\n",
    "    errors_hist.append(error)\n",
    "    errprs_hist_arr = np.array(errors_hist)\n",
    "    if i < 100:\n",
    "        action, _states = model.predict(observation=obs, deterministic=True)\n",
    "    else:\n",
    "        action = pid_predict(obs[0, :], errprs_hist_arr, setpoint, pid_regs)\n",
    "        action = np.array(np.clip(action, -1, 1), dtype=np.float32)[np.newaxis, :]\n",
    "    obs, rewards, dones, info = model_env.step(action)\n",
    "    states = model_env.envs[0].flow_field.obs.copy()\n",
    "\n",
    "    reward_pid_list.append(rewards)\n",
    "    action_pid_list.append(action[0,:])\n",
    "    states_pid_list.append(np.array(states))\n",
    "    obs_pid_list.append(obs[0,:])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 画图对比第1个输出\n",
    "plt.figure(figsize=(10, 5))\n",
    "plt.plot(np.array(action_list)[:, 2], label='DRL')\n",
    "plt.plot(np.array(action_pid_list)[:, 2], label='PID')\n",
    "plt.xlabel('Time')\n",
    "plt.ylabel('Action')\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# LBM信号转换与FFT分析\n",
    "\n",
    "## 参数说明\n",
    "- **LBM模拟参数**: 粘性ν=0.002, 流速U=0.01, 密度ρ=1, 圆柱直径D=40, 控制间隔400步\n",
    "- **现实参数**: 圆柱直径D_real=36mm, 流速U_real=0.005284m/s, 雷诺数Re=200"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "LBM雷诺数 Re = 200.0\n",
      "\n",
      "LBM特征时间 t*_lbm = 4000.0 步\n",
      "现实特征时间 t*_real = 6.8130 s\n",
      "\n",
      "1 LBM步 = 0.001703 s\n",
      "控制间隔 (400步) = 0.6813 s\n",
      "\n",
      "速度换算: 模拟单位速度 × 0.005284 = 现实速度 (m/s)\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "from scipy.fft import fft, ifft, fftfreq\n",
    "import json\n",
    "\n",
    "# ==================== 1. 参数定义与单位换算 ====================\n",
    "\n",
    "# LBM模拟参数\n",
    "nu_lbm = 0.002       # LBM粘性\n",
    "U_lbm = 0.01         # LBM流速\n",
    "rho_lbm = 1          # LBM密度\n",
    "D_lbm = 40           # LBM圆柱直径\n",
    "control_interval = 400  # 每400步做一次控制\n",
    "\n",
    "# 验证雷诺数\n",
    "Re = U_lbm * D_lbm / nu_lbm\n",
    "print(f\"LBM雷诺数 Re = {Re}\")\n",
    "\n",
    "# 现实世界参数\n",
    "D_real = 0.036       # 现实圆柱直径 (m)\n",
    "U_real = 0.005284    # 现实流速 (m/s)\n",
    "\n",
    "# 时间尺度换算\n",
    "# 特征时间 t* = D/U\n",
    "t_char_lbm = D_lbm / U_lbm          # LBM特征时间 (步)\n",
    "t_char_real = D_real / U_real       # 现实特征时间 (s)\n",
    "\n",
    "print(f\"\\nLBM特征时间 t*_lbm = {t_char_lbm} 步\")\n",
    "print(f\"现实特征时间 t*_real = {t_char_real:.4f} s\")\n",
    "\n",
    "# 单步LBM对应的现实时间\n",
    "dt_real_per_lbm_step = t_char_real / t_char_lbm\n",
    "print(f\"\\n1 LBM步 = {dt_real_per_lbm_step:.6f} s\")\n",
    "\n",
    "# 控制间隔对应的现实时间\n",
    "dt_control_real = control_interval * dt_real_per_lbm_step\n",
    "print(f\"控制间隔 ({control_interval}步) = {dt_control_real:.4f} s\")\n",
    "\n",
    "# 速度换算系数: 模拟中以U_lbm为单位的速度转换为现实m/s\n",
    "velocity_scale = U_real  # 因为模拟数据已经是以U_lbm为单位的无量纲值\n",
    "print(f\"\\n速度换算: 模拟单位速度 × {velocity_scale} = 现实速度 (m/s)\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "数据列名: ['action1', 'action2', 'action3', 'cd1', 'cl1', 'cd2', 'cl2', 'cd3', 'cl3', 'sensor1', 'sensor2', 'sensor3', 'sensor4', 'sensor5', 'sensor6']\n",
      "数据形状: (100, 15)\n",
      "\n",
      "前5行数据:\n",
      "    action1   action2   action3       cd1       cl1       cd2       cl2  \\\n",
      "0  0.219772 -1.102502  0.785094  0.833128 -0.442332  0.605913  1.432663   \n",
      "1  0.172939 -1.189193  0.705810  0.825710 -0.451864  0.561754  1.384577   \n",
      "2  0.125060 -1.277674  0.660628  0.817200 -0.454893  0.538382  1.342992   \n",
      "3  0.068533 -1.362991  0.631910  0.807906 -0.450888  0.535376  1.315636   \n",
      "4  0.005777 -1.429859  0.614729  0.799177 -0.438877  0.550793  1.308386   \n",
      "\n",
      "        cd3       cl3   sensor1   sensor2   sensor3   sensor4   sensor5  \\\n",
      "0  1.409209 -2.234100  1.628976 -0.358234  1.106453 -0.857525  1.085309   \n",
      "1  1.458620 -2.229482  1.674574 -0.291866  1.111415 -0.711050  1.029870   \n",
      "2  1.495660 -2.195558  1.712737 -0.217691  1.116291 -0.520836  0.985981   \n",
      "3  1.518787 -2.141293  1.733820 -0.128267  1.124613 -0.287110  0.955525   \n",
      "4  1.528402 -2.074041  1.732470 -0.024555  1.138474 -0.031928  0.939950   \n",
      "\n",
      "    sensor6  \n",
      "0 -0.417791  \n",
      "1 -0.345252  \n",
      "2 -0.259184  \n",
      "3 -0.162913  \n",
      "4 -0.059992  \n",
      "\n",
      "前三列名称: ['action1', 'action2', 'action3']\n",
      "原始模拟数据范围:\n",
      "  action1: [-0.1615, 0.4944]\n",
      "  action2: [-1.5130, -0.6257]\n",
      "  action3: [0.5151, 1.6279]\n",
      "\n",
      "现实世界数据范围 (m/s):\n",
      "  action1: [-0.000854, 0.002612]\n",
      "  action2: [-0.007995, -0.003306]\n",
      "  action3: [0.002722, 0.008602]\n",
      "\n",
      "数据点数: 100\n",
      "总模拟时间: 39600 步 = 67.45 s\n"
     ]
    }
   ],
   "source": [
    "# ==================== 2. 读取CSV数据并转换 ====================\n",
    "\n",
    "# 读取数据\n",
    "csv_path = '/home/frank14f/Frank_LBM/output/250620/var_vis/02_time.csv'\n",
    "df = pd.read_csv(csv_path)\n",
    "\n",
    "print(\"数据列名:\", df.columns.tolist())\n",
    "print(f\"数据形状: {df.shape}\")\n",
    "print(f\"\\n前5行数据:\\n{df.head()}\")\n",
    "\n",
    "# 提取前三列 (action1, action2, action3)\n",
    "action_cols = df.columns[:3].tolist()\n",
    "print(f\"\\n前三列名称: {action_cols}\")\n",
    "\n",
    "# 原始模拟数据 (以U_lbm为单位)\n",
    "actions_sim = df[action_cols].values\n",
    "print(f\"原始模拟数据范围:\")\n",
    "for i, col in enumerate(action_cols):\n",
    "    print(f\"  {col}: [{actions_sim[:, i].min():.4f}, {actions_sim[:, i].max():.4f}]\")\n",
    "\n",
    "# 转换为现实世界速度 (m/s)\n",
    "actions_real = actions_sim * velocity_scale\n",
    "print(f\"\\n现实世界数据范围 (m/s):\")\n",
    "for i, col in enumerate(action_cols):\n",
    "    print(f\"  {col}: [{actions_real[:, i].min():.6f}, {actions_real[:, i].max():.6f}]\")\n",
    "\n",
    "# 生成时间轴\n",
    "n_samples = len(df)\n",
    "t_sim = np.arange(n_samples) * control_interval  # LBM步数\n",
    "t_real = np.arange(n_samples) * dt_control_real  # 现实时间 (s)\n",
    "\n",
    "print(f\"\\n数据点数: {n_samples}\")\n",
    "print(f\"总模拟时间: {t_sim[-1]} 步 = {t_real[-1]:.2f} s\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "action1 FFT分析结果:\n",
      "  均值: 0.000851 m/s\n",
      "  主要频率成分:\n",
      "    1. f=0.0440 Hz, T=22.7101 s, A=0.001530 m/s, φ=91.7°\n",
      "    2. f=0.0587 Hz, T=17.0326 s, A=0.000125 m/s, φ=-80.9°\n",
      "    3. f=0.1321 Hz, T=7.5700 s, A=0.000114 m/s, φ=-64.3°\n",
      "    4. f=0.0734 Hz, T=13.6260 s, A=0.000113 m/s, φ=-80.8°\n",
      "    5. f=0.0294 Hz, T=34.0651 s, A=0.000097 m/s, φ=98.1°\n",
      "\n",
      "action2 FFT分析结果:\n",
      "  均值: -0.005306 m/s\n",
      "  主要频率成分:\n",
      "    1. f=0.0440 Hz, T=22.7101 s, A=0.002254 m/s, φ=120.2°\n",
      "    2. f=0.0881 Hz, T=11.3550 s, A=0.000431 m/s, φ=35.0°\n",
      "\n",
      "action3 FFT分析结果:\n",
      "  均值: 0.004913 m/s\n",
      "  主要频率成分:\n",
      "    1. f=0.0440 Hz, T=22.7101 s, A=0.002672 m/s, φ=93.9°\n",
      "    2. f=0.0881 Hz, T=11.3550 s, A=0.000641 m/s, φ=-169.7°\n",
      "    3. f=0.0587 Hz, T=17.0326 s, A=0.000149 m/s, φ=-154.9°\n"
     ]
    }
   ],
   "source": [
    "# ==================== 3. FFT分析 ====================\n",
    "\n",
    "def analyze_periodic_signal(signal, dt, name=\"signal\"):\n",
    "    \"\"\"\n",
    "    对周期信号进行FFT分析，提取主要频率成分\n",
    "    \n",
    "    参数:\n",
    "        signal: 输入信号\n",
    "        dt: 采样间隔 (s)\n",
    "        name: 信号名称\n",
    "    \n",
    "    返回:\n",
    "        features: 包含主要频率成分的特征字典\n",
    "    \"\"\"\n",
    "    n = len(signal)\n",
    "    \n",
    "    # 去除直流分量 (均值)\n",
    "    signal_mean = np.mean(signal)\n",
    "    signal_centered = signal - signal_mean\n",
    "    \n",
    "    # FFT\n",
    "    fft_vals = fft(signal_centered)\n",
    "    freqs = fftfreq(n, dt)\n",
    "    \n",
    "    # 只取正频率部分\n",
    "    pos_mask = freqs > 0\n",
    "    freqs_pos = freqs[pos_mask]\n",
    "    fft_pos = fft_vals[pos_mask]\n",
    "    \n",
    "    # 计算幅值谱\n",
    "    amplitudes = 2.0 * np.abs(fft_pos) / n\n",
    "    phases = np.angle(fft_pos)\n",
    "    \n",
    "    # 找到主要频率成分 (幅值最大的几个)\n",
    "    # 按幅值排序\n",
    "    sorted_indices = np.argsort(amplitudes)[::-1]\n",
    "    \n",
    "    # 提取主要频率成分 (取前5个或幅值大于最大幅值10%的)\n",
    "    max_amp = amplitudes[sorted_indices[0]]\n",
    "    threshold = max_amp * 0.05  # 5%阈值\n",
    "    \n",
    "    main_components = []\n",
    "    for idx in sorted_indices:\n",
    "        if amplitudes[idx] >= threshold and len(main_components) < 10:\n",
    "            main_components.append({\n",
    "                'frequency': float(freqs_pos[idx]),\n",
    "                'amplitude': float(amplitudes[idx]),\n",
    "                'phase': float(phases[idx])\n",
    "            })\n",
    "        elif amplitudes[idx] < threshold:\n",
    "            break\n",
    "    \n",
    "    features = {\n",
    "        'name': name,\n",
    "        'mean': float(signal_mean),\n",
    "        'n_samples': n,\n",
    "        'dt': float(dt),\n",
    "        'total_time': float(n * dt),\n",
    "        'components': main_components\n",
    "    }\n",
    "    \n",
    "    return features, freqs_pos, amplitudes, phases\n",
    "\n",
    "# 对三个action信号进行FFT分析\n",
    "all_features = []\n",
    "fft_results = []\n",
    "\n",
    "for i, col in enumerate(action_cols):\n",
    "    features, freqs, amps, phases = analyze_periodic_signal(\n",
    "        actions_real[:, i], dt_control_real, name=col\n",
    "    )\n",
    "    all_features.append(features)\n",
    "    fft_results.append((freqs, amps, phases))\n",
    "    \n",
    "    print(f\"\\n{col} FFT分析结果:\")\n",
    "    print(f\"  均值: {features['mean']:.6f} m/s\")\n",
    "    print(f\"  主要频率成分:\")\n",
    "    for j, comp in enumerate(features['components'][:5]):\n",
    "        period = 1.0 / comp['frequency'] if comp['frequency'] > 0 else float('inf')\n",
    "        print(f\"    {j+1}. f={comp['frequency']:.4f} Hz, T={period:.4f} s, A={comp['amplitude']:.6f} m/s, φ={np.degrees(comp['phase']):.1f}°\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_3884356/1732364836.py:43: UserWarning: Glyph 26102 (\\N{CJK UNIFIED IDEOGRAPH-65F6}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1732364836.py:43: UserWarning: Glyph 38388 (\\N{CJK UNIFIED IDEOGRAPH-95F4}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1732364836.py:43: UserWarning: Glyph 36895 (\\N{CJK UNIFIED IDEOGRAPH-901F}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1732364836.py:43: UserWarning: Glyph 24230 (\\N{CJK UNIFIED IDEOGRAPH-5EA6}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1732364836.py:43: UserWarning: Glyph 22495 (\\N{CJK UNIFIED IDEOGRAPH-57DF}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1732364836.py:43: UserWarning: Glyph 20449 (\\N{CJK UNIFIED IDEOGRAPH-4FE1}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1732364836.py:43: UserWarning: Glyph 21495 (\\N{CJK UNIFIED IDEOGRAPH-53F7}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1732364836.py:43: UserWarning: Glyph 21407 (\\N{CJK UNIFIED IDEOGRAPH-539F}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1732364836.py:43: UserWarning: Glyph 22987 (\\N{CJK UNIFIED IDEOGRAPH-59CB}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1732364836.py:43: UserWarning: Glyph 37325 (\\N{CJK UNIFIED IDEOGRAPH-91CD}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1732364836.py:43: UserWarning: Glyph 26500 (\\N{CJK UNIFIED IDEOGRAPH-6784}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1732364836.py:43: UserWarning: Glyph 39057 (\\N{CJK UNIFIED IDEOGRAPH-9891}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1732364836.py:43: UserWarning: Glyph 29575 (\\N{CJK UNIFIED IDEOGRAPH-7387}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1732364836.py:43: UserWarning: Glyph 24133 (\\N{CJK UNIFIED IDEOGRAPH-5E45}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1732364836.py:43: UserWarning: Glyph 20540 (\\N{CJK UNIFIED IDEOGRAPH-503C}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1732364836.py:43: UserWarning: Glyph 35889 (\\N{CJK UNIFIED IDEOGRAPH-8C31}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1732364836.py:44: UserWarning: Glyph 36895 (\\N{CJK UNIFIED IDEOGRAPH-901F}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1732364836.py:44: UserWarning: Glyph 24230 (\\N{CJK UNIFIED IDEOGRAPH-5EA6}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1732364836.py:44: UserWarning: Glyph 26102 (\\N{CJK UNIFIED IDEOGRAPH-65F6}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1732364836.py:44: UserWarning: Glyph 22495 (\\N{CJK UNIFIED IDEOGRAPH-57DF}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1732364836.py:44: UserWarning: Glyph 20449 (\\N{CJK UNIFIED IDEOGRAPH-4FE1}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1732364836.py:44: UserWarning: Glyph 21495 (\\N{CJK UNIFIED IDEOGRAPH-53F7}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1732364836.py:44: UserWarning: Glyph 38388 (\\N{CJK UNIFIED IDEOGRAPH-95F4}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1732364836.py:44: UserWarning: Glyph 21407 (\\N{CJK UNIFIED IDEOGRAPH-539F}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1732364836.py:44: UserWarning: Glyph 22987 (\\N{CJK UNIFIED IDEOGRAPH-59CB}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1732364836.py:44: UserWarning: Glyph 37325 (\\N{CJK UNIFIED IDEOGRAPH-91CD}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1732364836.py:44: UserWarning: Glyph 26500 (\\N{CJK UNIFIED IDEOGRAPH-6784}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1732364836.py:44: UserWarning: Glyph 24133 (\\N{CJK UNIFIED IDEOGRAPH-5E45}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1732364836.py:44: UserWarning: Glyph 20540 (\\N{CJK UNIFIED IDEOGRAPH-503C}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1732364836.py:44: UserWarning: Glyph 39057 (\\N{CJK UNIFIED IDEOGRAPH-9891}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1732364836.py:44: UserWarning: Glyph 35889 (\\N{CJK UNIFIED IDEOGRAPH-8C31}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1732364836.py:44: UserWarning: Glyph 29575 (\\N{CJK UNIFIED IDEOGRAPH-7387}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png', dpi=150, bbox_inches='tight')\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 36895 (\\N{CJK UNIFIED IDEOGRAPH-901F}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 24230 (\\N{CJK UNIFIED IDEOGRAPH-5EA6}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 26102 (\\N{CJK UNIFIED IDEOGRAPH-65F6}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 22495 (\\N{CJK UNIFIED IDEOGRAPH-57DF}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 20449 (\\N{CJK UNIFIED IDEOGRAPH-4FE1}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 21495 (\\N{CJK UNIFIED IDEOGRAPH-53F7}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 38388 (\\N{CJK UNIFIED IDEOGRAPH-95F4}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 21407 (\\N{CJK UNIFIED IDEOGRAPH-539F}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 22987 (\\N{CJK UNIFIED IDEOGRAPH-59CB}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 37325 (\\N{CJK UNIFIED IDEOGRAPH-91CD}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 26500 (\\N{CJK UNIFIED IDEOGRAPH-6784}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 24133 (\\N{CJK UNIFIED IDEOGRAPH-5E45}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 20540 (\\N{CJK UNIFIED IDEOGRAPH-503C}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 39057 (\\N{CJK UNIFIED IDEOGRAPH-9891}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 35889 (\\N{CJK UNIFIED IDEOGRAPH-8C31}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 29575 (\\N{CJK UNIFIED IDEOGRAPH-7387}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 216176 characters]",
      "text/plain": [
       "<Figure size 1400x1000 with 6 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "图片已保存到: /home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png\n"
     ]
    }
   ],
   "source": [
    "# ==================== 4. 绘图对比 ====================\n",
    "\n",
    "fig, axes = plt.subplots(3, 2, figsize=(14, 10))\n",
    "colors = ['#1f77b4', '#ff7f0e', '#2ca02c']\n",
    "\n",
    "for i, col in enumerate(action_cols):\n",
    "    # 左列: 时域对比\n",
    "    ax_time = axes[i, 0]\n",
    "    ax_time.plot(t_real, actions_real[:, i], 'b-', alpha=0.7, label='原始信号', linewidth=1)\n",
    "    \n",
    "    # 重构信号\n",
    "    features = all_features[i]\n",
    "    t_recon = t_real\n",
    "    signal_recon = np.ones_like(t_recon) * features['mean']\n",
    "    for comp in features['components']:\n",
    "        signal_recon += comp['amplitude'] * np.cos(2 * np.pi * comp['frequency'] * t_recon + comp['phase'])\n",
    "    \n",
    "    ax_time.plot(t_real, signal_recon, 'r--', alpha=0.8, label='FFT重构', linewidth=1.5)\n",
    "    ax_time.set_xlabel('时间 (s)')\n",
    "    ax_time.set_ylabel('速度 (m/s)')\n",
    "    ax_time.set_title(f'{col} - 时域信号')\n",
    "    ax_time.legend(loc='upper right')\n",
    "    ax_time.grid(True, alpha=0.3)\n",
    "    \n",
    "    # 右列: 频谱\n",
    "    ax_freq = axes[i, 1]\n",
    "    freqs, amps, _ = fft_results[i]\n",
    "    ax_freq.stem(freqs[:len(freqs)//4], amps[:len(amps)//4], linefmt=colors[i], markerfmt='o', basefmt='k-')\n",
    "    ax_freq.set_xlabel('频率 (Hz)')\n",
    "    ax_freq.set_ylabel('幅值 (m/s)')\n",
    "    ax_freq.set_title(f'{col} - 频谱')\n",
    "    ax_freq.grid(True, alpha=0.3)\n",
    "    \n",
    "    # 标注主频\n",
    "    main_freq = features['components'][0]['frequency']\n",
    "    main_amp = features['components'][0]['amplitude']\n",
    "    ax_freq.annotate(f'f={main_freq:.4f}Hz\\nT={1/main_freq:.2f}s', \n",
    "                     xy=(main_freq, main_amp), \n",
    "                     xytext=(main_freq*1.5, main_amp*0.8),\n",
    "                     arrowprops=dict(arrowstyle='->', color='red'),\n",
    "                     fontsize=9)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png', dpi=150, bbox_inches='tight')\n",
    "plt.show()\n",
    "print(\"\\n图片已保存到: /home/frank14f/Frank_LBM/output/250620/var_vis/signal_fft_analysis.png\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "特征文件已保存到: /home/frank14f/Frank_LBM/output/250620/var_vis/signal_features.json\n",
      "\n",
      "特征文件内容预览:\n",
      "{\n",
      "  \"description\": \"LBM模拟信号FFT特征提取结果\",\n",
      "  \"conversion_parameters\": {\n",
      "    \"LBM\": {\n",
      "      \"viscosity\": 0.002,\n",
      "      \"velocity\": 0.01,\n",
      "      \"density\": 1,\n",
      "      \"diameter\": 40,\n",
      "      \"control_interval_steps\": 400,\n",
      "      \"Reynolds_number\": 200.0\n",
      "    },\n",
      "    \"Real_world\": {\n",
      "      \"diameter_m\": 0.036,\n",
      "      \"velocity_m_s\": 0.005284,\n",
      "      \"dt_per_lbm_step_s\": 0.0017032551097653293,\n",
      "      \"dt_control_s\": 0.6813020439061317,\n",
      "      \"velocity_scale\": 0.005284\n",
      "    }\n",
      "  },\n",
      "  \"signals\": [\n",
      "    {\n",
      "      \"name\": \"action1\",\n",
      "      \"mean\": 0.0008512038149141455,\n",
      "      \"n_samples\": 100,\n",
      "      \"dt\": 0.6813020439061317,\n",
      "      \"total_time\": 68.13020439061317,\n",
      "      \"components\": [\n",
      "        {\n",
      "          \"frequency\": 0.044033333333333334,\n",
      "          \"amplitude\": 0.0015295375570157808,\n",
      "          \"phase\": 1.6001886509104652\n",
      "        },\n",
      "        {\n",
      "          \"frequency\": 0.05871111111111111,\n",
      "          \"amplitude\": 0.0001245949831349201,\n",
      "          \"phase\": -1.4115671949346449\n",
      "        },\n",
      "        {\n",
      "          \"frequency\": 0.1321,\n",
      "          \"amplitude\": 0.00011396701317243492,\n",
      "          \"phase\": -1.1216562527011376\n",
      "        },\n",
      "        {\n",
      "          \"frequency\": 0.07338888888888889,\n",
      "          \"amplitude\": 0.00011265972705982442,\n",
      "          \"phase\": -1.4102590671930986\n",
      "        },\n",
      "        {\n",
      "          \"frequency\": 0.029355555555555555,\n",
      "          \"amplitude\": 9.741920137286841e-05,\n",
      "          \"phase\": 1.7124618259522597\n",
      "        }\n",
      "      ]\n",
      "    },\n",
      "    {\n",
      "      \"name\": \"action2\",\n",
      "      \"mean\": -0.005306393809482972,\n",
      "      \"n_samples\": 100,\n",
      "      \"dt\": 0.6813020439061317,\n",
      "      \"total_time\": 68.13020439061317,\n",
      "      \"components\": [\n",
      "        {\n",
      "          \"frequency\": 0.044033333333333334,\n",
      "          \"amplitude\": 0.0022537283592376674,\n",
      "          \"phase\": 2.098729567895884\n",
      "        },\n",
      "        {\n",
      "          \"frequency\": 0.08806666666666667,\n",
      "          \"amplitude\": 0.0004306460952092756,\n",
      "          \"phase\": 0.6115163825013263\n",
      "        }\n",
      "      ]\n",
      "    },\n",
      "    {\n",
      "      \"name\": \"action3\",\n",
      "      \"mean\": 0.004913377161881004,\n",
      "      \"n_samples\": 1...\n"
     ]
    }
   ],
   "source": [
    "# ==================== 5. 保存特征到文件 ====================\n",
    "\n",
    "# 创建完整的特征文件\n",
    "feature_file = {\n",
    "    'description': 'LBM模拟信号FFT特征提取结果',\n",
    "    'conversion_parameters': {\n",
    "        'LBM': {\n",
    "            'viscosity': nu_lbm,\n",
    "            'velocity': U_lbm,\n",
    "            'density': rho_lbm,\n",
    "            'diameter': D_lbm,\n",
    "            'control_interval_steps': control_interval,\n",
    "            'Reynolds_number': Re\n",
    "        },\n",
    "        'Real_world': {\n",
    "            'diameter_m': D_real,\n",
    "            'velocity_m_s': U_real,\n",
    "            'dt_per_lbm_step_s': dt_real_per_lbm_step,\n",
    "            'dt_control_s': dt_control_real,\n",
    "            'velocity_scale': velocity_scale\n",
    "        }\n",
    "    },\n",
    "    'signals': all_features\n",
    "}\n",
    "\n",
    "# 保存为JSON文件\n",
    "feature_path = '/home/frank14f/Frank_LBM/output/250620/var_vis/signal_features.json'\n",
    "with open(feature_path, 'w', encoding='utf-8') as f:\n",
    "    json.dump(feature_file, f, indent=2, ensure_ascii=False)\n",
    "\n",
    "print(f\"特征文件已保存到: {feature_path}\")\n",
    "print(\"\\n特征文件内容预览:\")\n",
    "print(json.dumps(feature_file, indent=2, ensure_ascii=False)[:2000] + \"...\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_3884356/1982610840.py:58: UserWarning: Glyph 26102 (\\N{CJK UNIFIED IDEOGRAPH-65F6}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1982610840.py:58: UserWarning: Glyph 38388 (\\N{CJK UNIFIED IDEOGRAPH-95F4}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1982610840.py:58: UserWarning: Glyph 36895 (\\N{CJK UNIFIED IDEOGRAPH-901F}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1982610840.py:58: UserWarning: Glyph 24230 (\\N{CJK UNIFIED IDEOGRAPH-5EA6}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1982610840.py:58: UserWarning: Glyph 29983 (\\N{CJK UNIFIED IDEOGRAPH-751F}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1982610840.py:58: UserWarning: Glyph 25104 (\\N{CJK UNIFIED IDEOGRAPH-6210}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1982610840.py:58: UserWarning: Glyph 30340 (\\N{CJK UNIFIED IDEOGRAPH-7684}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1982610840.py:58: UserWarning: Glyph 21608 (\\N{CJK UNIFIED IDEOGRAPH-5468}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1982610840.py:58: UserWarning: Glyph 26399 (\\N{CJK UNIFIED IDEOGRAPH-671F}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1982610840.py:58: UserWarning: Glyph 20449 (\\N{CJK UNIFIED IDEOGRAPH-4FE1}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1982610840.py:58: UserWarning: Glyph 21495 (\\N{CJK UNIFIED IDEOGRAPH-53F7}) missing from current font.\n",
      "  plt.tight_layout()\n",
      "/tmp/ipykernel_3884356/1982610840.py:59: UserWarning: Glyph 36895 (\\N{CJK UNIFIED IDEOGRAPH-901F}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/generated_signals.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1982610840.py:59: UserWarning: Glyph 24230 (\\N{CJK UNIFIED IDEOGRAPH-5EA6}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/generated_signals.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1982610840.py:59: UserWarning: Glyph 29983 (\\N{CJK UNIFIED IDEOGRAPH-751F}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/generated_signals.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1982610840.py:59: UserWarning: Glyph 25104 (\\N{CJK UNIFIED IDEOGRAPH-6210}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/generated_signals.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1982610840.py:59: UserWarning: Glyph 30340 (\\N{CJK UNIFIED IDEOGRAPH-7684}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/generated_signals.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1982610840.py:59: UserWarning: Glyph 21608 (\\N{CJK UNIFIED IDEOGRAPH-5468}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/generated_signals.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1982610840.py:59: UserWarning: Glyph 26399 (\\N{CJK UNIFIED IDEOGRAPH-671F}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/generated_signals.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1982610840.py:59: UserWarning: Glyph 20449 (\\N{CJK UNIFIED IDEOGRAPH-4FE1}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/generated_signals.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1982610840.py:59: UserWarning: Glyph 21495 (\\N{CJK UNIFIED IDEOGRAPH-53F7}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/generated_signals.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1982610840.py:59: UserWarning: Glyph 26102 (\\N{CJK UNIFIED IDEOGRAPH-65F6}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/generated_signals.png', dpi=150, bbox_inches='tight')\n",
      "/tmp/ipykernel_3884356/1982610840.py:59: UserWarning: Glyph 38388 (\\N{CJK UNIFIED IDEOGRAPH-95F4}) missing from current font.\n",
      "  plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/generated_signals.png', dpi=150, bbox_inches='tight')\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 29983 (\\N{CJK UNIFIED IDEOGRAPH-751F}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 25104 (\\N{CJK UNIFIED IDEOGRAPH-6210}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 30340 (\\N{CJK UNIFIED IDEOGRAPH-7684}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 21608 (\\N{CJK UNIFIED IDEOGRAPH-5468}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n",
      "/home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 26399 (\\N{CJK UNIFIED IDEOGRAPH-671F}) missing from current font.\n",
      "  fig.canvas.print_figure(bytes_io, **kw)\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 108868 characters]",
      "text/plain": [
       "<Figure size 1200x800 with 3 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "生成信号图已保存到: /home/frank14f/Frank_LBM/output/250620/var_vis/generated_signals.png\n"
     ]
    }
   ],
   "source": [
    "# ==================== 6. 信号生成器 ====================\n",
    "\n",
    "def generate_periodic_signals(t, feature_file_path=None, features=None):\n",
    "    \"\"\"\n",
    "    根据特征文件在任意时间点生成三个周期信号\n",
    "    \n",
    "    参数:\n",
    "        t: 时间数组或标量 (秒)，可以是任意长度和任意时间点\n",
    "        feature_file_path: 特征文件路径 (JSON格式)\n",
    "        features: 或直接传入特征字典\n",
    "    \n",
    "    返回:\n",
    "        signals: dict, 包含三个信号的数组\n",
    "    \"\"\"\n",
    "    # 加载特征\n",
    "    if features is None:\n",
    "        with open(feature_file_path, 'r', encoding='utf-8') as f:\n",
    "            features = json.load(f)\n",
    "    \n",
    "    t = np.atleast_1d(t)\n",
    "    \n",
    "    signals = {}\n",
    "    for signal_feature in features['signals']:\n",
    "        name = signal_feature['name']\n",
    "        mean = signal_feature['mean']\n",
    "        components = signal_feature['components']\n",
    "        \n",
    "        # 初始化为均值\n",
    "        signal = np.ones_like(t, dtype=float) * mean\n",
    "        \n",
    "        # 叠加各频率成分\n",
    "        for comp in components:\n",
    "            freq = comp['frequency']\n",
    "            amp = comp['amplitude']\n",
    "            phase = comp['phase']\n",
    "            signal += amp * np.cos(2 * np.pi * freq * t + phase)\n",
    "        \n",
    "        signals[name] = signal\n",
    "    \n",
    "    return signals\n",
    "\n",
    "\n",
    "# 测试: 生成0-30秒的信号\n",
    "t_test = np.linspace(0, 30, 1000)\n",
    "signals_generated = generate_periodic_signals(t_test, features=feature_file)\n",
    "\n",
    "# 绘图验证\n",
    "fig, axes = plt.subplots(3, 1, figsize=(12, 8))\n",
    "colors = ['#1f77b4', '#ff7f0e', '#2ca02c']\n",
    "\n",
    "for i, (name, signal) in enumerate(signals_generated.items()):\n",
    "    axes[i].plot(t_test, signal, color=colors[i], linewidth=1)\n",
    "    axes[i].set_xlabel('时间 (s)')\n",
    "    axes[i].set_ylabel('速度 (m/s)')\n",
    "    axes[i].set_title(f'{name} - 生成的周期信号 (0-30s)')\n",
    "    axes[i].grid(True, alpha=0.3)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.savefig('/home/frank14f/Frank_LBM/output/250620/var_vis/generated_signals.png', dpi=150, bbox_inches='tight')\n",
    "plt.show()\n",
    "print(\"\\n生成信号图已保存到: /home/frank14f/Frank_LBM/output/250620/var_vis/generated_signals.png\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "独立信号生成脚本已保存到: /home/frank14f/Frank_LBM/output/250620/var_vis/signal_generator.py\n",
      "\n",
      "使用示例:\n",
      "  python /home/frank14f/Frank_LBM/output/250620/var_vis/signal_generator.py --feature_file signal_features.json --t_start 0 --t_end 60 --output output.csv\n"
     ]
    }
   ],
   "source": [
    "# ==================== 7. 独立使用的信号生成脚本 ====================\n",
    "# 以下代码可以复制出去作为独立脚本使用\n",
    "\n",
    "signal_generator_code = '''\n",
    "#!/usr/bin/env python3\n",
    "\"\"\"\n",
    "周期信号生成器\n",
    "根据FFT特征文件在任意时间点生成周期信号\n",
    "\n",
    "使用方法:\n",
    "    python signal_generator.py --feature_file signal_features.json --t_start 0 --t_end 60 --n_points 1000 --output output.csv\n",
    "\"\"\"\n",
    "\n",
    "import numpy as np\n",
    "import json\n",
    "import argparse\n",
    "\n",
    "def generate_periodic_signals(t, feature_file_path):\n",
    "    \"\"\"\n",
    "    根据特征文件在任意时间点生成周期信号\n",
    "    \n",
    "    参数:\n",
    "        t: 时间数组 (秒)\n",
    "        feature_file_path: 特征文件路径\n",
    "    \n",
    "    返回:\n",
    "        signals: dict, 包含各信号的数组\n",
    "    \"\"\"\n",
    "    with open(feature_file_path, 'r', encoding='utf-8') as f:\n",
    "        features = json.load(f)\n",
    "    \n",
    "    t = np.atleast_1d(t)\n",
    "    signals = {}\n",
    "    \n",
    "    for signal_feature in features['signals']:\n",
    "        name = signal_feature['name']\n",
    "        mean = signal_feature['mean']\n",
    "        components = signal_feature['components']\n",
    "        \n",
    "        signal = np.ones_like(t, dtype=float) * mean\n",
    "        for comp in components:\n",
    "            freq = comp['frequency']\n",
    "            amp = comp['amplitude']\n",
    "            phase = comp['phase']\n",
    "            signal += amp * np.cos(2 * np.pi * freq * t + phase)\n",
    "        \n",
    "        signals[name] = signal\n",
    "    \n",
    "    return signals\n",
    "\n",
    "\n",
    "def get_signal_at_time(t, feature_file_path):\n",
    "    \"\"\"\n",
    "    获取单个时间点的信号值\n",
    "    \n",
    "    参数:\n",
    "        t: 时间 (秒)\n",
    "        feature_file_path: 特征文件路径\n",
    "    \n",
    "    返回:\n",
    "        values: list, 三个信号的值\n",
    "    \"\"\"\n",
    "    signals = generate_periodic_signals(np.array([t]), feature_file_path)\n",
    "    return [signals[name][0] for name in signals.keys()]\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    parser = argparse.ArgumentParser(description='周期信号生成器')\n",
    "    parser.add_argument('--feature_file', type=str, required=True, help='特征文件路径')\n",
    "    parser.add_argument('--t_start', type=float, default=0, help='起始时间 (s)')\n",
    "    parser.add_argument('--t_end', type=float, default=60, help='结束时间 (s)')\n",
    "    parser.add_argument('--n_points', type=int, default=1000, help='采样点数')\n",
    "    parser.add_argument('--output', type=str, default='generated_signals.csv', help='输出文件')\n",
    "    \n",
    "    args = parser.parse_args()\n",
    "    \n",
    "    t = np.linspace(args.t_start, args.t_end, args.n_points)\n",
    "    signals = generate_periodic_signals(t, args.feature_file)\n",
    "    \n",
    "    # 保存为CSV\n",
    "    import pandas as pd\n",
    "    df = pd.DataFrame({'time_s': t, **signals})\n",
    "    df.to_csv(args.output, index=False)\n",
    "    print(f\"信号已保存到: {args.output}\")\n",
    "'''\n",
    "\n",
    "# 保存为独立脚本\n",
    "script_path = '/home/frank14f/Frank_LBM/output/250620/var_vis/signal_generator.py'\n",
    "with open(script_path, 'w', encoding='utf-8') as f:\n",
    "    f.write(signal_generator_code)\n",
    "\n",
    "print(f\"独立信号生成脚本已保存到: {script_path}\")\n",
    "print(\"\\n使用示例:\")\n",
    "print(f\"  python {script_path} --feature_file signal_features.json --t_start 0 --t_end 60 --output output.csv\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "============================================================\n",
      "LBM信号转换与FFT分析总结\n",
      "============================================================\n",
      "\n",
      "【单位换算结果】\n",
      "  • 1 LBM步 = 1.7033 ms\n",
      "  • 控制间隔 (400步) = 0.6813 s = 681.30 ms\n",
      "  • 速度换算: v_real = v_sim × 0.005284 m/s\n",
      "\n",
      "【信号周期特征】\n",
      "  • action1: 主频 f = 0.0440 Hz, 周期 T = 22.7101 s\n",
      "  • action2: 主频 f = 0.0440 Hz, 周期 T = 22.7101 s\n",
      "  • action3: 主频 f = 0.0440 Hz, 周期 T = 22.7101 s\n",
      "\n",
      "【输出文件】\n",
      "  • 特征文件: signal_features.json\n",
      "  • FFT分析图: signal_fft_analysis.png\n",
      "  • 生成信号图: generated_signals.png\n",
      "  • 信号生成脚本: signal_generator.py\n",
      "\n",
      "【使用方法】\n",
      "  1. 直接在Python中使用 generate_periodic_signals(t, features=feature_file)\n",
      "  2. 命令行: python signal_generator.py --feature_file signal_features.json --t_start 0 --t_end 60\n",
      "============================================================\n"
     ]
    }
   ],
   "source": [
    "# ==================== 8. 总结输出 ====================\n",
    "\n",
    "print(\"=\" * 60)\n",
    "print(\"LBM信号转换与FFT分析总结\")\n",
    "print(\"=\" * 60)\n",
    "\n",
    "print(\"\\n【单位换算结果】\")\n",
    "print(f\"  • 1 LBM步 = {dt_real_per_lbm_step*1000:.4f} ms\")\n",
    "print(f\"  • 控制间隔 (400步) = {dt_control_real:.4f} s = {dt_control_real*1000:.2f} ms\")\n",
    "print(f\"  • 速度换算: v_real = v_sim × {velocity_scale:.6f} m/s\")\n",
    "\n",
    "print(\"\\n【信号周期特征】\")\n",
    "for features in all_features:\n",
    "    main_freq = features['components'][0]['frequency']\n",
    "    main_period = 1.0 / main_freq\n",
    "    print(f\"  • {features['name']}: 主频 f = {main_freq:.4f} Hz, 周期 T = {main_period:.4f} s\")\n",
    "\n",
    "print(\"\\n【输出文件】\")\n",
    "print(f\"  • 特征文件: signal_features.json\")\n",
    "print(f\"  • FFT分析图: signal_fft_analysis.png\")\n",
    "print(f\"  • 生成信号图: generated_signals.png\")\n",
    "print(f\"  • 信号生成脚本: signal_generator.py\")\n",
    "\n",
    "print(\"\\n【使用方法】\")\n",
    "print(\"  1. 直接在Python中使用 generate_periodic_signals(t, features=feature_file)\")\n",
    "print(\"  2. 命令行: python signal_generator.py --feature_file signal_features.json --t_start 0 --t_end 60\")\n",
    "print(\"=\" * 60)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pycuda_3_10",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
