Browse Source

1:修正文件路径

wmy 6 months ago
parent
commit
4f9a704916
56 changed files with 13711 additions and 0 deletions
  1. 6 0
      .idea/misc.xml
  2. 0 0
      models/uf-rl/训练/__init__.py
  3. 25 0
      models/uf-rl/训练/config/dqn_config.yaml
  4. 124 0
      models/uf-rl/训练/config/env_config.yaml
  5. 44 0
      models/uf-rl/训练/config/uf_analyze_config.yaml
  6. 190 0
      models/uf-rl/训练/uf_data_process/calculate.py
  7. 113 0
      models/uf-rl/训练/uf_data_process/filter.py
  8. 381 0
      models/uf-rl/训练/uf_data_process/fit.py
  9. 79 0
      models/uf-rl/训练/uf_data_process/label.py
  10. 41 0
      models/uf-rl/训练/uf_data_process/load.py
  11. 374 0
      models/uf-rl/训练/uf_data_process/pipeline.py
  12. 32 0
      models/uf-rl/训练/uf_data_process/run_ufdata_pipeline.py
  13. 500 0
      models/uf-rl/训练/uf_train/README.md
  14. 1076 0
      models/uf-rl/训练/uf_train/UF_RL_架构问题与优化方案.md
  15. 1082 0
      models/uf-rl/训练/uf_train/UF_RL_训练与预测流程详解.md
  16. 1160 0
      models/uf-rl/训练/uf_train/UF_RL_详细技术文档.md
  17. 0 0
      models/uf-rl/训练/uf_train/__init__.py
  18. 0 0
      models/uf-rl/训练/uf_train/data_to_rl/__init__.py
  19. 83 0
      models/uf-rl/训练/uf_train/data_to_rl/data_splitter.py
  20. 221 0
      models/uf-rl/训练/uf_train/data_to_rl/data_to_rl_config.yaml
  21. 34 0
      models/uf-rl/训练/uf_train/data_to_rl/get_reset_pool.py
  22. 33 0
      models/uf-rl/训练/uf_train/data_to_rl/loader.py
  23. 79 0
      models/uf-rl/训练/uf_train/data_to_rl/run_data_to_rl_pipeline.py
  24. 28 0
      models/uf-rl/训练/uf_train/data_to_rl/state_construction.py
  25. 47 0
      models/uf-rl/训练/uf_train/data_to_rl/state_space_bounds.py
  26. 0 0
      models/uf-rl/训练/uf_train/env/__init__.py
  27. 138 0
      models/uf-rl/训练/uf_train/env/check_initial_state.py
  28. 350 0
      models/uf-rl/训练/uf_train/env/env_params.py
  29. 185 0
      models/uf-rl/训练/uf_train/env/env_reset.py
  30. 107 0
      models/uf-rl/训练/uf_train/env/env_visual.py
  31. BIN
      models/uf-rl/训练/uf_train/env/resistance_model_bw.pth
  32. BIN
      models/uf-rl/训练/uf_train/env/resistance_model_fp.pth
  33. 472 0
      models/uf-rl/训练/uf_train/env/uf_env.py
  34. 529 0
      models/uf-rl/训练/uf_train/env/uf_physics.py
  35. 169 0
      models/uf-rl/训练/uf_train/env/uf_resistance_models_define.py
  36. 59 0
      models/uf-rl/训练/uf_train/env/uf_resistance_models_load.py
  37. 0 0
      models/uf-rl/训练/uf_train/rl_model/DQN/__init__.py
  38. 186 0
      models/uf-rl/训练/uf_train/rl_model/DQN/dqn_decider.py
  39. 94 0
      models/uf-rl/训练/uf_train/rl_model/DQN/dqn_params.py
  40. 261 0
      models/uf-rl/训练/uf_train/rl_model/DQN/dqn_statebuilder.py
  41. 168 0
      models/uf-rl/训练/uf_train/rl_model/DQN/dqn_trainer.py
  42. BIN
      models/uf-rl/训练/uf_train/rl_model/DQN/model/dqn_model.zip
  43. BIN
      models/uf-rl/训练/uf_train/rl_model/DQN/model/loss.png
  44. 11 0
      models/uf-rl/训练/uf_train/rl_model/DQN/online_datasets/UF1_init_cycle.csv
  45. 2511 0
      models/uf-rl/训练/uf_train/rl_model/DQN/online_datasets/UF1_prev_cycle.csv
  46. 251 0
      models/uf-rl/训练/uf_train/rl_model/DQN/run_dqn_decide.py
  47. 267 0
      models/uf-rl/训练/uf_train/rl_model/DQN/run_dqn_deicde_totalstate.py
  48. 219 0
      models/uf-rl/训练/uf_train/rl_model/DQN/run_dqn_train.py
  49. 0 0
      models/uf-rl/训练/uf_train/rl_model/__init__.py
  50. 161 0
      models/uf-rl/训练/进水动作版超滤训练源码/UF_resistance_models.py
  51. 233 0
      models/uf-rl/训练/进水动作版超滤训练源码/fixed_DQN_decide.py
  52. 1052 0
      models/uf-rl/训练/进水动作版超滤训练源码/fixed_DQN_env.py
  53. 536 0
      models/uf-rl/训练/进水动作版超滤训练源码/fixed_DQN_train.py
  54. BIN
      models/uf-rl/训练/进水动作版超滤训练源码/model/dqn_model.zip
  55. BIN
      models/uf-rl/训练/进水动作版超滤训练源码/resistance_model_bw.pth
  56. BIN
      models/uf-rl/训练/进水动作版超滤训练源码/resistance_model_fp.pth

+ 6 - 0
.idea/misc.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Black">
+    <option name="sdkName" value="env_gpu" />
+  </component>
+</project>

+ 0 - 0
models/uf-rl/训练/__init__.py


+ 25 - 0
models/uf-rl/训练/config/dqn_config.yaml

@@ -0,0 +1,25 @@
+# ==================== DQN 超参数配置 ====================
+
+# ===== 神经网络参数 =====
+learning_rate: 1.0e-4
+
+# ===== 经验回放参数 =====
+buffer_size: 100000
+learning_starts: 10000
+batch_size: 32
+
+# ===== 强化学习核心参数 =====
+gamma: 0.95
+train_freq: 4
+
+# ===== 目标网络更新参数 =====
+target_update_interval: 1
+tau: 0.005
+
+# ===== 探索策略(ε-greedy) =====
+exploration_initial_eps: 1.0
+exploration_fraction: 0.3
+exploration_final_eps: 0.02
+
+# ===== 实验标识 =====
+remark: "default"

+ 124 - 0
models/uf-rl/训练/config/env_config.yaml

@@ -0,0 +1,124 @@
+UFState:
+  # ===== 膜动态运行参数 =====
+  q_UF: 360.0
+  TMP: 0.03
+  temp: 25.0
+  R: 200.0
+
+  # ===== 膜阻力模型参数 =====
+  nuK: 170.0
+  slope: 2.0
+  power: 1.032
+  ceb_removal: 100.0
+
+
+UFPhysicsParams:
+  # ===== TMP 全局约束 =====
+  global_TMP_hard_limit: 0.08
+  global_TMP_soft_limit: 0.06
+
+  # ===== 物理反洗参数 =====
+  tau_bw_s: 20.0
+  gamma_t: 1.0
+  q_bw_m3ph: 1000.0
+
+  # ===== CEB 化学反洗参数 =====
+  T_ceb_interval_h: 48.0
+  v_ceb_m3: 20.0
+  t_ceb_s: 2400.0   # 40 * 60
+
+  # ===== 膜组件参数 =====
+  A: 5120.0   # 128 * 40
+
+  # ===== 吨水电耗查找表 =====
+  energy_lookup:
+    3600: 0.1034
+    3660: 0.1031
+    3720: 0.1029
+    3780: 0.1026
+    3840: 0.1023
+    3900: 0.1021
+    3960: 0.1019
+    4020: 0.1017
+    4080: 0.1015
+    4140: 0.1012
+    4200: 0.1011
+    4260: 0.1008
+    4320: 0.1007
+    4380: 0.1005
+    4440: 0.1003
+    4500: 0.1001
+    4560: 0.0999
+    4620: 0.0998
+    4680: 0.0996
+    4740: 0.0995
+    4800: 0.0993
+
+
+UFActionSpec:
+  # ===== 动作空间范围 =====
+  L_min_s: 3800.0
+  L_max_s: 4800.0
+  t_bw_min_s: 40.0
+  t_bw_max_s: 60.0
+
+  # ===== 动作离散化步长 =====
+  L_step_s: 60.0
+  t_bw_step_s: 5.0
+
+
+UFRewardParams:
+  # ===== TMP 安全与惩罚 =====
+  global_TMP_hard_limit: 0.08
+  global_TMP_soft_limit: 0.06
+  w_tmp_hard: 5.0
+  w_tmp: 1.5
+  p: 3.0
+  w_trend: 1.0
+
+  # ===== 回收率 =====
+  k_rec: 5.0
+  rec_low: 0.92
+  rec_high: 0.99
+  w_rec: 1.0
+
+  # ===== 残余污染 =====
+  k_res: 10.0
+  residual_ref_ratio: null
+  w_res: 2.0
+
+  # ===== 吨水电耗 =====
+  k_energy: 5.0
+  energy_low: 0.0993
+  energy_high: 0.1034
+  energy_ref: 0.1011
+  w_energy: 1.0
+
+
+UFStateBounds:
+  # ===== 流量初始化约束 =====
+  q_UF_min: 210.0
+  q_UF_max: 380.0
+
+  # ===== 温度初始化约束 =====
+  temp_min: 16.0
+  temp_max: 32.0
+
+  # ===== TMP 初始化约束 =====
+  TMP0_min: 0.01
+  TMP0_max: 0.045
+  global_TMP_hard_limit: 0.08
+
+  # ===== 短期污染参数 =====
+  nuK_min: 40.0
+  nuK_max: 260.0
+
+  # ===== 长期污染参数 =====
+  slope_min: 0.03
+  slope_max: 27.0
+  power_min: 0.4
+  power_max: 2.2
+
+  # ===== CEB 去除能力 =====
+  ceb_removal_min: 40.0
+  ceb_removal_max: 250.0

+ 44 - 0
models/uf-rl/训练/config/uf_analyze_config.yaml

@@ -0,0 +1,44 @@
+UF:
+  units: ["UF1", "UF2", "UF3", "UF4"]
+  area_m2: 128 * 40
+
+  inlet_codes: [21.0, 22.0, 23.0, 24.0, 25.0, 26.0]
+  stable_inlet_code: 26.0
+
+  physical_bw_code: 45.0
+  chemical_bw_code: 95.0
+
+  # 列名
+  flow_col_template: "C.M.{unit}_FT_JS@out"
+  temp_col: "C.M.RO_TT_ZJS@out"
+  orp_col: "C.M.UF_ORP_ZCS@out"
+  cond_col: "C.M.RO_Cond_ZJS@out"
+
+Params:
+  # 稳定段提取
+  min_stable_points: 30
+  initial_points: 10
+
+  # 阻力趋势计算
+  segment_head_n: 5
+  segment_tail_n: 5
+
+  scale_factor: 1e10
+
+
+Plot:
+  figsize: [12, 6]
+  dpi: 120
+  color_inlet: "#1f77b4"
+  color_bw_phys: "#ff7f0e"
+  color_bw_chem: "#d62728"
+
+Paths:
+  project_root: "E:/Greentech" # 请根据项目根目录修改相应路径
+
+  raw_data_path: "models/uf-rl/datasets/raw"
+  output_path: "models/uf-rl/datasets/processed/segments"
+  filter_output_path: "models/uf-rl/datasets/processed/filter_segments"
+
+  output_format: "csv"
+

+ 190 - 0
models/uf-rl/训练/uf_data_process/calculate.py

@@ -0,0 +1,190 @@
+from typing import List
+
+import numpy as np
+import pandas as pd
+
+class UFResistanceCalculator:
+    """针对单个段或段列表计算膜通量、TMP、粘度和膜阻力"""
+
+    def __init__(self, units: list, area_m2, scale_factor):
+        self.units = units
+
+        # 处理面积
+        self.A = area_m2
+        if isinstance(self.A, str):
+            try:
+                self.A = float(eval(self.A))  # 将 "128*40" -> 5120.0
+            except Exception as e:
+                print(f"无法解析面积 self.A: {self.A}, 错误: {e}")
+
+        # 处理 scale_factor
+        self.scale_factor = scale_factor
+        if isinstance(self.scale_factor, str):
+            try:
+                self.scale_factor = float(eval(self.scale_factor))
+            except Exception as e:
+                print(f"无法解析 scale_factor: {self.scale_factor}, 错误: {e}")
+
+    @staticmethod
+    def xishan_viscosity(T_celsius):
+        """锡山水厂水温校正粘度,单位 Pa·s"""
+        x = (T_celsius + 273.15) / 300
+        factor = 890 / (
+            280.68 * x ** -1.9 +
+            511.45 * x ** -7.7 +
+            61.131 * x ** -19.6 +
+            0.45903 * x ** -40
+        )
+        mu = 0.00089 / factor
+        return mu
+
+    def calculate_for_segment(self, seg_df: pd.DataFrame, temp_col="C.M.RO_TT_ZJS@out", flow_col=None):
+        """计算单个稳定进水段的 TMP、通量、粘度和膜阻力"""
+        seg_df = seg_df.copy()
+
+        for unit in self.units:
+            unit_flow_col = flow_col or f"C.M.{unit}_FT_JS@out"
+            press_col = f"C.M.{unit}_DB@press_PV"
+
+            if not all(col in seg_df.columns for col in [unit_flow_col, press_col, temp_col]):
+                continue
+
+            # 计算前检查
+            cols_to_check = [press_col, unit_flow_col, temp_col]
+            for col in cols_to_check:
+                # 检查非数值
+                if not pd.api.types.is_numeric_dtype(seg_df[col]):
+                    # 尝试强制转换为数值
+                    seg_df[col] = pd.to_numeric(seg_df[col], errors='coerce')
+
+                # 检查是否还有 NaN 或非有限值
+                if seg_df[col].isna().any() or not np.isfinite(seg_df[col]).all():
+                    print(f"⚠️ 列 {col} 包含 NaN 或非有限值")
+                    print(seg_df[col][~np.isfinite(seg_df[col])])
+
+            seg_df[f"{unit}_TMP_Pa"] = seg_df[press_col] * 1e6
+            seg_df[f"{unit}_J"] = seg_df[unit_flow_col] / self.A / 3600
+            seg_df[f"{unit}_mu"] = self.xishan_viscosity(seg_df[temp_col])
+            seg_df[f"{unit}_R"] = seg_df[f"{unit}_TMP_Pa"] / (seg_df[f"{unit}_J"] * seg_df[f"{unit}_mu"])
+            seg_df[f"{unit}_R_scaled"] = seg_df[f"{unit}_R"] / self.scale_factor
+
+        return seg_df
+
+    def calculate_for_segments(self, segments: list, temp_col="C.M.RO_TT_ZJS@out", flow_col=None):
+        result_segments = []
+        for seg in segments:
+            seg_res = self.calculate_for_segment(seg, temp_col=temp_col, flow_col=flow_col)
+            result_segments.append(seg_res)
+        return result_segments
+
+
+# =============================
+#   单变量稳定性分析(例如 flow/TMP)
+# =============================
+class VariableStabilityAnalyzer:
+    """
+    单变量统计特征分析
+    为每段数据增加:均值 / 标准差 / CV 系数
+    """
+
+    def __init__(self):
+        pass
+
+    def analyze(self, seg: pd.DataFrame, col, prefix=None) -> pd.DataFrame:
+        """分析单段:计算均值、标准差、CV,并添加三列"""
+
+        seg = seg.copy()
+
+        prefix = prefix or col
+
+        mean_val = seg[col].mean()
+        std_val = seg[col].std()
+        cv_val = std_val / mean_val if mean_val != 0 else np.nan
+
+        # 添加三列(整段相同)
+        seg[f"{prefix}_mean"] = mean_val
+        seg[f"{prefix}_std"] = std_val
+        seg[f"{prefix}_cv"] = cv_val
+
+        return seg
+
+    def analyze_segments(self, segments: list, col, prefix=None) -> list:
+        """批量处理段列表"""
+        return [self.analyze(seg, col, prefix) for seg in segments]
+
+# =============================
+#       tmp_start / tmp_end 计算
+# =============================
+class UFPressureAnalyzer:
+    """
+    统计每段的压力起止平均值 tmp_start / tmp_end
+    """
+
+    def __init__(
+        self,
+        press_col,
+        head_n=20,
+        tail_n=20,
+        feature_start="tmp_start",
+        feature_end="tmp_end",
+    ):
+        self.press_col = press_col
+        self.head_n = head_n
+        self.tail_n = tail_n
+        self.feature_start = feature_start
+        self.feature_end = feature_end
+
+    def analyze(self, seg: pd.DataFrame) -> pd.DataFrame:
+        """分析单段,将 tmp_start / tmp_end 加入 DataFrame"""
+        seg = seg.sort_values("time").reset_index(drop=True).copy()
+
+        if len(seg) < self.head_n + self.tail_n:
+            seg[self.feature_start] = None
+            seg[self.feature_end] = None
+        else:
+            seg[self.feature_start] = (
+                seg[self.press_col].iloc[: self.head_n].mean()
+            )
+            seg[self.feature_end] = (
+                seg[self.press_col].iloc[-self.tail_n :].mean()
+            )
+
+        return seg
+
+    def analyze_segments(self, segments: list) -> list:
+        """批量处理段列表"""
+        return [self.analyze(seg) for seg in segments]
+
+# =============================
+#       R_scaled_start / R_scaled_end 计算
+# =============================
+class UFResistanceAnalyzer:
+    """
+    统计每段的放缩后膜阻力起止值 R_scaled_start / R_scaled_end
+    """
+
+    def __init__(self, resistance_col, head_n=20, tail_n=20, feature_start="R_scaled_start", feature_end="R_scaled_end"):
+        self.res_col = resistance_col
+        self.head_n = head_n
+        self.tail_n = tail_n
+        self.feature_start = feature_start
+        self.feature_end = feature_end
+
+    def analyze(self, seg: pd.DataFrame) -> pd.DataFrame:
+        """分析单段,将 R_scaled_start / R_scaled_end 加入 DataFrame"""
+        seg = seg.sort_values("time").reset_index(drop=True).copy()
+
+        if len(seg) < self.head_n + self.tail_n:
+            seg[self.feature_start] = None
+            seg[self.feature_end] = None
+        else:
+            seg[self.feature_start] = seg[self.res_col].iloc[:self.head_n].median()
+            seg[self.feature_end] = seg[self.res_col].iloc[-self.tail_n:].median()
+        return seg
+
+    def analyze_segments(self, segments: list) -> list:
+        """批量处理段列表"""
+        return [self.analyze(seg) for seg in segments]
+
+
+

+ 113 - 0
models/uf-rl/训练/uf_data_process/filter.py

@@ -0,0 +1,113 @@
+import os
+from typing import List
+import numpy as np
+import pandas as pd
+import yaml
+
+# =============================
+#       过滤网络错误上传重复数据
+# =============================
+class ConstantFlowFilter:
+    """
+    过滤掉因网络异常导致的“连续若干行流量重复”的无效段。
+    """
+
+    def __init__(self, flow_col, repeat_len=20):
+        self.flow_col = flow_col
+        self.repeat_len = repeat_len
+
+    def filter(self, seg_df):
+        valid_segments = []
+
+        for sid, g in seg_df.groupby("segment_id"):
+            g = g.sort_values("time").reset_index(drop=True)
+
+            # 若小于 repeat_len,无需检查
+            if len(g) < self.repeat_len:
+                valid_segments.append(g)
+                continue
+
+            # 检查是否存在连续 repeat_len 行流量完全相同
+            has_constant_flow = False
+            for i in range(len(g) - self.repeat_len + 1):
+                window = g[self.flow_col].iloc[i:i + self.repeat_len]
+                if window.nunique() == 1:     # 流量全程不变
+                    has_constant_flow = True
+                    break
+
+            if not has_constant_flow:
+                valid_segments.append(g)
+
+        return valid_segments
+
+
+# =============================
+#       过滤过短事件
+# =============================
+class EventQualityFilter:
+    """
+    根据段落最基本质量进行过滤:
+    例如点数是否足够、事件类型是否单一等。
+    """
+
+    def __init__(self, min_points=40):
+        self.min_points = min_points
+
+    def filter(self, seg_df):
+        """
+        seg_df: pd.DataFrame 或者 list of pd.DataFrame
+        返回 list of pd.DataFrame,每个元素都是通过质量过滤的段
+        """
+        # 如果传入的是单个 DataFrame,就包装成 list
+        if isinstance(seg_df, pd.DataFrame):
+            seg_list = [seg_df]
+        elif isinstance(seg_df, list):
+            seg_list = seg_df
+        else:
+            raise ValueError(f"Unsupported type for seg_df: {type(seg_df)}")
+
+        valid = []
+        for df in seg_list:
+            for sid, g in df.groupby("segment_id"):
+                if len(g) >= self.min_points:
+                    valid.append(g)
+
+        return valid
+
+
+
+# =============================
+#       稳定进水数据提取
+# =============================
+class InletSegmentFilter:
+    def __init__(self, control_col, stable_value=26.0, min_points=40):
+        self.control_col = control_col
+        self.stable_value = stable_value
+        self.min_points = min_points
+
+    def extract(self, seg_df):
+        """
+        seg_df: pd.DataFrame 或 list of pd.DataFrame
+        返回 list of pd.DataFrame,每个元素都是稳定段
+        """
+        # 如果传入的是单个 DataFrame,就包装成 list
+        if isinstance(seg_df, pd.DataFrame):
+            seg_list = [seg_df]
+        elif isinstance(seg_df, list):
+            seg_list = seg_df
+        else:
+            raise ValueError(f"Unsupported type for seg_df: {type(seg_df)}")
+
+        stable_segments = []
+        for df in seg_list:
+            for sid, g in df.groupby("segment_id"):
+                stable = g[g[self.control_col] == self.stable_value]
+                if len(stable) >= self.min_points:
+                    stable_segments.append(stable)
+
+        return stable_segments
+
+
+
+
+

+ 381 - 0
models/uf-rl/训练/uf_data_process/fit.py

@@ -0,0 +1,381 @@
+# cycle_analysis.py
+import numpy as np
+import pandas as pd
+from scipy.optimize import minimize, curve_fit
+from sklearn.metrics import r2_score
+from datetime import timedelta
+
+class ChemicalCycleSegmenter:
+    """
+    根据 event_type == 'bw_chem' 划分化学周期。
+    每两个化学反冲洗之间所有 inlet 稳定段属于一个化学周期。
+    若化学周期时间长度 > max_hours,则标记为无效周期。
+    """
+
+    def __init__(self, max_hours=60):
+        self.max_hours = max_hours
+
+    def assign_cycles(self, df_unit, stable_segments):
+        """
+        df_unit:含 time, event_type
+        stable_segments:列表,每段有 segment_id, time 等
+
+        返回 cycles: dict(cycle_id -> { segments:[], valid:True/False, start_time/end_time })
+        """
+
+        # 找出所有化学反冲洗的位置
+        ceb_times = df_unit.loc[df_unit["event_type"] == "bw_chem", "time"].sort_values().to_list()
+
+        cycles = {}
+        if len(ceb_times) < 2:
+            return cycles
+
+        for i in range(len(ceb_times) - 1):
+            cycle_id = i + 1
+            t_start = ceb_times[i]
+            t_end = ceb_times[i + 1]
+
+            # 该周期内的稳定 segment
+            segs = []
+            for seg in stable_segments:
+                if seg["time"].iloc[0] >= t_start and seg["time"].iloc[-1] <= t_end:
+                    segs.append(seg)
+
+            if len(segs) == 0:
+                continue
+
+            # 判断周期有效性(长度 <= max_hours)
+            hours = (t_end - t_start).total_seconds() / 3600
+            valid = hours <= self.max_hours
+
+            cycles[cycle_id] = {
+                "segments": segs,
+                "start": t_start,
+                "end": t_end,
+                "hours": hours,
+                "valid": valid
+            }
+
+            # 回写到段中
+            for seg in segs:
+                seg["chem_cycle_id"] = cycle_id
+                seg["chem_cycle_valid"] = valid
+
+        return cycles
+
+class ShortTermCycleFoulingFitter:
+    """
+    在一个化学周期内:
+    - 每个 segment 有独立的 R0 和 t
+    - 所有 segment 共享同一个短期污染速率 νK
+    """
+
+    def __init__(self, unit):
+        self.unit = unit
+
+    @staticmethod
+    def _is_invalid(x):
+        return (
+            x is None
+            or np.any(pd.isna(x))
+            or np.any(np.isinf(x))
+            or np.any(np.abs(x) > 1e20)
+        )
+
+    def fit_cycle(self, segments):
+        # ============================
+        # 1. 初步检查
+        # ============================
+        if not segments:
+            return np.nan, np.nan
+
+        R_col = f"{self.unit}_R_scaled"
+        J_col = f"{self.unit}_J"
+
+        # ============================
+        # 2. 预处理每个 segment
+        # ============================
+        seg_data = []
+
+        for seg in segments:
+            if not {"time", R_col, J_col}.issubset(seg.columns):
+                continue
+
+            seg = seg.sort_values("time").drop_duplicates(subset=["time"])
+            if len(seg) < 2:
+                continue
+
+            # 局部时间(秒)
+            try:
+                t = (seg["time"] - seg["time"].iloc[0]).dt.total_seconds().astype(float)
+            except Exception:
+                continue
+
+            # 丢弃 t <= 0
+            mask = t > 0
+            if mask.sum() < 1:
+                continue
+
+            R = seg.loc[mask, R_col].astype(float).values
+            J = seg.loc[mask, J_col].astype(float).values
+            t = t[mask].values
+            R0 = float(seg[R_col].iloc[0])
+
+            # 数值与物理检查
+            if any(self._is_invalid(x) for x in [R, J, t, R0]):
+                continue
+            if np.all(J == 0):
+                continue
+
+            seg_data.append((R, J, t, R0))
+
+        if len(seg_data) == 0:
+            return np.nan, np.nan
+
+        # ============================
+        # 3. 定义 Huber 损失
+        # ============================
+        delta = 1.0
+
+        def huber_loss(res):
+            abs_r = np.abs(res)
+            quad = np.minimum(abs_r, delta)
+            lin = abs_r - quad
+            return 0.5 * quad**2 + delta * lin
+
+        # ============================
+        # 4. 定义 cycle 级 loss(共享 νK)
+        # ============================
+        def loss(nuk):
+            nuk = float(nuk[0])
+            total = 0.0
+
+            for R, J, t, R0 in seg_data:
+                pred = R0 + nuk * J * t
+                res = R - pred
+                total += np.sum(huber_loss(res))
+
+            return total
+
+        # ============================
+        # 5. 优化求解 νK
+        # ============================
+        try:
+            res = minimize(
+                loss,
+                x0=[1e-6],
+                bounds=[(0.0, None)],
+                options={"maxiter": 500, "disp": False}
+            )
+        except Exception:
+            return np.nan, np.nan
+
+        if not res.success:
+            return np.nan, np.nan
+
+        nuk_opt = float(res.x[0])
+
+        # ============================
+        # 6. 计算整体 R²(跨 segment)
+        # ============================
+        R_all = []
+        pred_all = []
+
+        for R, J, t, R0 in seg_data:
+            pred = R0 + nuk_opt * J * t
+            if self._is_invalid(pred):
+                continue
+            R_all.append(R)
+            pred_all.append(pred)
+
+        if not R_all:
+            return nuk_opt, np.nan
+
+        R_all = np.concatenate(R_all)
+        pred_all = np.concatenate(pred_all)
+
+        try:
+            r2 = r2_score(R_all, pred_all)
+        except Exception:
+            r2 = np.nan
+
+        return nuk_opt, r2
+
+
+
+class LongTermFoulingFitter:
+    """
+    长期 fouling 拟合:
+    - 使用每个 segment 起点的“稳健不可逆阻力估计”
+    - 拟合不可逆阻力随 cycle 内累计时间的增长
+    """
+
+    def __init__(self, unit, max_hours=60.0):
+        self.unit = unit
+        self.max_hours = max_hours
+
+    @staticmethod
+    def _power_law(t, a, b, R0):
+        return R0 + a * np.power(t, b)
+
+    @staticmethod
+    def _is_invalid(x):
+        return (
+            x is None
+            or np.any(pd.isna(x))
+            or np.any(np.isinf(x))
+            or np.any(np.abs(x) > 1e20)
+        )
+
+    def _robust_segment_R0(self, seg):
+        """
+        计算单个 segment 的稳健不可逆阻力:
+        1. 优先使用 post_bw_inlet == True 的行
+        2. 否则使用前 10 行
+        """
+        R_col = "R_scaled_start"
+
+        if "post_bw_inlet" in seg.columns:
+            mask = seg["post_bw_inlet"] == True
+            if mask.any():
+                R_vals = seg.loc[mask, R_col].astype(float).values
+                if not self._is_invalid(R_vals):
+                    return float(np.mean(R_vals))
+
+        # fallback:前 10 行
+        R_vals = seg[R_col].iloc[:10].astype(float).values
+        if not self._is_invalid(R_vals):
+            return float(np.mean(R_vals))
+
+        return None
+
+    def fit_cycle(self, segments):
+        # ============================
+        # 1. 空检查
+        # ============================
+        if not segments:
+            return np.nan, np.nan, np.nan
+
+        T_list = []
+        R_list = []
+
+        # cycle 起点
+        try:
+            cycle_start = min(seg["time"].iloc[0] for seg in segments)
+        except Exception:
+            return np.nan, np.nan, np.nan
+
+        R0_cycle = None
+
+        for seg in segments:
+            if "time" not in seg.columns or "R_scaled_start" not in seg.columns:
+                continue
+
+            seg = seg.sort_values("time")
+            if len(seg) < 1:
+                continue
+
+            t0 = seg["time"].iloc[0]
+
+            # ===== 稳健 R0 =====
+            R0 = self._robust_segment_R0(seg)
+            if R0 is None:
+                continue
+
+            # cycle 起点 R0
+            if t0 == cycle_start:
+                R0_cycle = R0
+
+            # 累计时间(小时)
+            T = (t0 - cycle_start).total_seconds() / 3600.0
+            if T < 0 or T > self.max_hours:
+                continue
+
+            if self._is_invalid(T) or self._is_invalid(R0):
+                continue
+
+            T_list.append(T)
+            R_list.append(R0)
+
+        # 至少 3 个点
+        if len(T_list) < 3 or R0_cycle is None:
+            return np.nan, np.nan, np.nan
+
+        T = np.asarray(T_list, dtype=float)
+        R = np.asarray(R_list, dtype=float)
+
+        if self._is_invalid(T) or self._is_invalid(R):
+            return np.nan, np.nan, np.nan
+
+        # ============================
+        # 2. 幂律拟合(b ∈ [0, 3])
+        # ============================
+        try:
+            popt, _ = curve_fit(
+                lambda tt, a, b: self._power_law(tt, a, b, R0_cycle),
+                T,
+                R,
+                p0=(0.1, 1.0),
+                bounds=([0.0, 0.0], [np.inf, 3.0]),
+                maxfev=8000
+            )
+            a, b = popt
+        except Exception:
+            return np.nan, np.nan, np.nan
+
+        if self._is_invalid(a) or self._is_invalid(b):
+            return np.nan, np.nan, np.nan
+
+        # ============================
+        # 3. 拟合质量
+        # ============================
+        pred = self._power_law(T, a, b, R0_cycle)
+        if self._is_invalid(pred):
+            return np.nan, np.nan, np.nan
+
+        try:
+            r2 = r2_score(R, pred)
+        except Exception:
+            r2 = np.nan
+
+        return a, b, r2
+
+
+class ChemicalBackwashCleaner:
+    """
+    根据有效化学周期计算 CEB 去除膜阻力:
+    ΔR = 上周期末 R_end - 下周期初 R_start
+    """
+
+    def __init__(self, unit):
+        self.unit = unit
+
+    def compute_removal(self, cycles):
+        ids = sorted(cycles.keys())
+
+        for i in range(len(ids) - 1):
+            cid1 = ids[i]
+            cid2 = ids[i + 1]
+
+            c1 = cycles[cid1]
+            c2 = cycles[cid2]
+
+            if not (c1["valid"] and c2["valid"]):
+                c1["R_removed"] = None
+                continue
+
+            # 上周期末:最后段的 R_end
+            seg_last = c1["segments"][-1]
+            R_end = seg_last[f"R_scaled_end"]
+
+            # 下周期初:第一段的 R_start
+            seg_first = c2["segments"][0]
+            R_start = seg_first[f"R_scaled_start"]
+
+            c1["R_removed"] = R_end - R_start
+
+        # 最后一个周期无 CEB 去除值
+        if ids:
+            cycles[ids[-1]]["R_removed"] = None
+
+        return cycles

+ 79 - 0
models/uf-rl/训练/uf_data_process/label.py

@@ -0,0 +1,79 @@
+import numpy as np
+import pandas as pd
+
+# =============================
+#     事件识别和划分
+# =============================
+class UFEventClassifier:
+    def __init__(self, unit_name, inlet_codes, physical_code, chemical_code):
+        self.unit = unit_name
+        self.inlet_codes = inlet_codes
+        self.physical_code = physical_code
+        self.chemical_code = chemical_code
+        self.ctrl_col = f"C.M.{unit_name}_DB@word_control"
+
+    def classify(self, df):
+        df = df.copy()
+        df["event_type"] = "other"
+
+        df.loc[df[self.ctrl_col].isin(self.inlet_codes), "event_type"] = "inlet"
+        df.loc[(df[self.ctrl_col] >= self.physical_code - 5) &(df[self.ctrl_col] <= self.physical_code + 5),"event_type"] = "bw_phys"
+        df.loc[(df[self.ctrl_col] >= self.chemical_code - 5) &(df[self.ctrl_col] <= self.chemical_code + 5),"event_type"] = "bw_chem"
+
+        return df
+
+    def segment(self, df):
+        df = df.copy()
+        df["segment_id"] = np.nan
+        seg_id = 0
+        in_inlet = False
+
+        for i, evt in enumerate(df["event_type"]):
+            if evt == "inlet":
+                if not in_inlet:
+                    seg_id += 1
+                    in_inlet = True
+                df.loc[i, "segment_id"] = seg_id
+            else:
+                in_inlet = False
+
+        df = df[df["segment_id"].notna()].copy()
+        df["segment_id"] = df["segment_id"].astype(int)
+        return df
+
+
+class PostBackwashInletMarker:
+    """
+    标记反冲洗事件后的前 N 个进水点
+    """
+
+    def __init__(self, n_points=10):
+        self.n_points = n_points
+        self.label_col = "post_bw_inlet"  # 新标记列
+
+    def mark(self, df: pd.DataFrame) -> pd.DataFrame:
+        df = df.copy()
+
+        # 确保 event_type 清洗干净,避免 object array 卡死
+        df['event_type'] = (
+            df['event_type']
+            .astype(str)
+            .str.strip()
+            .fillna('')
+        )
+
+        df[self.label_col] = False
+
+        # 找出所有反冲洗事件索引
+        bw_idx = df.index[df['event_type'].isin(['bw_phys', 'bw_chem'])]
+
+        # 预先计算 inlet mask,避免多次 object-level 比较
+        inlet_mask = (df['event_type'] == 'inlet')
+
+        for idx in bw_idx:
+            # 只看 idx 之后的 inlet
+            candidate_idx = df.index[(df.index > idx) & inlet_mask]
+            post_idx = candidate_idx[: self.n_points]
+            df.loc[post_idx, self.label_col] = True
+
+        return df

+ 41 - 0
models/uf-rl/训练/uf_data_process/load.py

@@ -0,0 +1,41 @@
+import os
+import pandas as pd
+import yaml
+
+
+# =============================
+#        配置加载器
+# =============================
+class UFConfigLoader:
+    def __init__(self, config_path="config/uf_analyze_config.yaml"):
+        with open(config_path, "r", encoding="utf-8") as f:
+            self.cfg = yaml.safe_load(f)
+
+    @property
+    def uf(self):
+        return self.cfg["UF"]
+
+    @property
+    def params(self):
+        return self.cfg["Params"]
+
+    @property
+    def paths(self):
+        return self.cfg["Paths"]
+
+# =============================
+#        数据加载器
+# =============================
+class UFDataLoader:
+    def __init__(self, data_path: str):
+        self.data_path = data_path
+
+    def load_all_csv(self) -> pd.DataFrame:
+        """读取目录下所有 CSV 并合并成一个 DataFrame"""
+        files = [f for f in os.listdir(self.data_path) if f.endswith(".csv")]
+        dfs = [pd.read_csv(os.path.join(self.data_path, f), parse_dates=["time"]) for f in files]
+        return pd.concat(dfs, ignore_index=True)
+
+    def load_single_csv(self, file_name: str) -> pd.DataFrame:
+        """读取单个 CSV"""
+        return pd.read_csv(os.path.join(self.data_path, file_name), parse_dates=["time"])

+ 374 - 0
models/uf-rl/训练/uf_data_process/pipeline.py

@@ -0,0 +1,374 @@
+import os
+from typing import List, Dict
+
+import numpy as np
+import pandas as pd
+from pathlib import Path
+from load import UFConfigLoader, UFDataLoader
+from label import UFEventClassifier, PostBackwashInletMarker
+from filter import ConstantFlowFilter, EventQualityFilter, InletSegmentFilter
+from calculate import UFResistanceCalculator, VariableStabilityAnalyzer, UFResistanceAnalyzer, UFPressureAnalyzer
+from fit import ChemicalCycleSegmenter, ChemicalBackwashCleaner, ShortTermCycleFoulingFitter,LongTermFoulingFitter
+
+class UFAnalysisPipeline:
+    """
+    Pipeline:
+    - 支持四个机组逐机组标记事件并分段
+    - 过滤与稳定段提取
+    - 每段逐行计算膜阻力,并对多变量做稳定性分析
+    - 仅在 CEB 前后最近段均为有效稳定进水段时计算去除阻力
+    - 生成每段汇总表并保存
+    """
+
+    def __init__(self, cfg: 'UFConfigLoader'):
+        self.cfg = cfg
+        uf_cfg = cfg.uf
+        params = cfg.params
+        paths = cfg.paths
+
+        # 机组列表(优先从配置读取)
+        self.units = uf_cfg.get("units", ["UF1", "UF2", "UF3", "UF4"])
+        self.stable_inlet_code = uf_cfg.get("stable_inlet_code", "26.0")
+
+        project_root = Path(paths["project_root"])
+        raw_data_path = project_root / paths["raw_data_path"]
+        output_path = project_root / paths["output_path"]
+        filter_output_path = project_root / paths["filter_output_path"]
+        self.loader = UFDataLoader(raw_data_path)
+        self.output_dir = output_path
+        self.filter_output_dir = filter_output_path
+        output_path.mkdir(parents=True, exist_ok=True)
+
+        # 过滤器
+        self.min_points = params.get("min_points", 40)
+        self.initial_points = params.get("initial_points", 10)
+
+        self.quality_filter = EventQualityFilter(min_points=self.min_points)
+        self.initial_label = PostBackwashInletMarker(n_points=self.initial_points)
+
+        # 阻力计算器
+        self.res_calc = UFResistanceCalculator(self.units, area_m2=uf_cfg["area_m2"], scale_factor=params.get("scale_factor", 1e10))
+        self.segment_head_n = params.get("segment_head_n", 10)
+        self.segment_tail_n = params.get("segment_tail_n", 10)
+
+        # 需要检查的常量列名(除流量外)
+        self.temp_col = uf_cfg.get("temp_col", "C.M.RO_TT_ZJS@out")
+        self.orp_col = uf_cfg.get("orp_col", "C.M.UF_ORP_ZCS@out")
+        self.cond_col = uf_cfg.get("cond_col", "C.M.RO_Cond_ZJS@out")
+
+    # ----------------------------
+    # 加载所有 CSV 并合并
+    # ----------------------------
+
+    def load_all(self) -> pd.DataFrame:
+        print(f"[INFO] Loading raw data from: {self.loader.data_path}")
+        df = self.loader.load_all_csv()
+
+        if df.empty:
+            print("[WARN] No CSV files found!")
+        return df
+
+    # ----------------------------
+    # 多变量稳定性检查(针对单个 segment)
+    # 返回每个变量的稳定性 bool 与总体稳定 bool(所有变量均稳定)
+    # ----------------------------
+    def analyze_variables_stability(self, seg: pd.DataFrame, flow_col: str) -> Dict[str, bool]:
+        cols = {
+            "flow": flow_col,
+            "temp": self.temp_col,
+            "orp": self.orp_col,
+            "cond": self.cond_col
+        }
+        results = {}
+        for name, col in cols.items():
+            if col not in seg.columns:
+                results[name] = False
+                continue
+            series = seg[col].dropna()
+            if len(series) < 2:
+                results[name] = False
+                continue
+            std = float(series.std())
+            x = np.arange(len(series))
+            try:
+                slope = float(np.polyfit(x, series.values, 1)[0])
+            except Exception:
+                slope = np.inf
+            results[name] = (std <= self.var_max_std) and (abs(slope) <= self.var_max_slope)
+        results["all_stable"] = all(results[k] for k in ["flow", "temp", "orp", "cond"])
+        return results
+
+    # ----------------------------
+    # 从 stable_segments 列表计算每段的 R_start/R_end(按机组)
+    # 返回 seg_summary DataFrame(每行对应一个 stable inlet segment)
+    # ----------------------------
+    def summarize_R_for_unit(self, stable_segments: List[pd.DataFrame], unit: str) -> pd.DataFrame:
+        rows = []
+        res_col = f"{unit}_R"
+        for seg in stable_segments:
+            seg = seg.sort_values("time").reset_index(drop=True)
+            if len(seg) < (self.segment_head_n + self.segment_tail_n):
+                # 跳过过短的
+                continue
+            R_start = seg[res_col].iloc[:self.segment_head_n].median()
+            R_end = seg[res_col].iloc[-self.segment_tail_n:].median()
+            rows.append({
+                "unit": unit,
+                "segment_id": int(seg["segment_id"].iloc[0]),
+                "start_time": seg["time"].iloc[0],
+                "end_time": seg["time"].iloc[-1],
+                "R_start": float(R_start),
+                "R_end": float(R_end),
+                "n_points": len(seg)
+            })
+        return pd.DataFrame(rows)
+
+    # ----------------------------
+    # 主运行逻辑
+    # ----------------------------
+    def run(self):
+        df = self.load_all()
+        if df.empty:
+            raise ValueError("未找到原始数据或数据为空")
+
+        df = df.sort_values("time").reset_index(drop=True)
+        df = df.ffill().bfill()
+        cols_to_convert = df.columns.difference(["time"])
+        df[cols_to_convert] = df[cols_to_convert].apply(pd.to_numeric, errors='coerce')
+        df[cols_to_convert] = df[cols_to_convert].ffill().bfill()
+
+        # 为安全:确保 time 列为 datetime
+        if not np.issubdtype(df["time"].dtype, np.datetime64):
+            df["time"] = pd.to_datetime(df["time"])
+
+        # 逐机组处理
+        for unit in self.units:
+            print(f"Processing {unit} ...")
+            ctrl_col = f"C.M.{unit}_DB@word_control"
+            flow_col = f"C.M.{unit}_FT_JS@out"
+
+            # 去除无关列
+            other_units = [u for u in self.units if u != unit]
+            cols_to_drop = [col for col in df.columns if any(ou in col for ou in other_units)]
+            df_unit = df.drop(columns=cols_to_drop)
+
+            # 逐机组事件识别:使用 UFEventClassifier(实例化单机组)
+            event_clf = UFEventClassifier(unit, self.cfg.uf["inlet_codes"],
+                                          self.cfg.uf["physical_bw_code"], self.cfg.uf["chemical_bw_code"])
+            df_unit = event_clf.classify(df_unit)  # 产生 event_type 列
+            df_unit_mark = self.initial_label.mark(df_unit)
+            seg_df = event_clf.segment(df_unit_mark)
+
+            # 对 seg_df 进行按 segment 分组后逐段过滤:
+            const_flow_filter = ConstantFlowFilter(flow_col=flow_col, repeat_len=20)
+            segments = const_flow_filter.filter(seg_df) # 去除出现网络错误的进水段
+            segments = self.quality_filter.filter(segments) # 去除时间过短的进水段
+            stable_extractor = InletSegmentFilter(ctrl_col,
+                                                  stable_value=self.stable_inlet_code,
+                                                  min_points=self.min_points)
+
+            stable_segments = stable_extractor.extract(segments)  # 提取稳定进水数据
+
+            # 若无稳定进水段,则记录不稳定段并跳过该机组
+            if len(stable_segments) == 0:
+                print(f"  No stable segments found for {unit}")
+                continue
+
+            # -----------------------------
+            # 逐段计算膜阻力
+            # -----------------------------
+            stable_segments = self.res_calc.calculate_for_segments(
+                stable_segments,
+                temp_col=self.temp_col,
+                flow_col=flow_col
+            )
+
+            # -----------------------------
+            # 逐段计算 起止跨膜压差tmp_start/tmp_end 放缩后的R_start/R_end 进水变量稳定性
+            # -----------------------------
+
+            # 变量稳定性分析
+            vsa = VariableStabilityAnalyzer()
+            stable_segments = vsa.analyze_segments(stable_segments, col=flow_col, prefix="flow")
+            stable_segments = vsa.analyze_segments(stable_segments, col=self.temp_col, prefix="temp")
+
+            # 跨膜压差统计
+            press_col = f"C.M.{unit}_DB@press_PV"
+            upa = UFPressureAnalyzer(
+                press_col=press_col,
+                head_n=self.segment_head_n,
+                tail_n=self.segment_tail_n,
+            )
+
+            stable_segments = upa.analyze_segments(stable_segments)
+
+            # 膜阻力统计
+            res_scaled_col = f"{unit}_R_scaled"
+            ura = UFResistanceAnalyzer(
+                resistance_col=res_scaled_col,
+                head_n=self.segment_head_n,
+                tail_n=self.segment_tail_n
+            )
+            stable_segments = ura.analyze_segments(stable_segments)
+
+            # ----------------------------------
+            # 1. 化学周期划分
+            # ----------------------------------
+            cycle_segmenter = ChemicalCycleSegmenter(max_hours=60)
+            cycles = cycle_segmenter.assign_cycles(df_unit, stable_segments)
+
+            # ----------------------------------
+            # 2. 每个周期拟合短期污染 nuK
+            # ----------------------------------
+            stf = ShortTermCycleFoulingFitter(unit)
+            for cid, cycle in cycles.items():
+                if not cycle["valid"]:
+                    cycle["nuk"] = None
+                    cycle["nuk_r2"] = None
+                    continue
+
+                nuk, r2 = stf.fit_cycle(cycle["segments"])
+                cycle["nuk"] = nuk
+                cycle["nuk_r2"] = r2
+
+                for seg in cycle["segments"]:
+                    seg["cycle_nuK"] = nuk
+                    seg["cycle_nuK_R2"] = r2
+
+            # ----------------------------------
+            # 3. 长期污染模型幂律拟合
+            # ----------------------------------
+            ltf = LongTermFoulingFitter(unit)
+            for cid, cycle in cycles.items():
+                if not cycle["valid"]:
+                    cycle["a"] = None
+                    cycle["b"] = None
+                    cycle["lt_r2"] = None
+                    continue
+
+                a, b, r2 = ltf.fit_cycle(cycle["segments"])
+                cycle["a"] = a
+                cycle["b"] = b
+                cycle["lt_r2"] = r2
+
+                for seg in cycle["segments"]:
+                    seg["cycle_long_a"] = a
+                    seg["cycle_long_b"] = b
+                    seg["cycle_long_r2"] = r2
+
+            # ----------------------------------
+            # 4. 计算化学反冲洗去除膜阻力
+            # ----------------------------------
+            cbc = ChemicalBackwashCleaner(unit)
+            cycles = cbc.compute_removal(cycles)
+
+            # 回写到稳定段
+            for cid, cycle in cycles.items():
+                R_removed = cycle["R_removed"]
+                for seg in cycle["segments"]:
+                    seg["cycle_R_removed"] = R_removed
+
+            # -----------------------------
+            # 记录所有稳定进水段信息(stable_segments 仅包含稳定段)
+            # -----------------------------
+            all_inlet_segment_rows = []
+
+            for seg in stable_segments:  # seg 是一个 DataFrame
+                seg_id = int(seg["segment_id"].iloc[0])
+
+                row = {
+                    "seg_id": seg_id,
+                    "start_time": seg["time"].iloc[0],
+                    "end_time": seg["time"].iloc[-1],
+                }
+
+                # 添加跨膜压差、膜阻力与变量波动性分析列
+                row.update({
+                    "tmp_start": seg["tmp_start"].iloc[0] if "tmp_start" in seg.columns else None,
+                    "tmp_end": seg["tmp_end"].iloc[-1] if "tmp_end" in seg.columns else None,
+
+                    "R_scaled_start": seg["R_scaled_start"].iloc[0] if "R_scaled_start" in seg.columns else None,
+                    "R_scaled_end": seg["R_scaled_end"].iloc[-1] if "R_scaled_end" in seg.columns else None,
+
+                    "flow_mean": seg["flow_mean"].iloc[0] if "flow_mean" in seg.columns else None,
+                    "flow_std": seg["flow_std"].iloc[0] if "flow_std" in seg.columns else None,
+                    "flow_cv": seg["flow_cv"].iloc[0] if "flow_cv" in seg.columns else None,
+
+                    "temp_mean": seg["temp_mean"].iloc[0] if "temp_mean" in seg.columns else None,
+                    "temp_std": seg["temp_std"].iloc[0] if "temp_std" in seg.columns else None,
+                    "temp_cv": seg["temp_cv"].iloc[0] if "temp_cv" in seg.columns else None,
+                })
+
+                # === 将化学周期字段写入 row ===
+                chem_cols = [col for col in seg.columns if col.startswith("chem_cycle_")]
+                for col in chem_cols:
+                    row[col] = seg[col].iloc[0]
+
+                # === 将 nuK、长期污染参数、CEB 去除阻力写入 row ===
+                cycle_cols = [col for col in seg.columns if col.startswith("cycle_")]
+                for col in cycle_cols:
+                    row[col] = seg[col].iloc[0]
+
+                all_inlet_segment_rows.append(row)
+
+            # -----------------------------
+            # 最终构建 DataFrame
+            # -----------------------------
+            df_segments = pd.DataFrame(all_inlet_segment_rows)
+
+            # 输出文件名:unit_xxx_segments.csv
+            filename = f"{unit}_segments.csv"
+            output_path = os.path.join(self.output_dir, filename)
+
+            df_segments.to_csv(output_path, index=False)
+            print(f"Saved inlet segment data to: {output_path}")
+
+            # ======================================================
+            # 扩展功能:化学周期过滤与导出(基于 chem_cycle_id)
+            # ======================================================
+
+            print(f">>> Post-filtering chemical cycles for unit {unit} ...")
+
+            # 1. 仅保留有化学周期标识的段
+            if "chem_cycle_id" not in df_segments.columns:
+                print(f"No chem_cycle_id found for unit {unit}, skipping cycle filtering.")
+            else:
+                df_valid = df_segments.dropna(subset=["chem_cycle_id"]).copy()
+                df_valid["chem_cycle_id"] = df_valid["chem_cycle_id"].astype(int)
+
+                # 2. 根据 R2 过滤化学周期
+                grouped = df_valid.groupby("chem_cycle_id")
+
+                valid_cycle_ids = []
+                for cid, g in grouped:
+                    # 同一化学周期的 R2 相同(周期级拟合),用第一行即可
+                    nuk_r2 = g["cycle_nuK_R2"].iloc[0] if "cycle_nuK_R2" in g.columns else None
+                    long_r2 = g["cycle_long_r2"].iloc[0] if "cycle_long_r2" in g.columns else None
+
+                    if nuk_r2 is not None and long_r2 is not None:
+                        if nuk_r2 > 0.5 and long_r2 > 0.5:
+                            valid_cycle_ids.append(cid)
+
+                # 3. 过滤后的化学周期数据
+                df_cycles_kept = df_valid[df_valid["chem_cycle_id"].isin(valid_cycle_ids)]
+
+                # 4. 保存过滤结果
+                cycle_filename = f"{unit}_filtered_cycles.csv"
+                cycle_output_path = os.path.join(self.filter_output_dir, cycle_filename)
+
+                df_cycles_kept.to_csv(cycle_output_path, index=False)
+                print(f"Saved filtered chemical cycles to: {cycle_output_path}")
+
+                # 5. 统计保留行占比
+                total_rows = len(df_valid)
+                kept_rows = len(df_cycles_kept)
+                pct = kept_rows / total_rows * 100 if total_rows > 0 else 0
+
+                print(
+                    f"Unit {unit}: kept {kept_rows}/{total_rows} rows "
+                    f"({pct:.2f} percent) after cycle R2 filtering."
+                )
+
+
+
+

+ 32 - 0
models/uf-rl/训练/uf_data_process/run_ufdata_pipeline.py

@@ -0,0 +1,32 @@
+import os
+import sys
+
+SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
+
+from load import UFConfigLoader
+from pipeline import UFAnalysisPipeline
+
+
+def main():
+    print("=====================================")
+    print("    UF Multi-Unit Analysis Pipeline")
+    print("=====================================")
+
+    # 1. 加载配置文件
+    config_path = os.path.join(SCRIPT_DIR, "uf_analyze_config.yaml")
+    cfg = UFConfigLoader(config_path)
+
+    # 2. 创建 pipeline
+    pipeline = UFAnalysisPipeline(cfg)
+
+    # 3. 执行 pipeline
+    print(">>> Running pipeline ...")
+    results = pipeline.run()
+
+    print("=========================================")
+    print("Pipeline finished. Results saved.")
+    print("=========================================")
+
+
+if __name__ == "__main__":
+    main()

+ 500 - 0
models/uf-rl/训练/uf_train/README.md

@@ -0,0 +1,500 @@
+# UF超滤系统强化学习决策模型训练逻辑说明
+
+## 模型概述
+
+这是一个基于**深度强化学习(DQN)**的超滤系统运行参数优化模型。不同于前两个"预测模型",这个模型的目标是**决策**:在给定当前跨膜压差(TMP)的情况下,自动决定最优的产水时长和反洗时长。
+
+**核心问题**:如何平衡产水量、回收率、能耗和膜寿命?
+
+## 问题背景
+
+### 超滤运行周期
+
+超滤系统运行遵循"小周期"模式:
+```
+[产水L秒] → [反洗t_bw秒] → [产水L秒] → [反洗t_bw秒] → ... → [化学清洗CEB]
+```
+
+- **产水阶段**:过滤原水,TMP逐渐升高(膜污染)
+- **反洗阶段**:反向冲洗,TMP部分恢复
+- **化学清洗(CEB)**:每48小时一次,TMP完全恢复
+
+### 决策难题
+
+**调节杠杆**:
+- `L_s`:单次产水时长(3600-6000秒)
+- `t_bw_s`:单次反洗时长(40-60秒)
+
+**矛盾目标**:
+1. **产水量↑**:希望L_s长、t_bw_s短(多产水、少反洗)
+2. **回收率↑**:希望t_bw_s短(减少反洗水耗)
+3. **膜保护↓**:希望L_s短、t_bw_s长(频繁反洗、TMP不升太高)
+4. **能耗↓**:产水时间越长,单位吨水的泵能耗越低
+
+**传统方法**:人工经验+固定参数,难以在复杂约束下找到最优解  
+**强化学习方法**:让AI自己探索,学习在不同TMP下的最佳决策
+
+## 核心思路:强化学习框架
+
+### 1. 强化学习是什么?
+
+把决策问题想象成玩游戏:
+```
+游戏状态(TMP)→ AI选择动作(L_s, t_bw_s)→ 执行动作 → 获得奖励(回收率、净供水率)→ 新状态(TMP更新)
+```
+
+AI通过**反复试错**,学习哪些动作能获得高奖励。
+
+### 2. Markov决策过程(MDP)建模
+
+#### 状态(State)
+```python
+state = [
+    TMP0_normalized,           # 当前初始TMP(归一化到0-1)
+    last_L_s_normalized,       # 上一次产水时长(归一化)
+    last_t_bw_s_normalized,    # 上一次反洗时长(归一化)
+    max_TMP_normalized         # 本周期最高TMP(归一化)
+]
+```
+**4维状态向量**描述当前系统状态
+
+#### 动作(Action)
+```python
+# 离散动作空间:L_s × t_bw_s的网格
+L_s范围:3800-6000秒,步长60秒 → 37个选项
+t_bw_s范围:40-60秒,步长5秒 → 5个选项
+
+总动作数 = 37 × 5 = 185个
+```
+
+每个动作对应一个`(L_s, t_bw_s)`组合
+
+#### 奖励(Reward)
+```python
+# 多目标加权奖励
+reward = 0.8 × recovery           # 回收率(主要目标)
+       + 0.2 × rate_normalized    # 净供水率
+       - 0.2 × headroom_penalty   # TMP贴边惩罚
+```
+
+**奖励设计原则**:
+- 高回收率 → 高奖励
+- 高净供水率 → 高奖励
+- TMP接近上限 → 负奖励(膜风险)
+- 违反约束 → 大负奖励(-20)
+
+#### 状态转移
+```python
+# 模拟器:根据物理模型计算下一个状态
+def simulate_one_supercycle(TMP0, L_s, t_bw_s):
+    # 1. 计算产水阶段TMP上升
+    delta_TMP = model_fp(L_s)  # 调用TMP增长模型
+    TMP_peak = TMP0 + delta_TMP
+    
+    # 2. 计算反洗恢复
+    phi = model_bw(L_s, t_bw_s)  # 调用反洗恢复模型
+    TMP_after_bw = TMP_peak - phi × (TMP_peak - TMP0)
+    
+    # 3. 多次小周期后CEB
+    TMP_new = TMP0  # 化学清洗后完全恢复
+    
+    # 4. 计算指标
+    recovery = (产水 - 反洗水耗 - CEB水耗) / 产水
+    net_rate = 净产水 / 总时间
+    
+    return TMP_new, recovery, net_rate, ...
+```
+
+## DQN算法详解
+
+### 什么是DQN?
+
+**Deep Q-Network(深度Q网络)**:
+- 用神经网络估计**Q值函数**:`Q(state, action) = 预期累积奖励`
+- 最优策略:在每个状态选择Q值最大的动作
+
+```
+状态 → [神经网络] → 每个动作的Q值 → 选择最大Q值的动作
+```
+
+### 神经网络结构
+
+```python
+# Stable-Baselines3的MlpPolicy默认结构
+输入层:4维状态
+隐藏层1:64神经元 + ReLU
+隐藏层2:64神经元 + ReLU
+输出层:185个动作的Q值
+```
+
+### 训练流程(`DQN_train.py`)
+
+#### 1. 经验回放(Experience Replay)
+```python
+buffer_size = 10000  # 存储10000条经验
+
+# 交互过程
+for step in range(total_timesteps):
+    action = model.select_action(state)        # ε-贪心选择动作
+    next_state, reward = env.step(action)      # 执行动作
+    buffer.store(state, action, reward, next_state)  # 存入缓冲区
+    
+    # 从缓冲区随机采样训练
+    if step > learning_starts:
+        batch = buffer.sample(batch_size=32)
+        model.train_on_batch(batch)
+```
+
+**为什么需要经验回放?**
+- 打破数据相关性(连续状态往往相似)
+- 提高样本利用效率(同一条经验可多次使用)
+
+#### 2. ε-贪心探索
+```python
+# 随机探索 vs 利用已学知识
+if random() < epsilon:
+    action = random_action()   # 探索:随机选
+else:
+    action = argmax(Q(state))  # 利用:选Q值最大的
+
+# epsilon从1.0衰减到0.02
+epsilon = 1.0 → 0.8 → ... → 0.02
+```
+
+**探索-利用权衡**:
+- 初期多探索(发现好动作)
+- 后期多利用(稳定在最优策略)
+
+#### 3. 目标网络(Target Network)
+```python
+# 两个网络:当前网络 + 目标网络
+Q_current(state, action)  # 每步更新
+Q_target(next_state, a')   # 每2000步同步一次
+
+# TD误差
+loss = MSE(Q_current(s,a), reward + γ × max(Q_target(s', a')))
+```
+
+**为什么需要目标网络?**
+- 稳定训练(避免"追逐移动目标"问题)
+- 减少Q值估计的震荡
+
+#### 4. 训练超参数
+
+```python
+class DQNParams:
+    learning_rate = 1e-4          # 学习率
+    buffer_size = 10000           # 经验池大小
+    learning_starts = 200         # 200步后开始学习
+    batch_size = 32               # 每次训练32个样本
+    gamma = 0.95                  # 折扣因子(重视长期奖励)
+    train_freq = 4                # 每4步训练一次
+    target_update_interval = 2000 # 每2000步更新目标网络
+    exploration_fraction = 0.3    # 前30%训练时间用于探索
+    exploration_final_eps = 0.02  # 最终保留2%探索
+```
+
+## 模拟环境(`DQN_env.py`)
+
+### UFSuperCycleEnv类
+
+```python
+class UFSuperCycleEnv(gym.Env):
+    def reset(self):
+        # 重置环境:随机初始TMP
+        self.TMP0 = random.uniform(0.01, 0.03)
+        return self._get_obs()
+    
+    def step(self, action):
+        # 执行动作
+        L_s, t_bw_s = self._decode_action(action)
+        
+        # 调用模拟器
+        feasible, info = simulate_one_supercycle(self.TMP0, L_s, t_bw_s)
+        
+        if feasible:
+            reward = _score(info)  # 计算奖励
+            self.TMP0 = info["TMP_after_ceb"]  # 更新TMP
+            done = False
+        else:
+            reward = -20  # 违反约束,大负奖励
+            done = True   # episode终止
+        
+        return next_state, reward, done, info
+```
+
+### 约束检查
+
+```python
+# 硬约束1:TMP峰值不得超过0.06 MPa
+if TMP_peak > 0.06:
+    return False
+
+# 硬约束2:单次残余增量不得超过0.001 MPa
+if (TMP_after_bw - TMP0) > 0.001:
+    return False
+
+# 硬约束3:TMP不得超过上限的98%
+if TMP_peak / TMP_max > 0.98:
+    return False
+```
+
+### 物理模型集成
+
+```python
+# TMP增长模型(uf_fp.pth)
+def _delta_tmp(L_h):
+    return model_fp(params, L_h)  # 产水时长 → TMP增量
+
+# 反洗恢复模型(uf_bw.pth)
+def phi_bw_of(L_s, t_bw_s):
+    return model_bw(params, L_s, t_bw_s)  # (产水时长, 反洗时长) → 恢复比例
+```
+
+这两个模型是基于数据拟合或物理建模得到的。
+
+## 决策使用(`DQN_decide.py`)
+
+### 单步决策接口
+
+```python
+def run_uf_DQN_decide(uf_params, TMP0_value):
+    # 1. 创建环境
+    env = UFSuperCycleEnv(uf_params)
+    env.current_params.TMP0 = TMP0_value  # 设置当前TMP
+    
+    # 2. 加载训练好的模型
+    model = DQN.load("dqn_model.zip")
+    
+    # 3. 预测动作(确定性,不探索)
+    action, _ = model.predict(state, deterministic=True)
+    
+    # 4. 解码动作
+    L_s, t_bw_s = decode_action(action)
+    
+    return {
+        "action": action,
+        "L_s": L_s,
+        "t_bw_s": t_bw_s,
+        "expected_recovery": info["recovery"],
+        ...
+    }
+```
+
+### PLC指令生成
+
+为了避免频繁大幅调整(工艺稳定性),使用**渐进式调整**:
+
+```python
+def generate_plc_instructions(current, model_prev, model_current):
+    # 计算差异
+    diff = model_current - effective_current
+    
+    # 渐进调整:每次只调整一个步长
+    if abs(diff) >= threshold:
+        adjustment = +step_size if diff > 0 else -step_size
+    else:
+        adjustment = 0
+    
+    next_value = effective_current + adjustment
+    return next_value
+```
+
+**示例**:
+```
+当前L_s = 4000秒
+模型建议 = 4300秒
+步长 = 60秒
+
+第1轮下发:4060秒(+60)
+第2轮下发:4120秒(+60)
+...
+第5轮下发:4300秒(到达目标)
+```
+
+## 性能指标计算(`DQN_decide.py`)
+
+```python
+def calc_uf_cycle_metrics(TMP0, L_s, t_bw_s):
+    # 模拟一个超级周期
+    feasible, info = simulate_one_supercycle(params, L_s, t_bw_s)
+    
+    return {
+        "k_bw_per_ceb": 小周期次数,
+        "recovery": 回收率,
+        "net_delivery_rate_m3ph": 净供水率(m³/h),
+        "daily_prod_time_h": 日均产水时间(h/天),
+        "ton_water_energy_kWh_per_m3": 吨水电耗(kWh/m³),
+        "max_permeability": 最高渗透率(lmh/bar)
+    }
+```
+
+## 文件结构说明
+
+```
+uf-rl/
+├── DQN_train.py         # 强化学习训练脚本(DQN算法)
+├── DQN_env.py           # 模拟环境(MDP定义、物理模拟)
+├── DQN_decide.py        # 决策接口(加载模型、生成指令)
+├── UF_decide.py         # 传统优化方法(网格搜索,用于对比)
+├── UF_models.py         # 物理模型定义(TMP增长、反洗恢复)
+├── uf_fp.pth            # TMP增长模型权重
+├── uf_bw.pth            # 反洗恢复模型权重
+└── dqn_model.zip        # 训练好的DQN模型
+```
+
+## 训练流程总结
+
+```mermaid
+graph LR
+    A[初始化环境] --> B[随机初始TMP]
+    B --> C{ε-贪心选择动作}
+    C -->|探索| D[随机动作]
+    C -->|利用| E[Q值最大动作]
+    D --> F[模拟执行]
+    E --> F
+    F --> G{约束检查}
+    G -->|可行| H[计算奖励]
+    G -->|不可行| I[负奖励-20]
+    H --> J[存入经验池]
+    I --> J
+    J --> K{达到学习步数?}
+    K -->|是| L[采样训练]
+    K -->|否| M[继续交互]
+    L --> N{episode结束?}
+    M --> N
+    N -->|否| C
+    N -->|是| B
+```
+
+## 与传统方法对比
+
+### 传统网格搜索(`UF_decide.py`)
+
+```python
+# 穷举所有(L_s, t_bw_s)组合
+for L_s in [3600, 3660, ..., 4200]:
+    for t_bw_s in [90, 92, ..., 100]:
+        feasible, metrics = simulate(L_s, t_bw_s)
+        if feasible and score > best_score:
+            best = (L_s, t_bw_s)
+```
+
+**优点**:简单、可解释、保证找到网格上的最优解  
+**缺点**:
+- 计算量大(数百次模拟)
+- 参数空间离散化(可能错过真正最优点)
+- 无法泛化(每个TMP都要重新搜索)
+
+### 强化学习(DQN)
+
+**优点**:
+- 训练后推理快(一次前向传播)
+- 能泛化到不同TMP(学到状态-动作映射)
+- 可处理更复杂的状态(如历史趋势)
+
+**缺点**:
+- 训练耗时(需要大量交互)
+- 黑盒性(难以解释为何选择某动作)
+- 性能受模拟器精度影响
+
+## 训练建议
+
+### 提升策略性能
+
+1. **改进奖励设计**:
+   ```python
+   # 添加渗透率奖励
+   reward += 0.1 × permeability
+   
+   # 添加稳定性奖励(动作变化小)
+   reward -= 0.05 × |action - last_action|
+   ```
+
+2. **增加状态信息**:
+   ```python
+   state = [
+       TMP0, last_L, last_t_bw, max_TMP,
+       water_quality,  # 水质指标
+       days_since_ceb, # 距上次CEB天数
+       ...
+   ]
+   ```
+
+3. **课程学习(Curriculum Learning)**:
+   ```python
+   # 阶段1:简单场景(TMP变化小)
+   env.TMP_range = [0.025, 0.035]
+   train(10000 steps)
+   
+   # 阶段2:中等场景
+   env.TMP_range = [0.01, 0.04]
+   train(20000 steps)
+   
+   # 阶段3:困难场景(全范围)
+   env.TMP_range = [0.01, 0.05]
+   train(20000 steps)
+   ```
+
+### 加速训练
+
+```python
+# 1. 减少训练步数
+total_timesteps = 10000  # 从50000降到10000
+
+# 2. 增大batch_size(如果内存足够)
+batch_size = 64
+
+# 3. 调高learning_rate(小心不稳定)
+learning_rate = 5e-4
+
+# 4. 预训练:从传统方法生成初始数据
+buffer.load_from_grid_search()
+```
+
+## 常见问题
+
+**Q:为什么用强化学习而不是监督学习?**  
+A:监督学习需要"正确答案"标签,但这里没有标准答案(最优策略本身就是要学习的)。强化学习通过奖励信号自己探索最优策略。
+
+**Q:模拟器不准确怎么办?**  
+A:这是强化学习最大风险。解决方法:
+- 用真实数据校准模拟器
+- Sim-to-Real迁移(在真实系统上微调)
+- 保守策略(加大安全裕度)
+
+**Q:能否用于在线学习?**  
+A:可以,但需谨慎:
+- 设置安全约束(避免危险动作)
+- 分阶段部署(先离线验证)
+- 人工监督(关键决策需人工确认)
+
+**Q:为什么动作空间是离散的?**  
+A:DQN擅长离散动作(每个动作一个Q值)。如果需要连续动作,可用DDPG、SAC等算法。
+
+**Q:如何评估策略好坏?**  
+A:
+- 离线:在验证集上计算平均回收率、净供水率
+- 在线:实际运行后对比历史数据
+- 对比基线:与传统固定参数、网格搜索比较
+
+## 未来优化方向
+
+1. **多智能体协同**:多个UF模组联合优化
+2. **分层强化学习**:高层决策策略,低层决策参数
+3. **模型预测控制(MPC)集成**:结合物理模型和学习策略
+4. **安全强化学习**:硬约束保证(Safety RL)
+5. **离线强化学习**:仅用历史数据训练(Offline RL)
+
+## 总结
+
+UF-RL模型是一个**决策优化系统**,通过深度强化学习学习在不同跨膜压差下的最优运行策略。相比传统方法:
+- **更智能**:能适应不同状态,无需人工调参
+- **更高效**:训练后推理快速
+- **更全面**:平衡多个矛盾目标
+
+但同时也需要:
+- **准确的模拟器**:保证学到的策略有效
+- **充分的训练**:探索足够多的状态-动作组合
+- **谨慎的部署**:实际应用前充分验证
+

+ 1076 - 0
models/uf-rl/训练/uf_train/UF_RL_架构问题与优化方案.md

@@ -0,0 +1,1076 @@
+# UF-RL 架构问题分析与优化方案
+
+## 目录
+1. [架构层面问题](#架构层面问题)
+2. [代码实现问题](#代码实现问题)
+3. [算法设计问题](#算法设计问题)
+4. [工程质量问题](#工程质量问题)
+5. [优化方案](#优化方案)
+6. [重构建议](#重构建议)
+
+---
+
+## 架构层面问题
+
+### 问题1:物理模型是伪神经网络 ⚠️⚠️⚠️
+
+**位置**:`UF_models.py`
+
+**问题描述**:
+```python
+class TMPIncreaseModel(torch.nn.Module):
+    def forward(self, p, L_h):
+        # 这不是神经网络,只是数学公式!
+        return float(p.alpha * (p.q_UF ** p.belta) * L_h)
+```
+
+**问题分析**:
+1. **没有可训练参数**:继承`nn.Module`但没有定义任何`nn.Parameter`
+2. **保存.pth无意义**:`state_dict()`是空字典
+3. **完全基于人工公式**:没有从数据中学习
+
+**验证问题**:
+```python
+model = TMPIncreaseModel()
+print(model.state_dict())  # 输出:OrderedDict()
+print(list(model.parameters()))  # 输出:[]
+```
+
+**影响**:
+- 模拟器精度完全取决于人工公式的准确性
+- 无法利用真实运行数据改进模型
+- 存在**Sim-to-Real Gap**(模拟与真实的差异)
+
+**评级**:🔴 严重问题
+
+---
+
+### 问题2:状态空间信息不足 ⚠️⚠️
+
+**位置**:`DQN_env.py` - `_get_obs()`
+
+**当前状态**:
+```python
+state = [
+    TMP0_norm,      # 当前TMP
+    L_norm,         # 上次产水时长
+    t_bw_norm,      # 上次反洗时长
+    max_TMP_norm    # 周期最高TMP
+]  # 仅4维
+```
+
+**缺失信息**:
+1. **水质特征**:浊度、COD、温度、pH等
+2. **历史趋势**:TMP变化速率、污染累积趋势
+3. **时间信息**:自上次CEB的时间、季节性
+4. **膜状态**:膜龄、历史清洗次数、累积运行时间
+5. **运行模式**:当前流量、压力、回收率
+
+**后果**:
+- 智能体难以学习长期策略
+- 无法适应不同水质条件
+- 泛化能力弱
+
+**评级**:🟡 中等问题
+
+---
+
+### 问题3:奖励函数设计不合理 ⚠️⚠️
+
+**位置**:`DQN_env.py` - `_score()`
+
+**问题代码**:
+```python
+def _score(p, rec):
+    base_reward = 0.8 × recovery + 0.2 × rate_norm - 0.2 × headroom_penalty
+    # 基础奖励范围:0.6 ~ 0.9
+    
+    # 非线性放大
+    amplified = (base_reward - 0.5) ** 2 * 5.0
+    if base_reward < 0.5:
+        amplified = -amplified
+    
+    return amplified
+```
+
+**问题1:非线性变换过于激进**
+
+奖励映射示例:
+| base_reward | amplified | 倍数变化 |
+|-------------|-----------|---------|
+| 0.85 | 0.613 | - |
+| 0.80 | 0.450 | ↓36% |
+| 0.75 | 0.313 | ↓31% |
+| 0.70 | 0.200 | ↓36% |
+
+**后果**:
+- Q值估计困难(奖励尺度不一致)
+- 梯度不稳定
+- 可能导致训练震荡
+
+**问题2:约束违反惩罚不合理**
+
+```python
+if not feasible:
+    reward = -20  # 硬编码的大惩罚
+```
+
+**分析**:
+- `-20`与正常奖励(0.2~0.8)相差25-100倍
+- 没有区分不同约束违反的严重程度
+- 可能导致智能体过度保守
+
+**问题3:sigmoid惩罚形式复杂**
+
+```python
+headroom_penalty = 1 / (1 + exp(-10 × (tmp_ratio - 1.0)))
+```
+
+- 参数k=10是硬编码的
+- TMP贴边惩罚与其他目标权重不匹配
+
+**评级**:🟡 中等问题
+
+---
+
+### 问题4:episode设置不合理 ⚠️
+
+**位置**:`DQN_env.py` - `__init__()`
+
+**问题代码**:
+```python
+class UFSuperCycleEnv:
+    def __init__(self, base_params, max_episode_steps=20):
+        self.max_episode_steps = 20  # 固定20步
+```
+
+**问题分析**:
+1. **太短**:20步约等于20个超级周期(40-60天)
+   - 智能体难以学习长期策略
+   - 无法捕捉膜长期劣化趋势
+
+2. **固定长度**:
+   - 不利于学习不同时间尺度的策略
+   - 没有自然终止条件(如膜完全失效)
+
+3. **截断vs终止混淆**:
+   ```python
+   truncated = self.current_step >= 20  # 强制截断
+   ```
+   - 截断的episode不应该视为失败
+   - 但当前代码没有区分处理
+
+**建议**:
+- 增加到50-100步
+- 添加自然终止条件(如TMP超限3次)
+
+**评级**:🟡 中等问题
+
+---
+
+## 代码实现问题
+
+### 问题5:目标网络更新策略冲突 ⚠️⚠️⚠️
+
+**位置**:`DQN_train.py` - `_create_model()`
+
+**冲突代码**:
+```python
+class DQNParams:
+    target_update_interval: int = 2000  # 参数说明:每2000步更新
+
+def _create_model(self):
+    return DQN(
+        ...
+        target_update_interval=1,  # 实际代码:每1步更新
+        tau=0.005,                # 软更新系数
+        ...
+    )
+```
+
+**问题分析**:
+1. **参数说明与实现不一致**
+2. **软更新与硬更新混淆**:
+   - `target_update_interval=1` + `tau=0.005` → 软更新
+   - 注释说的是硬更新
+   - 两种策略特性不同
+
+**soft update(当前实际使用)**:
+```python
+θ_target = 0.005 × θ_current + 0.995 × θ_target
+```
+- 优点:平滑收敛
+- 缺点:可能不够稳定(对DQN而言)
+
+**hard update(注释说明)**:
+```python
+Every 2000 steps: θ_target = θ_current
+```
+- 优点:稳定性好(DQN原始设计)
+- 缺点:更新滞后
+
+**建议**:
+```python
+# 改为经典DQN的硬更新
+target_update_interval=1000,  # 每1000步硬更新
+tau=1.0,                      # tau=1表示完全复制
+```
+
+**评级**:🔴 严重问题
+
+---
+
+### 问题6:经验池太小 ⚠️
+
+**位置**:`DQN_train.py` - `DQNParams`
+
+**问题代码**:
+```python
+buffer_size: int = 10000  # 仅10000条经验
+```
+
+**问题分析**:
+1. **相对于动作空间太小**:
+   - 185个动作
+   - 理想情况:每个动作至少100条经验 → 需要18500
+   - 考虑不同状态:需要更多
+
+2. **经验覆盖率低**:
+   - 10000步训练期间,大部分动作可能没被充分探索
+   - 导致Q值估计偏差
+
+3. **旧经验快速被覆盖**:
+   - 50000步训练,经验池会被覆盖5次
+   - 早期的好经验可能被丢弃
+
+**建议**:
+```python
+buffer_size: int = 50000  # 增加到50000
+```
+
+**评级**:🟡 中等问题
+
+---
+
+### 问题7:探索开始过早 ⚠️
+
+**位置**:`DQN_train.py` - `DQNParams`
+
+**问题代码**:
+```python
+learning_starts: int = 200  # 仅200步随机探索
+```
+
+**问题分析**:
+1. **预填充不足**:
+   - 200步 < 动作数量(185)
+   - 许多动作可能一次都没被采样
+
+2. **早期训练不稳定**:
+   - 经验池数据分布严重偏斜
+   - Q值初始估计误差大
+
+**标准实践**:
+- 至少 `buffer_size × 0.1` = 5000步
+- 或 `action_space × 10` = 1850步
+
+**建议**:
+```python
+learning_starts: int = 5000  # 增加到5000
+```
+
+**评级**:🟡 中等问题
+
+---
+
+### 问题8:归一化范围硬编码 ⚠️
+
+**位置**:`DQN_env.py` - `_get_obs()`
+
+**问题代码**:
+```python
+def _get_obs(self):
+    TMP0_norm = (TMP0 - 0.01) / (0.05 - 0.01)  # 硬编码范围
+    ...
+```
+
+**问题分析**:
+1. **缺乏灵活性**:
+   - 如果TMP范围变化(新膜/旧膜),需要修改代码
+   - 不同工厂TMP范围可能不同
+
+2. **边界处理不当**:
+   ```python
+   TMP0 = 0.03  # 如果0.03对应的归一化值?
+   norm = (0.03 - 0.01) / 0.04 = 0.5  # 中间值
+   ```
+   - 如果TMP超出范围[0.01, 0.05]会怎样?未做clipping
+
+3. **不同维度归一化不一致**:
+   - TMP: [0.01, 0.05]
+   - L_s: [3800, 6000]
+   - t_bw_s: [40, 60]
+   - 范围差异大,但都归一化到[0, 1]
+
+**建议**:
+```python
+class Normalizer:
+    def __init__(self):
+        self.tmp_min = 0.01
+        self.tmp_max = 0.05
+        # ...可配置
+    
+    def normalize_tmp(self, tmp):
+        return np.clip((tmp - self.tmp_min) / (self.tmp_max - self.tmp_min), 0, 1)
+```
+
+**评级**:🟢 轻微问题
+
+---
+
+### 问题9:全局模型加载 ⚠️
+
+**位置**:`DQN_env.py` - 顶层
+
+**问题代码**:
+```python
+# 全局加载(模块导入时执行)
+model_fp = TMPIncreaseModel()
+model_bw = TMPDecreaseModel()
+model_fp.load_state_dict(torch.load("uf_fp.pth"))
+model_bw.load_state_dict(torch.load("uf_bw.pth"))
+model_fp.eval()
+model_bw.eval()
+```
+
+**问题分析**:
+1. **不支持多环境并行**:
+   - 如果使用`SubprocVecEnv`(多进程),每个进程都会加载
+   - 浪费内存
+
+2. **路径硬编码**:
+   - 必须在当前目录下有`uf_fp.pth`
+   - 不利于部署
+
+3. **无法动态切换模型**:
+   - 如果想测试不同的物理模型,需要重启程序
+
+4. **测试困难**:
+   - 单元测试时无法mock这些模型
+
+**建议**:
+```python
+class UFSuperCycleEnv:
+    def __init__(self, base_params, model_dir="./"):
+        self.model_fp = TMPIncreaseModel()
+        self.model_bw = TMPDecreaseModel()
+        self.model_fp.load_state_dict(torch.load(f"{model_dir}/uf_fp.pth"))
+        self.model_bw.load_state_dict(torch.load(f"{model_dir}/uf_bw.pth"))
+```
+
+**评级**:🟡 中等问题
+
+---
+
+### 问题10:缺少模型checkpoint ⚠️⚠️
+
+**位置**:`DQN_train.py` - `train()`
+
+**问题代码**:
+```python
+def train(self, total_timesteps: int):
+    self.model.learn(total_timesteps=total_timesteps, callback=self.callback)
+    # 训练结束后才保存一次
+```
+
+**问题分析**:
+1. **训练中断风险**:
+   - 50000步训练可能需要数小时
+   - 如果中途崩溃,所有进度丢失
+
+2. **无法回滚到最佳模型**:
+   - 如果训练后期发散,无法恢复到中间的好模型
+
+3. **难以对比不同阶段**:
+   - 无法分析不同训练阶段的策略差异
+
+**建议**:
+```python
+from stable_baselines3.common.callbacks import CheckpointCallback
+
+checkpoint_callback = CheckpointCallback(
+    save_freq=5000,  # 每5000步保存
+    save_path='./checkpoints/',
+    name_prefix='uf_dqn'
+)
+
+model.learn(..., callback=[checkpoint_callback, training_callback])
+```
+
+**评级**:🟡 中等问题
+
+---
+
+## 算法设计问题
+
+### 问题11:DQN不是最佳选择 ⚠️⚠️
+
+**当前选择**:DQN(Deep Q-Network)
+
+**DQN特点**:
+- ✅ 简单、稳定
+- ✅ 离散动作空间
+- ❌ 样本效率低
+- ❌ 难以处理连续动作
+- ❌ 探索能力弱
+
+**问题分析**:
+
+1. **动作空间其实是连续的**:
+   - L_s ∈ [3800, 6000] 秒(连续)
+   - t_bw_s ∈ [40, 60] 秒(连续)
+   - 当前用网格离散化(37×5=185个点)
+   - 损失精度
+
+2. **更适合的算法**:
+
+| 算法 | 优点 | 缺点 | 适用性 |
+|------|------|------|--------|
+| **SAC** | 连续动作、样本高效、稳定 | 稍复杂 | ⭐⭐⭐⭐⭐ |
+| **TD3** | 连续动作、稳定 | 探索能力弱 | ⭐⭐⭐⭐ |
+| **PPO** | 稳定、易调参 | 样本效率低 | ⭐⭐⭐ |
+| **DQN** | 简单 | 连续动作支持差 | ⭐⭐ |
+
+**推荐改用SAC**:
+```python
+from stable_baselines3 import SAC
+
+model = SAC(
+    policy="MlpPolicy",
+    env=env,
+    learning_rate=3e-4,
+    buffer_size=100000,
+    batch_size=256,
+    tau=0.005,
+    gamma=0.99,
+    verbose=1
+)
+```
+
+**改用SAC的好处**:
+- 动作空间从185个离散点 → 连续范围
+- 样本效率提升2-3倍
+- 更适合精细控制
+
+**评级**:🟡 中等问题
+
+---
+
+### 问题12:缺少curriculum learning ⚠️
+
+**当前训练**:
+```python
+def reset(self):
+    self.TMP0 = uniform(0.01, 0.03)  # 固定范围
+```
+
+**问题**:
+- 从一开始就面对全部难度
+- 智能体需要同时学习:
+  - 低TMP下的最优策略
+  - 高TMP下的安全策略
+  - 约束边界的处理
+- 学习效率低
+
+**curriculum learning思路**:
+
+```python
+# 阶段1:简单场景(0-10k步)
+TMP_range = [0.025, 0.03]  # 窄范围
+constraint_relaxed = True  # 放宽约束
+
+# 阶段2:中等场景(10k-30k步)
+TMP_range = [0.02, 0.035]
+constraint_relaxed = False
+
+# 阶段3:困难场景(30k-50k步)
+TMP_range = [0.01, 0.04]  # 全范围
+add_noise = True  # 增加噪声
+```
+
+**实现示例**:
+```python
+class CurriculumEnv(UFSuperCycleEnv):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.difficulty = 1  # 难度等级
+    
+    def reset(self):
+        if self.difficulty == 1:
+            self.TMP0 = uniform(0.025, 0.03)
+        elif self.difficulty == 2:
+            self.TMP0 = uniform(0.02, 0.035)
+        else:
+            self.TMP0 = uniform(0.01, 0.04)
+        return super().reset()
+    
+    def increase_difficulty(self):
+        self.difficulty = min(3, self.difficulty + 1)
+```
+
+**评级**:🟢 轻微问题
+
+---
+
+## 工程质量问题
+
+### 问题13:缺少单元测试 ⚠️⚠️
+
+**当前状态**:无任何测试代码
+
+**关键模块应测试**:
+
+```python
+# tests/test_env.py
+def test_env_reset():
+    env = UFSuperCycleEnv(UFParams())
+    obs, info = env.reset()
+    assert obs.shape == (4,)
+    assert 0 <= obs.all() <= 1
+
+def test_env_step():
+    env = UFSuperCycleEnv(UFParams())
+    env.reset()
+    obs, reward, done, truncated, info = env.step(0)
+    assert isinstance(reward, float)
+    assert isinstance(done, bool)
+
+def test_simulate_feasibility():
+    p = UFParams()
+    # 测试可行动作
+    feasible, info = simulate_one_supercycle(p, 4000, 50)
+    assert feasible == True
+    
+    # 测试不可行动作(过长时间)
+    feasible, info = simulate_one_supercycle(p, 7000, 50)
+    assert feasible == False
+
+def test_reward_range():
+    """测试奖励是否在合理范围"""
+    rewards = []
+    for _ in range(1000):
+        # 采样不同状态和动作
+        reward = _score(params, info)
+        rewards.append(reward)
+    
+    assert min(rewards) > -30  # 避免过大负奖励
+    assert max(rewards) < 10   # 避免奖励爆炸
+```
+
+**评级**:🟡 中等问题
+
+---
+
+### 问题14:缺少配置管理 ⚠️
+
+**当前状态**:参数散落在多个类中
+
+**建议结构**:
+
+```python
+# config.yaml
+environment:
+  tmp_range: [0.01, 0.05]
+  action_range:
+    L_s: [3800, 6000]
+    t_bw_s: [40, 60]
+  constraints:
+    tmp_max: 0.06
+    dTMP: 0.001
+
+dqn:
+  learning_rate: 1e-4
+  buffer_size: 50000
+  batch_size: 64
+  gamma: 0.95
+
+training:
+  total_timesteps: 100000
+  checkpoint_freq: 5000
+  eval_freq: 2000
+```
+
+```python
+# config.py
+import yaml
+from dataclasses import dataclass
+
+@dataclass
+class Config:
+    @staticmethod
+    def from_yaml(path):
+        with open(path) as f:
+            data = yaml.safe_load(f)
+        return Config(**data)
+```
+
+**评级**:🟢 轻微问题
+
+---
+
+### 问题15:缺少日志系统 ⚠️
+
+**当前状态**:只有print语句
+
+**建议**:
+```python
+import logging
+
+logging.basicConfig(
+    level=logging.INFO,
+    format='%(asctime)s [%(levelname)s] %(message)s',
+    handlers=[
+        logging.FileHandler('training.log'),
+        logging.StreamHandler()
+    ]
+)
+
+logger = logging.getLogger(__name__)
+
+# 使用
+logger.info(f"Episode {ep} - reward: {reward:.3f}")
+logger.warning(f"Constraint violation at step {step}")
+logger.error(f"Training failed: {error}")
+```
+
+**评级**:🟢 轻微问题
+
+---
+
+## 优化方案
+
+### 方案1:物理模型升级 🔥
+
+**目标**:用真实神经网络替代数学公式
+
+#### 数据收集
+```python
+# 收集真实运行数据
+data = {
+    'L_h': [...],         # 产水时长
+    'q_UF': [...],        # 流量
+    'temp': [...],        # 温度
+    'delta_TMP': [...]    # 实测TMP增长
+}
+```
+
+#### 模型训练
+```python
+class RealTMPIncreaseModel(nn.Module):
+    def __init__(self):
+        super().__init__()
+        self.net = nn.Sequential(
+            nn.Linear(10, 64),   # 输入:L_h, q_UF, temp, ...
+            nn.ReLU(),
+            nn.Linear(64, 32),
+            nn.ReLU(),
+            nn.Linear(32, 1)     # 输出:delta_TMP
+        )
+    
+    def forward(self, features):
+        return self.net(features)
+
+# 监督学习训练
+model = RealTMPIncreaseModel()
+optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
+
+for epoch in range(100):
+    for batch in data_loader:
+        pred = model(batch['features'])
+        loss = F.mse_loss(pred, batch['delta_TMP'])
+        optimizer.zero_grad()
+        loss.backward()
+        optimizer.step()
+```
+
+#### 集成到环境
+```python
+def _delta_tmp(p, L_h):
+    features = torch.FloatTensor([
+        L_h,
+        p.q_UF,
+        p.temp,
+        ...
+    ])
+    with torch.no_grad():
+        delta = model_fp(features).item()
+    return delta
+```
+
+**收益**:
+- ✅ 模拟器精度大幅提升
+- ✅ 可持续改进(随数据积累)
+- ✅ 缩小Sim-to-Real Gap
+
+---
+
+### 方案2:状态空间扩展 🔥
+
+**新状态设计**:
+```python
+def _get_obs(self):
+    state = [
+        # 基础状态(4维)
+        TMP0_norm,
+        L_norm,
+        t_bw_norm,
+        max_TMP_norm,
+        
+        # 水质特征(3维)
+        turbidity_norm,      # 浊度
+        conductivity_norm,   # 电导率
+        temperature_norm,    # 温度
+        
+        # 历史趋势(4维)
+        tmp_change_rate,     # TMP变化速率
+        avg_L_last_5,        # 最近5次平均产水时长
+        avg_recovery_last_5, # 最近5次平均回收率
+        days_since_ceb,      # 距上次CEB天数
+        
+        # 膜状态(2维)
+        membrane_age,        # 膜龄(归一化)
+        total_cycles,        # 总运行周期数
+    ]
+    return np.array(state, dtype=np.float32)  # 13维
+```
+
+**实现历史追踪**:
+```python
+class UFSuperCycleEnv:
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.history = {
+            'L_s': deque(maxlen=5),
+            'recovery': deque(maxlen=5),
+            'TMP': deque(maxlen=10)
+        }
+    
+    def step(self, action):
+        ...
+        self.history['L_s'].append(L_s)
+        self.history['recovery'].append(info['recovery'])
+        self.history['TMP'].append(self.TMP0)
+        ...
+```
+
+**收益**:
+- ✅ 智能体感知能力增强
+- ✅ 能学习长期策略
+- ✅ 适应不同运行条件
+
+---
+
+### 方案3:奖励函数重构 🔥
+
+**新奖励设计**:
+```python
+def _score_v2(p, rec, constraint_violation=None):
+    # 1. 基础奖励(保持简单)
+    recovery_reward = rec['recovery']  # [0.9, 0.98]
+    rate_reward = rec['net_rate'] / p.q_UF  # [0.85, 0.95]
+    
+    # 2. TMP惩罚(线性,避免非线性)
+    tmp_penalty = max(0, (rec['max_TMP'] / p.TMP_max - 0.9)) * 2
+    # TMP<90%上限:无惩罚
+    # TMP=95%上限:惩罚0.1
+    # TMP=100%上限:惩罚0.2
+    
+    # 3. 约束违反惩罚(分级)
+    if constraint_violation:
+        if constraint_violation == 'tmp_peak':
+            penalty = -5  # 峰值超限
+        elif constraint_violation == 'residual':
+            penalty = -3  # 残余增量超限
+        elif constraint_violation == 'headroom':
+            penalty = -2  # 贴边过度
+    else:
+        penalty = 0
+    
+    # 4. 稳定性奖励(鼓励平稳操作)
+    stability_bonus = 0
+    if hasattr(env, 'last_action'):
+        action_change = abs(current_action - env.last_action)
+        if action_change < 0.1:  # 动作变化小
+            stability_bonus = 0.05
+    
+    # 5. 总奖励(加权和,无非线性变换)
+    reward = (
+        0.6 * recovery_reward 
+        + 0.3 * rate_reward 
+        - 0.2 * tmp_penalty
+        + penalty
+        + stability_bonus
+    )
+    
+    return reward
+```
+
+**关键改进**:
+1. **移除非线性变换**:保持奖励尺度一致
+2. **分级惩罚**:区分不同约束违反的严重性
+3. **稳定性奖励**:鼓励平滑控制
+4. **可解释性**:每项奖励含义清晰
+
+**收益**:
+- ✅ 训练更稳定
+- ✅ Q值估计更准确
+- ✅ 策略更合理
+
+---
+
+### 方案4:改用SAC算法 🔥
+
+**完整实现**:
+
+```python
+from stable_baselines3 import SAC
+from gymnasium import spaces
+
+class UFSuperCycleEnvContinuous(UFSuperCycleEnv):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        
+        # 改为连续动作空间
+        self.action_space = spaces.Box(
+            low=np.array([0.0, 0.0]),      # [L_norm, t_bw_norm]
+            high=np.array([1.0, 1.0]),
+            dtype=np.float32
+        )
+    
+    def step(self, action):
+        # 反归一化
+        L_s = self.L_min + action[0] * (self.L_max - self.L_min)
+        t_bw_s = self.t_bw_min + action[1] * (self.t_bw_max - self.t_bw_min)
+        
+        # 其余逻辑相同
+        ...
+
+# 训练
+model = SAC(
+    policy="MlpPolicy",
+    env=env,
+    learning_rate=3e-4,
+    buffer_size=100000,
+    batch_size=256,
+    tau=0.005,
+    gamma=0.99,
+    ent_coef='auto',  # 自动调整熵系数
+    target_entropy='auto',
+    verbose=1,
+    tensorboard_log="./sac_tensorboard/"
+)
+
+model.learn(total_timesteps=100000)
+```
+
+**SAC优势**:
+- ✅ 连续动作(无需离散化)
+- ✅ 样本效率高(off-policy)
+- ✅ 探索能力强(熵正则化)
+- ✅ 更稳定(twin Q-networks)
+
+**收益**:
+- 训练时间缩短30-50%
+- 策略精度提升(无网格限制)
+- 更好的泛化能力
+
+---
+
+### 方案5:增加evaluation循环 🔥
+
+**实现**:
+```python
+from stable_baselines3.common.callbacks import EvalCallback
+
+eval_env = UFSuperCycleEnv(UFParams())
+eval_callback = EvalCallback(
+    eval_env,
+    best_model_save_path='./best_model/',
+    log_path='./eval_logs/',
+    eval_freq=2000,       # 每2000步评估一次
+    n_eval_episodes=10,   # 每次评估10个episode
+    deterministic=True,   # 确定性策略评估
+    render=False
+)
+
+model.learn(
+    total_timesteps=100000,
+    callback=[eval_callback, checkpoint_callback, training_callback]
+)
+```
+
+**收益**:
+- ✅ 实时监控泛化性能
+- ✅ 自动保存最佳模型
+- ✅ 早期发现过拟合
+
+---
+
+## 重构建议
+
+### 重构方案A:渐进式改进(推荐)
+
+**阶段1:修复关键bug(1-2天)**
+```
+1. 修复目标网络更新冲突 → 硬更新
+2. 增大buffer_size到50000
+3. 增大learning_starts到5000
+4. 添加checkpoint保存
+5. 修复奖励函数(移除非线性)
+```
+
+**阶段2:优化训练(3-5天)**
+```
+1. 扩展状态空间(添加历史信息)
+2. 增加episode长度到50步
+3. 实现curriculum learning
+4. 添加evaluation循环
+```
+
+**阶段3:算法升级(1周)**
+```
+1. 改用SAC算法
+2. 连续动作空间
+3. 超参数调优
+```
+
+**阶段4:模型升级(2-4周)**
+```
+1. 收集真实运行数据
+2. 训练神经网络物理模型
+3. 集成到环境
+4. 验证Sim-to-Real性能
+```
+
+---
+
+### 重构方案B:全面重写(高风险)
+
+**新架构设计**:
+
+```
+项目结构:
+uf_rl_v2/
+├── config/
+│   ├── env_config.yaml
+│   ├── sac_config.yaml
+│   └── train_config.yaml
+├── models/
+│   ├── physics/
+│   │   ├── tmp_model.py       # 神经网络物理模型
+│   │   └── train_physics.py   # 物理模型训练脚本
+│   ├── policy/
+│   │   └── sac_policy.py      # SAC策略网络
+│   └── reward/
+│       └── reward_shaping.py  # 奖励函数设计
+├── envs/
+│   ├── uf_env_v2.py           # 重构的环境
+│   └── wrappers.py            # 环境包装器
+├── utils/
+│   ├── logger.py              # 日志系统
+│   ├── callbacks.py           # 训练回调
+│   └── evaluation.py          # 评估工具
+├── tests/
+│   ├── test_env.py
+│   ├── test_physics.py
+│   └── test_training.py
+├── train.py                   # 训练入口
+└── requirements.txt
+```
+
+**核心改进**:
+1. **模块化设计**:物理模型、策略、环境解耦
+2. **配置驱动**:所有参数外部化
+3. **完整测试**:覆盖所有关键模块
+4. **现代算法**:使用SAC
+5. **数据驱动**:神经网络物理模型
+
+---
+
+## 优先级排序
+
+### 🔴 高优先级(必须修复)
+
+1. **目标网络更新冲突** → 1小时
+2. **奖励函数非线性变换** → 2小时
+3. **增大buffer_size和learning_starts** → 0.5小时
+
+**总计**:半天可完成
+
+---
+
+### 🟡 中优先级(建议修复)
+
+4. **状态空间扩展(添加历史)** → 1天
+5. **改用SAC算法** → 2-3天
+6. **增加checkpoint和evaluation** → 1天
+7. **episode长度调整** → 0.5天
+
+**总计**:1周可完成
+
+---
+
+### 🟢 低优先级(可选)
+
+8. **curriculum learning** → 2天
+9. **配置文件管理** → 1天
+10. **单元测试** → 2-3天
+11. **日志系统** → 0.5天
+12. **神经网络物理模型** → 2-4周
+
+**总计**:1-2周可完成
+
+---
+
+## 总结
+
+### 核心问题
+1. **物理模型是假神经网络**(最严重)
+2. **目标网络更新策略冲突**
+3. **奖励函数设计不合理**
+4. **状态空间信息不足**
+5. **DQN不是最佳算法选择**
+
+### 最小可行改进方案(MVP)
+
+```python
+# 1. 修复目标网络(5分钟)
+target_update_interval=1000, tau=1.0
+
+# 2. 简化奖励函数(10分钟)
+reward = 0.6*recovery + 0.3*rate - 0.2*tmp_penalty + constraint_penalty
+
+# 3. 增大经验池(1行)
+buffer_size=50000, learning_starts=5000
+
+# 4. 添加checkpoint(5分钟)
+CheckpointCallback(save_freq=5000, ...)
+
+# 5. 添加evaluation(5分钟)
+EvalCallback(eval_freq=2000, ...)
+```
+
+**总时间**:不到1小时  
+**预期提升**:训练稳定性提升50%+,最终性能提升20%+
+
+### 理想改进方案
+
+1. 收集真实数据 → 训练神经网络物理模型
+2. 改用SAC + 连续动作空间
+3. 扩展状态空间(13维)
+4. 重构奖励函数
+5. 完善工程质量(测试、日志、配置)
+
+**总时间**:3-4周  
+**预期提升**:训练效率提升3倍+,策略性能提升50%+,工业可用性大幅提升
+

+ 1082 - 0
models/uf-rl/训练/uf_train/UF_RL_训练与预测流程详解.md

@@ -0,0 +1,1082 @@
+# UF-RL 训练与预测流程详解
+
+## 目录
+1. [训练阶段完整流程](#训练阶段完整流程)
+2. [预测阶段完整流程](#预测阶段完整流程)
+3. [从训练到部署的完整链路](#从训练到部署的完整链路)
+
+---
+
+## 训练阶段完整流程
+
+### 概述:智能体如何学会决策
+
+把训练过程想象成**培养一个工程师**:
+
+```
+新手工程师(随机决策)
+    ↓ 通过大量实践
+    ↓ 记住成功/失败的经验
+    ↓ 总结规律
+    ↓
+经验丰富的工程师(最优决策)
+```
+
+强化学习就是这个过程的数学化实现。
+
+---
+
+### 阶段0:准备工作(程序启动)
+
+#### 步骤0.1:固定随机种子
+```python
+def set_global_seed(seed=2025):
+    random.seed(2025)
+    np.random.seed(2025)
+    torch.manual_seed(2025)
+    torch.cuda.manual_seed_all(2025)
+```
+
+**作用**:保证每次训练结果一致,便于调试和复现
+
+---
+
+#### 步骤0.2:创建超滤系统参数
+```python
+params = UFParams(
+    q_UF=360.0,          # 进水流量:360 m³/h
+    TMP0=0.03,           # 初始TMP:0.03 MPa
+    TMP_max=0.06,        # TMP上限:0.06 MPa
+    L_min_s=3800.0,      # 产水时长下限:3800秒
+    L_max_s=6000.0,      # 产水时长上限:6000秒
+    t_bw_min_s=40.0,     # 反洗时长下限:40秒
+    t_bw_max_s=60.0,     # 反洗时长上限:60秒
+    ...
+)
+```
+
+**这些参数定义了**:
+- 物理系统的运行范围
+- 决策空间的边界
+- 约束条件
+
+---
+
+#### 步骤0.3:创建模拟环境
+```python
+env = UFSuperCycleEnv(params)
+env = Monitor(env)           # 包装:记录统计信息
+env = DummyVecEnv([env])     # 包装:向量化接口
+```
+
+**环境的作用**:
+- 模拟超滤系统的运行
+- 接收智能体的动作(产水时长、反洗时长)
+- 返回奖励和下一个状态
+
+**为什么需要包装?**
+- `Monitor`:自动记录每个episode的奖励、长度等
+- `DummyVecEnv`:统一单环境/多环境的接口(虽然这里只有1个)
+
+---
+
+#### 步骤0.4:创建DQN智能体
+```python
+model = DQN(
+    policy="MlpPolicy",        # 使用多层感知机策略网络
+    env=env,
+    learning_rate=1e-4,        # 学习率
+    buffer_size=10000,         # 经验回放池大小
+    learning_starts=200,       # 开始学习前的随机探索步数
+    batch_size=32,             # 每次训练的样本数
+    gamma=0.95,                # 折扣因子(重视长期奖励)
+    train_freq=4,              # 每4步训练一次
+    target_update_interval=1,  # 目标网络更新间隔
+    tau=0.005,                 # 软更新系数
+    exploration_initial_eps=1.0,   # 初始探索率100%
+    exploration_fraction=0.3,      # 前30%训练时间探索衰减
+    exploration_final_eps=0.02,    # 最终探索率2%
+    verbose=1,
+    tensorboard_log="./uf_dqn_tensorboard/"
+)
+```
+
+**DQN智能体包含**:
+1. **Q网络(当前网络)**:估计每个动作的价值
+2. **目标网络**:提供稳定的学习目标
+3. **经验回放池**:存储历史经验
+4. **优化器**:更新网络参数
+
+**Q网络结构**:
+```python
+输入层:4维状态 → [TMP0, last_L, last_t_bw, max_TMP]
+隐藏层1:4 → 64 (ReLU激活)
+隐藏层2:64 → 64 (ReLU激活)
+输出层:64 → 185 (每个动作的Q值)
+```
+
+---
+
+#### 步骤0.5:创建回调和记录器
+```python
+recorder = UFEpisodeRecorder()          # 记录每个episode的数据
+callback = UFTrainingCallback(recorder) # 训练回调
+```
+
+**记录内容**:
+- 每一步的状态、动作、奖励
+- 每个episode的总奖励、回收率等
+- 用于训练后分析
+
+---
+
+### 阶段1:初始化(第1个Episode开始)
+
+#### 环境重置
+```python
+state = env.reset()
+
+# 环境内部执行:
+self.TMP0 = np.random.uniform(0.01, 0.03)  # 随机初始TMP,例如0.025
+self.current_step = 0
+self.last_action = (3800, 40)  # 初始动作:最保守的选择
+self.max_TMP = self.TMP0
+
+# 计算初始状态
+state = [
+    (0.025 - 0.01) / 0.04,     # TMP0归一化 = 0.375
+    (3800 - 3800) / 2200,       # last_L归一化 = 0.0
+    (40 - 40) / 20,             # last_t_bw归一化 = 0.0
+    (0.025 - 0.01) / 0.04       # max_TMP归一化 = 0.375
+]
+# state = [0.375, 0.0, 0.0, 0.375]
+```
+
+**状态解释**:
+- 当前是一个"全新的超滤膜",TMP=0.025
+- 还没有历史操作(last_L和last_t_bw都是初始值)
+- 周期最高TMP就是当前TMP
+
+---
+
+### 阶段2:交互与学习循环(50000步)
+
+#### 完整的一步流程图
+
+```
+┌─────────────────────────────────────────────────────────┐
+│                    第 N 步                              │
+└─────────────────────────────────────────────────────────┘
+
+1. 当前状态
+   state = [0.375, 0.491, 0.5, 0.429]
+        ↓
+
+2. 动作选择(ε-贪心)
+   ┌────────────────────────────┐
+   │ if random() < epsilon:     │
+   │     action = random(0-184) │  ← 探索
+   │ else:                      │
+   │     Q值 = Q_network(state) │  ← 利用
+   │     action = argmax(Q值)   │
+   └────────────────────────────┘
+        ↓
+   假设选择 action = 92
+        ↓
+
+3. 动作解码
+   L_idx = 92 // 5 = 18
+   t_bw_idx = 92 % 5 = 2
+   L_s = 3800 + 18×60 = 4880秒
+   t_bw_s = 40 + 2×5 = 50秒
+        ↓
+
+4. 执行模拟
+   simulate_one_supercycle(TMP0=0.025, L_s=4880, t_bw_s=50)
+   ┌──────────────────────────────────┐
+   │  计算小周期次数:k = 35         │
+   │  For i in range(35):             │
+   │    产水:TMP增长                 │
+   │    反洗:TMP部分恢复             │
+   │  CEB:TMP完全恢复                │
+   │  计算指标:回收率、净供水率等   │
+   └──────────────────────────────────┘
+        ↓
+   返回:feasible=True, info={recovery:0.97, net_rate:338, ...}
+        ↓
+
+5. 计算奖励
+   reward = _score(info)
+   = 0.8×0.97 + 0.2×0.94 - 0.2×0.00
+   = 0.964(基础奖励)
+   放大后 = (0.964-0.5)² × 5 = 1.076
+        ↓
+
+6. 观察新状态
+   TMP0_new = 0.025(CEB后恢复)
+   next_state = [0.375, 0.491, 0.5, 0.429]
+        ↓
+
+7. 存储经验
+   buffer.add(
+       state = [0.375, 0.0, 0.0, 0.375],
+       action = 92,
+       reward = 1.076,
+       next_state = [0.375, 0.491, 0.5, 0.429],
+       done = False
+   )
+        ↓
+
+8. 训练网络(每4步,且步数>200)
+   ┌──────────────────────────────────────┐
+   │ if step % 4 == 0 and step > 200:    │
+   │    batch = buffer.sample(32)         │
+   │    训练Q网络(详见下方)             │
+   └──────────────────────────────────────┘
+        ↓
+
+9. 更新epsilon
+   epsilon = max(0.02, 1.0 - step/15000)
+        ↓
+
+10. 记录数据
+    callback.record_step(state, action, reward, ...)
+        ↓
+
+11. 检查episode是否结束
+    if done or step >= 20:
+        state = env.reset()  # 开始新episode
+    else:
+        state = next_state   # 继续当前episode
+```
+
+---
+
+### 深入理解:Q网络训练(第204步首次训练)
+
+```python
+# ===== 第204步:终于可以开始学习了!=====
+
+# 1. 从经验池随机采样32条经验
+batch = buffer.sample(32)
+
+# 采样结果示例:
+batch = {
+    'state': [
+        [0.375, 0.0, 0.0, 0.375],      # 第1条经验的state
+        [0.425, 0.2, 0.3, 0.450],      # 第2条经验的state
+        ...                             # 共32条
+    ],
+    'action': [92, 105, 78, ...],      # 32个动作
+    'reward': [1.076, 0.845, -20, ...],# 32个奖励
+    'next_state': [...],                # 32个next_state
+    'done': [False, False, True, ...]   # 32个done标志
+}
+
+# 2. 转换为PyTorch张量
+state_tensor = torch.FloatTensor(batch['state'])       # [32, 4]
+action_tensor = torch.LongTensor(batch['action'])      # [32]
+reward_tensor = torch.FloatTensor(batch['reward'])     # [32]
+next_state_tensor = torch.FloatTensor(batch['next_state'])  # [32, 4]
+done_tensor = torch.FloatTensor(batch['done'])         # [32]
+
+# 3. 计算当前Q值(Q_current)
+q_values = Q_network(state_tensor)  # [32, 185]
+# 对于第1条经验,Q网络预测所有185个动作的Q值
+# 例如:[0.5, 0.6, ..., 1.2, ..., 0.8](185个值)
+
+q_current = q_values.gather(1, action_tensor.unsqueeze(1))  # [32, 1]
+# gather操作:取出实际执行的动作对应的Q值
+# 对于第1条经验,action=92,取出q_values[92] = 1.2
+# 结果:[1.2, 0.9, -5.0, ...](32个值)
+
+# 4. 计算目标Q值(Q_target)
+with torch.no_grad():  # 不计算梯度,加速
+    # 用目标网络预测next_state的Q值
+    next_q_values = Q_target(next_state_tensor)  # [32, 185]
+    
+    # 取每个next_state的最大Q值
+    next_q_max, _ = next_q_values.max(dim=1)  # [32]
+    # 例如:[1.5, 1.3, 0.0, ...]
+    
+    # 贝尔曼方程:Q_target = reward + gamma × max(Q_next) × (1-done)
+    target = reward_tensor + 0.95 × next_q_max × (1 - done_tensor)
+    # 对于第1条经验:
+    # target = 1.076 + 0.95 × 1.5 × (1-0) = 2.501
+    # 对于第3条经验(done=True):
+    # target = -20 + 0.95 × 0.0 × (1-1) = -20
+
+# 5. 计算TD误差(损失函数)
+loss = F.mse_loss(q_current.squeeze(), target)
+# MSE = mean((q_current - target)²)
+# 例如:mean([(1.2-2.501)², (0.9-2.135)², ...])
+# 假设 loss = 3.45
+
+# 6. 反向传播更新Q网络
+optimizer.zero_grad()   # 清空之前的梯度
+loss.backward()         # 计算梯度
+optimizer.step()        # 更新参数
+
+# 更新后,Q_network的参数被调整:
+# - 如果q_current < target,增大该动作的Q值
+# - 如果q_current > target,减小该动作的Q值
+
+# 7. 软更新目标网络
+for param, target_param in zip(Q_network.parameters(), Q_target.parameters()):
+    target_param.data.copy_(
+        0.005 × param.data + 0.995 × target_param.data
+    )
+# 目标网络缓慢追踪Q网络,tau=0.005表示每次只更新0.5%
+```
+
+---
+
+### 训练过程的关键时间点
+
+#### 时间线(50000步训练)
+
+```
+步数     | epsilon | 行为                    | 学习状态
+---------|---------|------------------------|------------------
+0-200    | 1.0     | 纯随机探索             | 填充经验池
+204      | 1.0     | 首次训练               | Q值从随机初始化开始学习
+500      | 0.98    | 探索为主               | Q值逐渐有意义
+2000     | 0.87    | 探索与利用并存         | 策略初步成型
+5000     | 0.67    | 开始偏向利用           | Q值趋于稳定
+15000    | 0.02    | 探索衰减完成           | 基本使用最优策略
+15000+   | 0.02    | 98%利用,2%探索        | 策略优化与稳定
+50000    | 0.02    | 训练结束               | 保存最终模型
+```
+
+#### 示例:第5000步时的决策过程
+
+```python
+# 当前状态:TMP稍高
+state = [0.625, 0.55, 0.6, 0.650]  # TMP0=0.035 MPa
+
+# epsilon=0.67,仍有67%概率随机探索
+if random() < 0.67:
+    action = random.randint(0, 184)  # 假设随机到115
+else:
+    # 33%概率使用Q网络
+    q_values = Q_network(state)
+    # Q值示例(部分):
+    # action 0 (L=3800, t_bw=40): Q=0.45  (保守,TMP低但产水少)
+    # action 92 (L=4880, t_bw=50): Q=0.78 (平衡)
+    # action 115 (L=5080, t_bw=55): Q=0.82 (激进,产水多但TMP升高)
+    # action 184 (L=6000, t_bw=60): Q=-2.5 (太激进,违反约束)
+    
+    action = argmax(q_values) = 115
+
+# 解码动作
+L_s = 5080, t_bw_s = 55
+
+# 执行模拟
+feasible, info = simulate_one_supercycle(0.035, 5080, 55)
+# 结果:feasible=True(刚好没违反约束)
+#       recovery=0.965, net_rate=330
+
+# 计算奖励
+reward = 0.75(较好但不是最优,因为TMP有点高)
+
+# 存储经验并训练
+# Q网络学到:在TMP=0.035时,action=115是一个还不错的选择
+```
+
+---
+
+### 示例:第25000步时的决策过程
+
+```python
+# 同样的状态
+state = [0.625, 0.55, 0.6, 0.650]  # TMP0=0.035 MPa
+
+# epsilon=0.02,只有2%概率随机探索
+if random() < 0.02:
+    action = random(0-184)
+else:
+    # 98%概率使用Q网络(此时Q值已经很准确)
+    q_values = Q_network(state)
+    # 经过25000步学习,Q值更精准:
+    # action 0: Q=0.52   (保守,稳定)
+    # action 85: Q=0.88  (最优!)✓
+    # action 92: Q=0.85  (次优)
+    # action 115: Q=0.65 (之前试过,风险高)
+    # action 184: Q=-15.0(确定违反约束)
+    
+    action = 85  # 选择最优动作
+
+# 解码
+L_s = 4720, t_bw_s = 45
+
+# 执行
+feasible, info = simulate(0.035, 4720, 45)
+# 结果:feasible=True
+#       recovery=0.972, net_rate=335
+#       TMP贴边度低,安全
+
+# 奖励
+reward = 0.92(接近最优)
+
+# 智能体学会了:
+# - 在TMP较高时,要稍微保守一点
+# - L_s=4720是在高TMP下的最佳平衡点
+```
+
+---
+
+### 阶段3:训练结束与保存
+
+```python
+# 训练完成
+model.learn(total_timesteps=50000)  # 循环结束
+
+# 保存模型
+model.save("dqn_model.zip")
+
+# 模型文件包含:
+# 1. Q_network的所有参数(权重和偏置)
+# 2. 优化器状态
+# 3. 训练配置(学习率等)
+# 不包含:经验回放池(太大且推理时不需要)
+```
+
+**训练日志统计**:
+```python
+stats = recorder.get_episode_stats()
+print(f"""
+训练完成统计:
+- 总步数:50000
+- 总episode数:约2500(平均每episode 20步)
+- 最终平均奖励:0.85
+- 约束违反率:5%(从初期80%大幅下降)
+- 平均回收率:0.968
+""")
+```
+
+---
+
+## 预测阶段完整流程
+
+### 概述:训练好的智能体如何工作
+
+训练完成后,模型已经学会了:
+```
+给定状态(TMP, 历史操作) → 选择最优动作(L_s, t_bw_s)
+```
+
+预测阶段不需要:
+- ✗ 探索(epsilon=0,总是选择最优)
+- ✗ 训练(不更新网络参数)
+- ✗ 经验池(不存储新经验)
+
+预测阶段只需要:
+- ✓ 加载训练好的Q网络
+- ✓ 输入当前状态
+- ✓ 输出最优动作
+
+---
+
+### 预测流程详解
+
+#### 场景:工厂实时决策
+
+假设当前时间:2025-01-15 10:00,超滤系统运行中,需要决定下一个周期的参数。
+
+---
+
+#### 步骤1:获取当前系统状态
+
+```python
+# 从工厂SCADA系统读取实时数据
+current_TMP0 = 0.032  # 当前TMP(MPa)
+last_L_s = 4500       # 上一周期产水时长(秒)
+last_t_bw_s = 50      # 上一周期反洗时长(秒)
+max_TMP_last = 0.045  # 上一周期最高TMP(MPa)
+
+# 也可以从数据库查询历史记录
+# 或者如果是第一次运行,使用默认值
+```
+
+---
+
+#### 步骤2:初始化决策环境和模型
+
+```python
+from DQN_decide import run_uf_DQN_decide
+from DQN_env import UFParams
+
+# 2.1 创建系统参数(与训练时一致)
+uf_params = UFParams(
+    q_UF=360.0,
+    TMP_max=0.06,
+    # ... 其他参数
+)
+
+# 2.2 加载训练好的模型(自动完成)
+# 模型文件:dqn_model.zip
+# 内部会执行:
+# model = DQN.load("dqn_model.zip")
+```
+
+---
+
+#### 步骤3:执行决策
+
+```python
+# 3.1 调用决策接口
+result = run_uf_DQN_decide(
+    uf_params=uf_params,
+    TMP0_value=0.032  # 输入当前TMP
+)
+
+# 3.2 决策内部流程详解
+def run_uf_DQN_decide(uf_params, TMP0_value):
+    # Step A: 创建环境实例
+    env = UFSuperCycleEnv(uf_params)
+    
+    # Step B: 设置当前TMP
+    env.current_params.TMP0 = 0.032
+    env.last_action = (4500, 50)      # 使用历史动作
+    env.max_TMP_during_filtration = 0.045
+    
+    # Step C: 获取归一化状态
+    obs = env._get_obs()
+    # obs = [
+    #     (0.032 - 0.01) / 0.04 = 0.55,   # TMP0
+    #     (4500 - 3800) / 2200 = 0.318,   # last_L
+    #     (50 - 40) / 20 = 0.5,           # last_t_bw
+    #     (0.045 - 0.01) / 0.04 = 0.875   # max_TMP
+    # ]
+    # obs = [0.55, 0.318, 0.5, 0.875]
+    
+    # Step D: 模型预测(确定性,不探索)
+    obs_reshaped = obs.reshape(1, -1)  # [1, 4]
+    action, _states = model.predict(obs_reshaped, deterministic=True)
+    
+    # 模型内部执行:
+    # q_values = Q_network(obs_reshaped)  # [1, 185]
+    # 例如 q_values = [[0.45, 0.67, ..., 0.89, ..., -3.2]]
+    # action = argmax(q_values) = 105
+    # (选择Q值最大的动作)
+    
+    action = action[0]  # 105
+    
+    # Step E: 解码动作
+    L_s, t_bw_s = env._get_action_values(105)
+    # L_idx = 105 // 5 = 21
+    # t_bw_idx = 105 % 5 = 0
+    # L_s = 3800 + 21×60 = 5060秒
+    # t_bw_s = 40 + 0×5 = 40秒
+    
+    # Step F: 模拟验证(可选,检查可行性)
+    next_obs, reward, terminated, truncated, info = env.step(105)
+    
+    # Step G: 返回决策结果
+    return {
+        "action": 105,
+        "L_s": 5060.0,
+        "t_bw_s": 40.0,
+        "next_obs": next_obs,
+        "reward": reward,
+        "terminated": terminated,
+        "truncated": truncated,
+        "info": info
+    }
+
+# 3.3 获取决策结果
+print(f"""
+模型决策结果:
+- 建议产水时长:{result['L_s']} 秒 (约{result['L_s']/60:.1f}分钟)
+- 建议反洗时长:{result['t_bw_s']} 秒
+- 预期回收率:{result['info']['recovery']:.3f}
+- 预期净供水率:{result['info']['net_delivery_rate_m3ph']:.1f} m³/h
+- 预期周期最高TMP:{result['info']['max_TMP_during_filtration']:.4f} MPa
+""")
+
+# 输出示例:
+# 模型决策结果:
+# - 建议产水时长:5060 秒 (约84.3分钟)
+# - 建议反洗时长:40 秒
+# - 预期回收率:0.968
+# - 预期净供水率:332.5 m³/h
+# - 预期周期最高TMP:0.0485 MPa
+```
+
+---
+
+#### 步骤4:生成PLC指令(渐进式调整)
+
+为了避免参数突变导致系统不稳定,使用渐进式调整策略:
+
+```python
+from DQN_decide import generate_plc_instructions
+
+# 4.1 准备输入
+current_L_s = 4500      # 工厂当前设定值
+current_t_bw_s = 50     # 工厂当前设定值
+model_prev_L_s = 4800   # 模型上一轮建议值(如果有)
+model_prev_t_bw_s = 45  # 模型上一轮建议值
+model_L_s = 5060        # 模型本轮建议值
+model_t_bw_s = 40       # 模型本轮建议值
+
+# 4.2 生成渐进式指令
+next_L_s, next_t_bw_s = generate_plc_instructions(
+    current_L_s,
+    current_t_bw_s,
+    model_prev_L_s,
+    model_prev_t_bw_s,
+    model_L_s,
+    model_t_bw_s
+)
+
+# 4.3 内部逻辑详解
+def generate_plc_instructions(...):
+    # Step 1: 选择基准值(上一轮模型值 vs 当前值)
+    # 选择更接近本轮模型建议的那个
+    if abs(current_L_s - model_L_s) <= abs(model_prev_L_s - model_L_s):
+        effective_current_L = 4500  # 当前值更接近
+    else:
+        effective_current_L = 4800  # 上轮值更接近
+    
+    # 假设选择了4800
+    
+    # Step 2: 计算差异
+    L_diff = model_L_s - effective_current_L
+    # = 5060 - 4800 = 260秒
+    
+    # Step 3: 渐进调整(每次最多变化1个步长)
+    L_step_s = 60  # 步长60秒
+    threshold = 1.0
+    
+    if abs(L_diff) >= threshold * L_step_s:
+        if L_diff > 0:
+            L_adjustment = +60  # 向上调整
+        else:
+            L_adjustment = -60  # 向下调整
+    else:
+        L_adjustment = 0  # 差异小,不调整
+    
+    next_L_s = 4800 + 60 = 4860秒
+    
+    # 同样处理t_bw_s
+    # t_bw_diff = 40 - 45 = -5秒
+    # abs(-5) >= 1.0 × 5 → True
+    # t_bw_adjustment = -5
+    next_t_bw_s = 45 - 5 = 40秒
+    
+    return 4860, 40
+
+# 4.4 结果
+print(f"""
+PLC指令:
+- 下发产水时长:{next_L_s} 秒(从{effective_current_L}秒逐步调整)
+- 下发反洗时长:{next_t_bw_s} 秒
+- 调整方向:向模型建议值({model_L_s}秒, {model_t_bw_s}秒)靠拢
+- 需要继续调整轮数:约{abs(model_L_s - next_L_s) // 60}轮
+""")
+
+# 输出:
+# PLC指令:
+# - 下发产水时长:4860 秒(从4800秒逐步调整)
+# - 下发反洗时长:40 秒
+# - 调整方向:向模型建议值(5060秒, 40秒)靠拢
+# - 需要继续调整轮数:约3轮
+```
+
+**渐进调整的好处**:
+- ✅ 避免参数突变导致TMP急剧波动
+- ✅ 给操作员时间观察和干预
+- ✅ 系统更平稳过渡
+
+---
+
+#### 步骤5:计算预期性能指标
+
+在实际下发指令前,先模拟计算性能:
+
+```python
+from DQN_decide import calc_uf_cycle_metrics
+
+# 5.1 计算指令对应的性能
+TMP0 = 0.032
+max_tmp = 0.048  # 如果工厂有实测数据
+min_tmp = 0.025
+L_s = 4860
+t_bw_s = 40
+
+metrics = calc_uf_cycle_metrics(
+    uf_params,
+    TMP0,
+    max_tmp,
+    min_tmp,
+    L_s,
+    t_bw_s
+)
+
+# 5.2 内部计算流程
+def calc_uf_cycle_metrics(...):
+    # 模拟一个完整超级周期
+    feasible, info = simulate_one_supercycle(params, L_s, t_bw_s)
+    
+    # 提取关键指标
+    k_bw_per_ceb = info["k_bw_per_ceb"]  # 小周期次数
+    recovery = info["recovery"]            # 回收率
+    net_rate = info["net_delivery_rate_m3ph"]  # 净供水率
+    daily_prod_time = info["daily_prod_time_h"]  # 日均产水时间
+    ton_water_energy = info["ton_water_energy_kWh_per_m3"]  # 吨水电耗
+    
+    # 计算渗透率
+    if min_tmp is not None:
+        max_permeability = 100 * q_UF / (膜面积) / min_tmp
+    else:
+        max_permeability = info中计算
+    
+    return {
+        "k_bw_per_ceb": 36,
+        "recovery": 0.968,
+        "net_delivery_rate_m3ph": 332.8,
+        "daily_prod_time_h": 18.5,
+        "ton_water_energy_kWh_per_m3": 0.1019,
+        "max_permeability": 58.3  # lmh/bar
+    }
+
+# 5.3 展示结果
+print(f"""
+预期性能指标:
+- 小周期次数(48h内):{metrics['k_bw_per_ceb']}
+- 回收率:{metrics['recovery']:.2%}
+- 净供水率:{metrics['net_delivery_rate_m3ph']:.1f} m³/h
+- 日均产水时间:{metrics['daily_prod_time_h']:.1f} 小时/天
+- 吨水电耗:{metrics['ton_water_energy_kWh_per_m3']:.4f} kWh/m³
+- 最高渗透率:{metrics['max_permeability']:.1f} lmh/bar
+""")
+
+# 输出:
+# 预期性能指标:
+# - 小周期次数(48h内):36
+# - 回收率:96.80%
+# - 净供水率:332.8 m³/h
+# - 日均产水时间:18.5 小时/天
+# - 吨水电耗:0.1019 kWh/m³
+# - 最高渗透率:58.3 lmh/bar
+```
+
+---
+
+#### 步骤6:下发PLC指令
+
+```python
+# 6.1 准备PLC通信数据包
+plc_command = {
+    "timestamp": "2025-01-15 10:00:00",
+    "L_s": 4860,        # 产水时长(秒)
+    "t_bw_s": 40,       # 反洗时长(秒)
+    "source": "AI_DQN", # 指令来源
+    "confidence": 0.95  # 模型置信度
+}
+
+# 6.2 发送到PLC(伪代码)
+# plc_client.write_registers(
+#     address=1000,
+#     values=[4860, 40]
+# )
+
+# 6.3 记录操作日志
+log_decision(
+    timestamp="2025-01-15 10:00:00",
+    TMP0=0.032,
+    model_L_s=5060,
+    model_t_bw_s=40,
+    plc_L_s=4860,
+    plc_t_bw_s=40,
+    expected_recovery=0.968
+)
+```
+
+---
+
+#### 步骤7:周期性重复预测
+
+```python
+# 每个超级周期结束后(约48小时)重新预测
+
+while True:
+    # 等待当前周期结束
+    wait_for_cycle_end()
+    
+    # 获取最新系统状态
+    current_state = get_system_state()
+    
+    # 执行决策
+    result = run_uf_DQN_decide(uf_params, current_state.TMP0)
+    
+    # 渐进调整
+    next_L, next_t_bw = generate_plc_instructions(...)
+    
+    # 下发指令
+    send_to_plc(next_L, next_t_bw)
+    
+    # 记录日志
+    log_decision(...)
+    
+    # 等待下一周期
+    sleep(48_hours)
+```
+
+---
+
+## 从训练到部署的完整链路
+
+### 完整流程图
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│                    阶段1:离线训练                           │
+└─────────────────────────────────────────────────────────────┘
+
+1. 数据准备
+   ├─ 历史运行数据(可选,用于物理模型)
+   └─ 系统参数配置(UFParams)
+
+2. 模拟器开发
+   ├─ 物理模型:TMP增长、反洗恢复
+   └─ 约束检查:TMP上限、残余增量
+
+3. 强化学习训练
+   ├─ 环境:UFSuperCycleEnv
+   ├─ 算法:DQN
+   ├─ 训练:50000步,约数小时
+   └─ 输出:dqn_model.zip
+
+4. 模型验证
+   ├─ 测试不同TMP条件
+   ├─ 检查约束满足率
+   └─ 评估性能指标
+
+        ↓
+
+┌─────────────────────────────────────────────────────────────┐
+│                    阶段2:在线部署                           │
+└─────────────────────────────────────────────────────────────┘
+
+5. 部署准备
+   ├─ 将dqn_model.zip部署到服务器
+   ├─ 配置PLC通信接口
+   └─ 搭建监控系统
+
+6. 实时决策循环
+   Every 48 hours:
+   ├─ 从SCADA读取TMP0
+   ├─ 调用run_uf_DQN_decide()
+   ├─ 生成PLC指令(渐进式)
+   ├─ 下发到PLC
+   └─ 记录日志
+
+7. 持续监控
+   ├─ 实时性能追踪
+   ├─ 异常告警
+   └─ 人工干预接口
+
+        ↓
+
+┌─────────────────────────────────────────────────────────────┐
+│                    阶段3:持续优化                           │
+└─────────────────────────────────────────────────────────────┘
+
+8. 数据收集
+   ├─ 记录实际运行数据
+   ├─ 标注异常事件
+   └─ 构建真实数据集
+
+9. 模型迭代
+   ├─ 用真实数据训练物理模型
+   ├─ 重新训练强化学习策略
+   └─ A/B测试新旧模型
+
+10. 上线新版本
+    ├─ 灰度发布
+    ├─ 性能对比
+    └─ 全量替换
+```
+
+---
+
+### 训练vs预测对比表
+
+| 维度 | 训练阶段 | 预测阶段 |
+|------|---------|---------|
+| **目标** | 学习最优策略 | 应用最优策略 |
+| **输入** | 随机初始状态 | 真实系统状态 |
+| **动作选择** | ε-贪心(探索+利用) | 贪心(纯利用) |
+| **奖励** | 计算并用于学习 | 计算但不学习 |
+| **网络更新** | 每4步更新参数 | 不更新参数 |
+| **经验池** | 存储并采样 | 不使用 |
+| **epsilon** | 1.0 → 0.02 | 0(不探索) |
+| **时间** | 数小时(50000步) | 毫秒级(单次推理) |
+| **输出** | dqn_model.zip | (L_s, t_bw_s) |
+
+---
+
+### 关键差异示例
+
+#### 训练时的动作选择(第1000步)
+```python
+state = [0.55, 0.318, 0.5, 0.875]
+epsilon = 0.93  # 仍在高探索期
+
+if random() < 0.93:
+    action = 127  # 93%概率:随机探索
+else:
+    q_values = Q_network(state)
+    action = argmax(q_values)  # 7%概率:利用
+
+# 即使知道最优动作,也故意选择次优动作来探索
+```
+
+#### 预测时的动作选择
+```python
+state = [0.55, 0.318, 0.5, 0.875]
+epsilon = 0  # 预测时不探索
+
+# 总是选择Q值最大的动作
+q_values = Q_network(state)
+# q_values = [0.45, ..., 0.89, ..., 0.76, ...]
+action = argmax(q_values)  # 一定选择最优
+
+# 确定性决策,相同状态总是输出相同动作
+```
+
+---
+
+## 常见问题解答
+
+### Q1:为什么训练需要50000步?
+**A**:
+- 185个动作 × 多种状态 = 需要大量经验
+- 前30%(15000步)主要是探索,发现好动作
+- 后70%(35000步)是优化,微调策略
+- 太少(如5000步)策略不稳定,太多(如500000步)训练时间长且收益递减
+
+### Q2:预测速度有多快?
+**A**:
+```python
+import time
+
+start = time.time()
+result = run_uf_DQN_decide(uf_params, 0.032)
+end = time.time()
+
+print(f"预测耗时:{(end-start)*1000:.2f} 毫秒")
+# 典型输出:预测耗时:15.32 毫秒
+
+# 分解:
+# - 状态归一化:<1ms
+# - Q网络前向传播:5-10ms
+# - 动作解码:<1ms
+# - 模拟验证:5-10ms
+```
+
+### Q3:模型何时需要重新训练?
+**A**:以下情况需要重训:
+1. **系统参数变化**:更换膜组、改变流量范围
+2. **物理模型更新**:有了真实数据,改进模拟器
+3. **性能下降**:实际回收率持续低于预期
+4. **新约束**:增加了新的运行限制
+
+通常**3-6个月**重训一次。
+
+### Q4:如何确保预测的动作安全?
+**A**:多重保障:
+```python
+# 1. 训练时的约束学习
+# 模型在训练时已经学会避免违反约束
+
+# 2. 预测后的模拟验证
+feasible, info = simulate_one_supercycle(TMP0, L_s, t_bw_s)
+if not feasible:
+    # 回退到保守策略
+    L_s, t_bw_s = safe_default_action()
+
+# 3. 渐进式调整
+# 每次只调整60秒,避免突变
+
+# 4. 人工监督
+# 操作员可以随时覆盖模型决策
+```
+
+### Q5:训练时为什么要探索?
+**A**:
+```
+假设没有探索(epsilon=0):
+
+初始Q值是随机的,假设:
+- action 50 的Q值 = 0.8(最高)
+- action 92 的Q值 = 0.3
+- action 120 的Q值 = 0.5
+
+智能体会一直选择action 50,永远不会尝试92和120。
+
+但实际上:
+- action 50 的真实价值 = 0.6(不够好)
+- action 92 的真实价值 = 0.9(最优!)
+- action 120 的真实价值 = 0.7
+
+没有探索,永远发现不了action 92才是最优的。
+
+有探索:
+- 前期随机尝试各种动作
+- 发现action 92 获得高奖励
+- 更新Q值:Q(92) = 0.3 → 0.9
+- 后期选择action 92
+```
+
+### Q6:预测可以并行吗?
+**A**:可以,但要注意:
+```python
+# 单个预测(串行)
+result = run_uf_DQN_decide(uf_params, TMP0)
+
+# 批量预测(并行,需要修改代码)
+TMP0_batch = [0.025, 0.030, 0.035, 0.040]
+results = run_uf_DQN_decide_batch(uf_params, TMP0_batch)
+
+# 内部并行:
+obs_batch = np.array([
+    [0.375, 0.0, 0.0, 0.375],
+    [0.55, 0.2, 0.3, 0.6],
+    ...
+])  # [4, 4]
+
+q_values = Q_network(obs_batch)  # [4, 185]
+actions = q_values.argmax(dim=1)  # [4]
+
+# GPU加速:
+# PyTorch自动利用GPU并行计算
+# 批量预测速度几乎和单次一样快
+```
+
+---
+
+## 总结
+
+### 训练流程核心
+1. **准备阶段**:创建环境、初始化Q网络
+2. **探索阶段**(0-15000步):随机尝试,填充经验池
+3. **学习阶段**(200-50000步):从经验中学习,更新Q值
+4. **优化阶段**(15000-50000步):利用为主,微调策略
+5. **保存模型**:导出dqn_model.zip
+
+### 预测流程核心
+1. **加载模型**:读取训练好的Q网络
+2. **获取状态**:从系统读取TMP等信息
+3. **模型推理**:Q网络计算,选择最优动作
+4. **渐进调整**:生成PLC指令,逐步靠近目标
+5. **下发执行**:发送到PLC,控制超滤系统
+
+### 关键洞察
+- **训练是学习过程**:智能体从"一无所知"到"经验丰富"
+- **预测是应用过程**:智能体"发挥所学"解决实际问题
+- **探索是必要代价**:没有探索就没有发现
+- **渐进是安全保障**:避免参数突变引发风险
+

+ 1160 - 0
models/uf-rl/训练/uf_train/UF_RL_详细技术文档.md

@@ -0,0 +1,1160 @@
+# UF-RL 强化学习系统详细技术文档
+
+## 目录
+1. [整体架构](#整体架构)
+2. [代码结构分析](#代码结构分析)
+3. [训练流程详解](#训练流程详解)
+4. [关键代码解析](#关键代码解析)
+5. [数据流与控制流](#数据流与控制流)
+
+---
+
+## 整体架构
+
+### 系统组成
+
+```
+┌─────────────────────────────────────────────────────────┐
+│                     DQN训练系统                         │
+│                                                         │
+│  ┌──────────────┐      ┌──────────────┐              │
+│  │  DQN_train   │─────▶│   DQN Agent  │              │
+│  │  (训练脚本)  │      │ (神经网络)   │              │
+│  └──────────────┘      └──────────────┘              │
+│         │                      │                       │
+│         │                      │ predict               │
+│         ▼                      ▼                       │
+│  ┌──────────────────────────────────────┐             │
+│  │         UFSuperCycleEnv              │             │
+│  │         (强化学习环境)               │             │
+│  │                                      │             │
+│  │  ┌────────────────────────────────┐ │             │
+│  │  │   simulate_one_supercycle()    │ │             │
+│  │  │   (物理模拟器)                 │ │             │
+│  │  │                                │ │             │
+│  │  │  ┌──────────┐  ┌──────────┐  │ │             │
+│  │  │  │model_fp  │  │model_bw  │  │ │             │
+│  │  │  │TMP增长   │  │反洗恢复  │  │ │             │
+│  │  │  └──────────┘  └──────────┘  │ │             │
+│  │  └────────────────────────────────┘ │             │
+│  └──────────────────────────────────────┘             │
+│         │                                              │
+│         ▼                                              │
+│  ┌──────────────┐                                     │
+│  │  Callback    │                                     │
+│  │  (记录器)    │                                     │
+│  └──────────────┘                                     │
+└─────────────────────────────────────────────────────────┘
+```
+
+### 文件职责
+
+| 文件 | 职责 | 核心类/函数 |
+|------|------|------------|
+| `DQN_train.py` | 训练入口、参数配置、训练循环 | `DQNTrainer`, `train_uf_rl_agent()` |
+| `DQN_env.py` | 强化学习环境、物理模拟 | `UFSuperCycleEnv`, `simulate_one_supercycle()` |
+| `UF_models.py` | TMP动力学模型 | `TMPIncreaseModel`, `TMPDecreaseModel` |
+| `DQN_decide.py` | 推理决策接口 | `run_uf_DQN_decide()`, `generate_plc_instructions()` |
+
+---
+
+## 代码结构分析
+
+### 1. UF_models.py - 物理模型层
+
+#### TMPIncreaseModel:TMP增长模型
+
+```python
+class TMPIncreaseModel(torch.nn.Module):
+    def forward(self, p, L_h):
+        # 简化的膜污染动力学公式
+        return float(p.alpha * (p.q_UF ** p.belta) * L_h)
+```
+
+**公式解释**:
+```
+ΔTMP = α × Q^β × t
+
+其中:
+- α (alpha):污染系数(1e-6)
+- Q (q_UF):进水流量(360 m³/h)
+- β (belta):幂指数(1.1)
+- t (L_h):过滤时间(小时)
+```
+
+**物理含义**:
+- TMP增长与过滤时间线性相关
+- 流量越大,污染速率越快(幂律关系)
+- 假设污染速率恒定(实际会随时间变化)
+
+#### TMPDecreaseModel:反洗恢复模型
+
+```python
+class TMPDecreaseModel(torch.nn.Module):
+    def forward(self, p, L_s, t_bw_s):
+        # 反洗去除比例上界(随过滤时长衰减)
+        upper_L = phi_bw_min + (phi_bw_max - phi_bw_min) * exp(-L / L_ref)
+        
+        # 反洗时长增益(饱和曲线)
+        time_gain = 1 - exp(-(t / tau_bw) ^ gamma_t)
+        
+        # 实际去除比例
+        phi = upper_L × time_gain
+        return clip(phi, 0.0, 0.999)
+```
+
+**公式解释**:
+```
+φ(L, t) = [φ_min + (φ_max - φ_min) × e^(-L/L_ref)] × [1 - e^(-(t/τ)^γ)]
+
+其中:
+- φ_min = 0.7:最小去除比例(长时间过滤后)
+- φ_max = 1.0:最大去除比例(短时间过滤)
+- L_ref = 4000s:过滤时长影响的时间尺度
+- τ = 20s:反洗时长影响的时间尺度
+- γ = 1.0:反洗时长作用指数
+```
+
+**物理含义**:
+- 过滤时间越长,污染越难清除(upper_L下降)
+- 反洗时间越长,去除效果越好(time_gain上升)
+- 存在饱和效应:反洗超过一定时间后效果不再显著提升
+
+**关键问题**:
+⚠️ 这两个"模型"实际上是**数学公式**,不是神经网络!
+- 没有可训练的参数(state_dict为空)
+- 保存为.pth文件没有实际意义
+- 完全基于人工设定的公式,可能与真实系统有偏差
+
+---
+
+### 2. DQN_env.py - 环境层
+
+#### UFParams:系统参数配置
+
+```python
+@dataclass
+class UFParams:
+    # 膜运行参数
+    q_UF: float = 360.0        # 进水流量
+    TMP0: float = 0.03         # 初始TMP
+    TMP_max: float = 0.06      # TMP上限
+    
+    # 污染动力学参数
+    alpha: float = 1e-6        # TMP增长系数
+    belta: float = 1.1         # 幂指数
+    
+    # 反洗参数
+    q_bw_m3ph: float = 1000.0  # 反洗流量
+    
+    # CEB参数
+    T_ceb_interval_h: float = 48.0  # CEB间隔
+    v_ceb_m3: float = 30.0     # CEB用水
+    t_ceb_s: float = 2400.0    # CEB时长
+    
+    # 约束
+    dTMP: float = 0.001        # 单次残余增量上限
+    
+    # 动作空间
+    L_min_s: float = 3800.0    # 过滤时长下限
+    L_max_s: float = 6000.0    # 过滤时长上限
+    t_bw_min_s: float = 40.0   # 反洗时长下限
+    t_bw_max_s: float = 60.0   # 反洗时长上限
+    
+    # 奖励权重
+    w_rec: float = 0.8         # 回收率权重
+    w_rate: float = 0.2        # 净供水率权重
+    w_headroom: float = 0.2    # TMP贴边惩罚权重
+```
+
+#### simulate_one_supercycle():核心物理模拟器
+
+```python
+def simulate_one_supercycle(p: UFParams, L_s: float, t_bw_s: float):
+    """
+    模拟一个完整的超级周期(多个小周期 + 1次CEB)
+    
+    输入:
+        p: 系统参数
+        L_s: 单次产水时长(秒)
+        t_bw_s: 单次反洗时长(秒)
+    
+    输出:
+        (feasible, info)
+        - feasible: 是否满足所有约束
+        - info: 性能指标字典
+    """
+```
+
+**执行流程**:
+
+```
+1. 初始化
+   tmp = TMP0
+   max_tmp = TMP0
+   min_tmp = TMP0
+
+2. 计算小周期次数
+   小周期时长 = L_s + t_bw_s
+   k_bw_per_ceb = floor(48小时 / 小周期时长)
+
+3. 循环k_bw_per_ceb次(多个小周期)
+   For i in range(k_bw_per_ceb):
+       3.1 产水阶段
+           tmp_start = tmp
+           Δtmp = model_fp(L_h)  # 计算TMP增长
+           tmp_peak = tmp_start + Δtmp
+           
+           约束检查1:tmp_peak ≤ TMP_max
+           If 违反: return False
+           
+       3.2 反洗阶段
+           φ = model_bw(L_s, t_bw_s)  # 计算去除比例
+           tmp_after_bw = tmp_peak - φ × (tmp_peak - tmp_start)
+           
+           约束检查2:(tmp_after_bw - tmp_start) ≤ dTMP
+           If 违反: return False
+           
+       3.3 更新TMP
+           tmp = tmp_after_bw
+           更新max_tmp和min_tmp
+
+4. CEB阶段
+   tmp = TMP0  # 完全恢复
+
+5. 计算性能指标
+   V_feed = k × q_UF × L_h           # 总进水
+   V_loss = k × V_bw + V_ceb         # 总损失
+   V_net = V_feed - V_loss            # 净产水
+   
+   recovery = V_net / V_feed          # 回收率
+   net_rate = V_net / T_super         # 净供水率
+   
+   吨水电耗 = 查表(L_s)
+   日均产水时间 = (k × L_h / T_super) × 24
+
+6. 贴边检查
+   headroom_ratio = max_tmp / TMP_max
+   If headroom_ratio > 0.98: return False
+
+7. 返回结果
+   return True, {
+       "recovery": recovery,
+       "net_delivery_rate_m3ph": net_rate,
+       "max_TMP_during_filtration": max_tmp,
+       ...
+   }
+```
+
+**约束体系**:
+
+| 约束 | 检查点 | 物理含义 |
+|------|--------|---------|
+| TMP峰值 ≤ 0.06 MPa | 产水后 | 防止膜破裂 |
+| 单次残余增量 ≤ 0.001 MPa | 反洗后 | 控制污染累积速率 |
+| TMP贴边 < 98% | 周期结束 | 保留安全余量 |
+
+#### _score():奖励函数
+
+```python
+def _score(p: UFParams, rec: dict) -> float:
+    # 1. 归一化净供水率
+    rate_norm = rec["net_delivery_rate_m3ph"] / p.q_UF
+    
+    # 2. TMP软惩罚(sigmoid)
+    tmp_ratio = rec["max_TMP"] / p.TMP_max
+    k = 10.0
+    headroom_penalty = 1 / (1 + exp(-k × (tmp_ratio - 1.0)))
+    
+    # 3. 基础奖励(加权和)
+    base_reward = (
+        0.8 × recovery 
+        + 0.2 × rate_norm 
+        - 0.2 × headroom_penalty
+    )
+    # 典型范围:0.6 ~ 0.9
+    
+    # 4. 非线性放大
+    amplified = (base_reward - 0.5)² × 5.0
+    
+    # 5. 保留符号
+    if base_reward < 0.5:
+        amplified = -amplified
+    
+    return amplified
+```
+
+**奖励设计逻辑**:
+
+```
+目标1:高回收率(主要)
+    - 回收率接近1 → 高奖励
+    - 权重0.8
+
+目标2:高净供水率(次要)
+    - 净供水率/进水流量 → 归一化到0-1
+    - 权重0.2
+
+惩罚:TMP贴边
+    - TMP接近上限 → sigmoid惩罚
+    - TMP超过上限 → 惩罚急剧增大
+    - 权重0.2
+
+非线性变换目的:
+    - 放大好动作和坏动作的差异
+    - 让Q值学习更快
+    - 典型奖励范围:-1.25 ~ 0.8
+```
+
+**奖励曲线分析**:
+
+```python
+base_reward = 0.85 → amplified = (0.85-0.5)²×5 = 0.6125
+base_reward = 0.70 → amplified = (0.70-0.5)²×5 = 0.2000
+base_reward = 0.50 → amplified = 0.0000
+base_reward = 0.30 → amplified = -(0.30-0.5)²×5 = -0.2000
+```
+
+**问题**:
+⚠️ 非线性变换可能导致:
+- Q值估计不稳定
+- 梯度爆炸/消失
+- 不同状态下的奖励尺度差异过大
+
+#### UFSuperCycleEnv:强化学习环境
+
+```python
+class UFSuperCycleEnv(gym.Env):
+    """
+    Gym标准环境接口
+    """
+    
+    def __init__(self, base_params, max_episode_steps=20):
+        # 离散动作空间
+        L_values = arange(3800, 6001, 60)  # 37个选项
+        t_bw_values = arange(40, 61, 5)    # 5个选项
+        self.action_space = Discrete(37 × 5 = 185)
+        
+        # 连续状态空间(归一化到[0,1])
+        self.observation_space = Box(
+            low=0, high=1, shape=(4,)
+        )
+```
+
+**状态定义**:
+
+```python
+def _get_obs(self):
+    # 状态向量:[TMP0, last_L, last_t_bw, max_TMP]
+    return [
+        (TMP0 - 0.01) / (0.05 - 0.01),           # 当前初始TMP
+        (L_s - 3800) / (6000 - 3800),            # 上次产水时长
+        (t_bw_s - 40) / (60 - 40),               # 上次反洗时长
+        (max_TMP - 0.01) / (0.05 - 0.01)         # 本周期最高TMP
+    ]
+```
+
+**状态空间分析**:
+
+| 维度 | 物理意义 | 归一化范围 | 作用 |
+|------|---------|-----------|------|
+| TMP0 | 当前初始压差 | [0.01, 0.05] MPa | 主要状态,决定可行动作范围 |
+| last_L | 上次产水时长 | [3800, 6000] s | 历史信息,捕捉趋势 |
+| last_t_bw | 上次反洗时长 | [40, 60] s | 历史信息,捕捉趋势 |
+| max_TMP | 周期最高TMP | [0.01, 0.05] MPa | 安全信息,避免贴边 |
+
+**动作映射**:
+
+```python
+def _get_action_values(self, action):
+    # action ∈ [0, 184]
+    L_idx = action // 5       # 过滤时长索引
+    t_bw_idx = action % 5      # 反洗时长索引
+    
+    L_s = 3800 + L_idx × 60    # 3800, 3860, ..., 6000
+    t_bw_s = 40 + t_bw_idx × 5 # 40, 45, 50, 55, 60
+    
+    return (L_s, t_bw_s)
+```
+
+**训练循环**:
+
+```python
+def reset(self):
+    # 随机初始TMP(增加训练多样性)
+    self.TMP0 = uniform(0.01, 0.03)
+    self.current_step = 0
+    self.last_action = (3800, 40)  # 初始为最保守动作
+    return self._get_obs()
+
+def step(self, action):
+    self.current_step += 1
+    
+    # 1. 解码动作
+    L_s, t_bw_s = self._get_action_values(action)
+    
+    # 2. 执行模拟
+    feasible, info = simulate_one_supercycle(
+        self.current_params, L_s, t_bw_s
+    )
+    
+    # 3. 计算奖励
+    if feasible:
+        reward = _score(self.current_params, info)
+        self.TMP0 = info["TMP_after_ceb"]  # 更新状态
+        terminated = False
+    else:
+        reward = -20  # 约束违反大惩罚
+        terminated = True
+    
+    # 4. 检查截断
+    truncated = (self.current_step >= 20)
+    
+    # 5. 返回
+    return next_obs, reward, terminated, truncated, info
+```
+
+**Episode流程示意**:
+
+```
+reset() → TMP0=0.025
+  ↓
+step(action=92) → (L=4900, t_bw=50) → reward=0.45 → TMP0=0.025
+  ↓
+step(action=105) → (L=5160, t_bw=45) → reward=0.52 → TMP0=0.026
+  ↓
+...(最多20步)
+  ↓
+truncated=True → episode结束
+```
+
+---
+
+### 3. DQN_train.py - 训练层
+
+#### DQNParams:训练超参数
+
+```python
+class DQNParams:
+    learning_rate = 1e-4           # Adam学习率
+    buffer_size = 10000            # 经验回放池大小
+    learning_starts = 200          # 开始学习前的随机探索步数
+    batch_size = 32                # 每次训练采样数
+    gamma = 0.95                   # 折扣因子
+    train_freq = 4                 # 每4步训练一次
+    target_update_interval = 2000  # 目标网络更新间隔
+    exploration_initial_eps = 1.0  # 初始探索率
+    exploration_fraction = 0.3     # 探索衰减比例
+    exploration_final_eps = 0.02   # 最终探索率
+```
+
+**参数含义详解**:
+
+| 参数 | 作用 | 典型值 | 当前值 | 评价 |
+|------|------|--------|--------|------|
+| learning_rate | 梯度下降步长 | 1e-4~1e-3 | 1e-4 | ✓ 合理 |
+| buffer_size | 经验池容量 | 10k~1M | 10k | ⚠️ 偏小 |
+| learning_starts | 预填充步数 | 1k~10k | 200 | ⚠️ 太小 |
+| batch_size | SGD批大小 | 32~256 | 32 | ✓ 合理 |
+| gamma | 未来奖励折扣 | 0.9~0.99 | 0.95 | ✓ 合理 |
+| train_freq | 训练频率 | 1~16 | 4 | ✓ 合理 |
+| target_update | 目标网络同步 | 1k~10k | 2000 | ⚠️ 代码冲突 |
+| exploration | 探索策略 | 前20-50% | 前30% | ✓ 合理 |
+
+#### DQNTrainer:训练器
+
+```python
+class DQNTrainer:
+    def __init__(self, env, params, callback=None):
+        self.env = env
+        self.params = params
+        self.callback = callback
+        
+        # 创建日志目录
+        self.log_dir = self._create_log_dir()
+        
+        # 创建DQN模型
+        self.model = self._create_model()
+```
+
+**模型创建**:
+
+```python
+def _create_model(self):
+    return DQN(
+        policy="MlpPolicy",  # 多层感知机
+        env=self.env,
+        learning_rate=1e-4,
+        buffer_size=10000,
+        learning_starts=200,
+        batch_size=32,
+        gamma=0.95,
+        train_freq=4,
+        
+        # ⚠️ 注意:这里有冲突
+        target_update_interval=1,  # 硬编码为1
+        tau=0.005,                 # soft update参数
+        
+        exploration_initial_eps=1.0,
+        exploration_fraction=0.3,
+        exploration_final_eps=0.02,
+        verbose=1,
+        tensorboard_log=self.log_dir
+    )
+```
+
+**目标网络更新策略冲突**:
+
+```python
+# 参数说明的是:
+target_update_interval = 2000  # 每2000步硬更新
+
+# 但代码实际使用:
+target_update_interval = 1     # 每1步软更新
+tau = 0.005                    # 软更新系数
+
+# 软更新公式:
+θ_target = τ × θ_current + (1-τ) × θ_target
+```
+
+**两种更新策略对比**:
+
+| 策略 | 优点 | 缺点 | 适用场景 |
+|------|------|------|---------|
+| 硬更新 | 稳定性好 | 更新滞后 | 经典DQN |
+| 软更新 | 平滑收敛 | 可能不稳定 | DDPG/TD3 |
+
+当前代码实际使用**软更新**,但注释说明是硬更新,存在混淆。
+
+#### 训练主流程
+
+```python
+def train(self, total_timesteps: int):
+    self.model.learn(
+        total_timesteps=total_timesteps,
+        callback=self.callback
+    )
+```
+
+**Stable-Baselines3内部流程**(简化):
+
+```python
+# learn() 内部逻辑
+for step in range(total_timesteps):
+    # 1. ε-贪心选择动作
+    if random() < epsilon:
+        action = env.action_space.sample()  # 探索
+    else:
+        action = argmax(Q_network(state))    # 利用
+    
+    # 2. 执行动作
+    next_state, reward, done, info = env.step(action)
+    
+    # 3. 存入经验池
+    replay_buffer.add(state, action, reward, next_state, done)
+    
+    # 4. 训练(每train_freq=4步一次)
+    if step % 4 == 0 and step > learning_starts:
+        # 从经验池采样
+        batch = replay_buffer.sample(batch_size=32)
+        
+        # 计算TD目标
+        with torch.no_grad():
+            q_next = Q_target(next_state).max(dim=1)
+            target = reward + gamma × q_next × (1 - done)
+        
+        # 计算当前Q值
+        q_current = Q_network(state)[action]
+        
+        # 计算损失
+        loss = MSE(q_current, target)
+        
+        # 反向传播
+        optimizer.zero_grad()
+        loss.backward()
+        optimizer.step()
+    
+    # 5. 软更新目标网络(每1步)
+    Q_target = tau × Q_network + (1-tau) × Q_target
+    
+    # 6. 衰减epsilon
+    epsilon = max(
+        epsilon_final,
+        epsilon_initial - step / (total_steps × exploration_fraction)
+    )
+    
+    # 7. 回调记录
+    callback.on_step()
+    
+    # 8. Episode重置
+    if done:
+        state = env.reset()
+```
+
+#### UFTrainingCallback:训练回调
+
+```python
+class UFTrainingCallback(BaseCallback):
+    def __init__(self, recorder, verbose=0):
+        self.recorder = recorder
+    
+    def _on_step(self) -> bool:
+        # 从locals获取当前步信息
+        obs = self.locals.get("new_obs")[0]
+        action = self.locals.get("actions")[0]
+        reward = self.locals.get("rewards")[0]
+        done = self.locals.get("dones")[0]
+        info = self.locals.get("infos")[0]
+        
+        # 记录到recorder
+        self.recorder.record_step(obs, action, reward, done, info)
+        
+        # 打印(如果verbose=1)
+        if self.verbose:
+            print(f"[Step {self.num_timesteps}] "
+                  f"action={action}, reward={reward:.3f}, done={done}")
+        
+        return True  # 继续训练
+```
+
+**记录器UFEpisodeRecorder**:
+
+```python
+class UFEpisodeRecorder:
+    def __init__(self):
+        self.episode_data = []      # 所有episode的记录
+        self.current_episode = []   # 当前episode的步数据
+    
+    def record_step(self, obs, action, reward, done, info):
+        step_data = {
+            "obs": obs,
+            "action": action,
+            "reward": reward,
+            "done": done,
+            "info": info
+        }
+        self.current_episode.append(step_data)
+        
+        if done:
+            self.episode_data.append(self.current_episode)
+            self.current_episode = []
+    
+    def get_episode_stats(self, episode_idx=-1):
+        episode = self.episode_data[episode_idx]
+        return {
+            "total_reward": sum(step["reward"] for step in episode),
+            "avg_recovery": mean([step["info"]["recovery"] for step in episode]),
+            "feasible_steps": sum(1 for s in episode if s["info"]["feasible"]),
+            "total_steps": len(episode)
+        }
+```
+
+---
+
+## 训练流程详解
+
+### 完整训练流程图
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│                      训练流程                               │
+└─────────────────────────────────────────────────────────────┘
+
+1. 初始化阶段
+   ├─ set_global_seed(2025)          # 固定随机种子
+   ├─ params = UFParams()             # 创建系统参数
+   ├─ env = UFSuperCycleEnv(params)   # 创建环境
+   ├─ env = Monitor(env)              # 包装监控
+   ├─ env = DummyVecEnv([env])        # 向量化环境
+   ├─ recorder = UFEpisodeRecorder()  # 创建记录器
+   ├─ callback = UFTrainingCallback() # 创建回调
+   └─ model = DQN(...)                # 创建DQN模型
+
+2. 训练循环(50000步)
+   For step = 1 to 50000:
+       ├─ 探索vs利用决策
+       │   If random() < epsilon(step):
+       │       action = random([0, 184])     # 探索
+       │   Else:
+       │       state_tensor = torch.FloatTensor(state)
+       │       q_values = Q_network(state_tensor)  # [185]
+       │       action = argmax(q_values)     # 利用
+       │
+       ├─ 执行动作
+       │   L_s, t_bw_s = decode_action(action)
+       │   next_state, reward, done, info = env.step(action)
+       │
+       ├─ 存储经验
+       │   replay_buffer.add(state, action, reward, next_state, done)
+       │
+       ├─ 训练网络(每4步,且step > 200)
+       │   If step % 4 == 0 and step > 200:
+       │       batch = replay_buffer.sample(32)
+       │       ├─ 前向传播
+       │       │   q_current = Q_network(batch.state)[batch.action]
+       │       │   q_next_max = Q_target(batch.next_state).max()
+       │       │   target = batch.reward + 0.95 × q_next_max × (1 - batch.done)
+       │       ├─ 计算损失
+       │       │   loss = MSE(q_current, target)
+       │       ├─ 反向传播
+       │       │   optimizer.zero_grad()
+       │       │   loss.backward()
+       │       │   optimizer.step()
+       │       └─ 软更新目标网络
+       │           Q_target ← 0.005×Q_network + 0.995×Q_target
+       │
+       ├─ 衰减epsilon
+       │   epsilon = max(0.02, 1.0 - step/15000)  # 前30%线性衰减
+       │
+       ├─ 记录数据
+       │   callback.on_step()  # 记录obs, action, reward
+       │
+       └─ Episode结束处理
+           If done or truncated:
+               state = env.reset()  # 重置环境
+               recorder.save_episode()
+
+3. 保存模型
+   ├─ model.save("dqn_model.zip")
+   └─ print(statistics)
+```
+
+### 训练时间线分析
+
+假设训练50000步,每个episode平均10步:
+
+```
+步数范围     | epsilon | 训练行为           | 说明
+------------|---------|-------------------|------------------
+0-200       | 1.0     | 纯随机探索         | 预填充经验池
+200-15000   | 1.0→0.02| 探索衰减期         | 逐渐从探索转向利用
+15000-50000 | 0.02    | 基本利用,2%探索   | 稳定策略优化
+
+训练触发:
+- 0-200步:不训练,仅收集经验
+- 200-50000步:每4步训练1次 → 共12450次梯度更新
+
+目标网络更新:
+- 每1步软更新,tau=0.005
+- 相当于每200步目标网络更新约63%
+```
+
+### 关键时刻详解
+
+#### Episode开始
+
+```python
+state = env.reset()
+# 环境内部执行:
+TMP0 = uniform(0.01, 0.03)  # 随机初始TMP
+current_step = 0
+last_action = (3800, 40)
+max_TMP = TMP0
+
+obs = [
+    (TMP0 - 0.01) / 0.04,    # 例:0.025 → 0.375
+    (3800 - 3800) / 2200,    # 0.0
+    (40 - 40) / 20,          # 0.0
+    (TMP0 - 0.01) / 0.04     # 0.375
+]
+```
+
+#### 第1步
+
+```python
+# 1. 动作选择(epsilon=1.0,纯探索)
+action = random.randint(0, 184)  # 假设选到92
+
+# 2. 解码动作
+L_idx = 92 // 5 = 18
+t_bw_idx = 92 % 5 = 2
+L_s = 3800 + 18×60 = 4880
+t_bw_s = 40 + 2×5 = 50
+
+# 3. 模拟执行
+simulate_one_supercycle(p, 4880, 50):
+    L_h = 4880 / 3600 = 1.356h
+    k_bw = floor(48 / ((4880+50)/3600)) = 35次
+    
+    For i in range(35):
+        # 产水
+        dtmp = 1e-6 × 360^1.1 × 1.356 = 0.00074 MPa
+        tmp_peak = 0.025 + 0.00074 = 0.02574 MPa
+        
+        # 检查约束
+        tmp_peak < 0.06 ✓
+        
+        # 反洗
+        phi = 0.836  # 通过model_bw计算
+        tmp_after = 0.02574 - 0.836×0.00074 = 0.02512 MPa
+        
+        # 检查约束
+        residual = 0.02512 - 0.025 = 0.00012 < 0.001 ✓
+        
+        tmp = 0.02512
+    
+    # CEB
+    tmp = 0.025
+    
+    # 计算指标
+    V_feed = 35 × 360 × 1.356 = 17089 m³
+    V_loss = 35 × (1000×50/3600) + 30 = 517 m³
+    V_net = 16572 m³
+    recovery = 0.970
+    net_rate = 16572 / 49.0 = 338.2 m³/h
+    
+    return True, {recovery: 0.970, net_rate: 338.2, ...}
+
+# 4. 计算奖励
+rate_norm = 338.2 / 360 = 0.939
+headroom_penalty = 1/(1+exp(-10×(0.02574/0.06-1))) = 0.00 (TMP很低)
+base_reward = 0.8×0.970 + 0.2×0.939 - 0.2×0.00 = 0.964
+amplified = (0.964-0.5)² × 5 = 1.076
+
+reward = 1.076  # 非常好的奖励!
+
+# 5. 下一状态
+TMP0_new = 0.025
+obs_new = [0.375, (4880-3800)/2200=0.491, (50-40)/20=0.5, 0.429]
+
+# 6. 存储经验
+buffer.add(
+    state=[0.375, 0.0, 0.0, 0.375],
+    action=92,
+    reward=1.076,
+    next_state=[0.375, 0.491, 0.5, 0.429],
+    done=False
+)
+
+# 7. 不训练(step=1 < 200)
+```
+
+#### 第204步(首次训练)
+
+```python
+# 此时经验池已有204条经验,开始训练
+
+# 1. 从经验池随机采样32条
+batch = buffer.sample(32)
+# batch.state: [32, 4]
+# batch.action: [32]
+# batch.reward: [32]
+# batch.next_state: [32, 4]
+# batch.done: [32]
+
+# 2. 计算当前Q值
+state_tensor = torch.FloatTensor(batch.state)  # [32, 4]
+q_values = Q_network(state_tensor)  # [32, 185]
+q_current = q_values.gather(1, batch.action.unsqueeze(1))  # [32, 1]
+
+# 3. 计算目标Q值
+with torch.no_grad():
+    next_q_values = Q_target(batch.next_state)  # [32, 185]
+    next_q_max = next_q_values.max(dim=1).values  # [32]
+    target = batch.reward + 0.95 × next_q_max × (1 - batch.done)  # [32]
+
+# 4. 计算TD误差
+loss = F.mse_loss(q_current.squeeze(), target)
+# 例:loss = 0.523
+
+# 5. 反向传播
+optimizer.zero_grad()
+loss.backward()
+optimizer.step()
+
+# 6. 软更新目标网络
+for param, target_param in zip(Q_network.parameters(), Q_target.parameters()):
+    target_param.data.copy_(0.005 × param.data + 0.995 × target_param.data)
+```
+
+---
+
+## 关键代码解析
+
+### Q网络结构(MlpPolicy默认)
+
+```python
+# Stable-Baselines3的MlpPolicy默认架构
+class QNetwork(nn.Module):
+    def __init__(self, state_dim=4, action_dim=185):
+        super().__init__()
+        self.net = nn.Sequential(
+            nn.Linear(4, 64),      # 输入层 → 隐藏层1
+            nn.ReLU(),
+            nn.Linear(64, 64),     # 隐藏层1 → 隐藏层2
+            nn.ReLU(),
+            nn.Linear(64, 185)     # 隐藏层2 → 输出层
+        )
+    
+    def forward(self, state):
+        # state: [batch, 4]
+        return self.net(state)  # [batch, 185]
+```
+
+**参数量**:
+```
+Layer 1: 4×64 + 64 = 320
+Layer 2: 64×64 + 64 = 4160
+Layer 3: 64×185 + 185 = 12025
+Total: 16505 参数
+```
+
+### ε-贪心策略实现
+
+```python
+def predict(self, observation, epsilon):
+    if np.random.random() < epsilon:
+        # 探索:均匀随机
+        return self.action_space.sample()
+    else:
+        # 利用:选择Q值最大的动作
+        with torch.no_grad():
+            obs_tensor = torch.FloatTensor(observation).unsqueeze(0)
+            q_values = self.q_network(obs_tensor)
+            return q_values.argmax(dim=1).item()
+```
+
+### 经验回放采样
+
+```python
+class ReplayBuffer:
+    def sample(self, batch_size):
+        # 均匀随机采样
+        indices = np.random.randint(0, len(self.buffer), size=batch_size)
+        
+        batch = {
+            'state': np.array([self.buffer[i][0] for i in indices]),
+            'action': np.array([self.buffer[i][1] for i in indices]),
+            'reward': np.array([self.buffer[i][2] for i in indices]),
+            'next_state': np.array([self.buffer[i][3] for i in indices]),
+            'done': np.array([self.buffer[i][4] for i in indices])
+        }
+        
+        return batch
+```
+
+### TensorBoard日志
+
+```python
+# 自动记录的指标(由Monitor包装)
+- rollout/ep_rew_mean: 平均episode奖励
+- rollout/ep_len_mean: 平均episode长度
+- time/fps: 训练速度(步/秒)
+- train/loss: TD误差
+- train/learning_rate: 当前学习率
+- train/n_updates: 梯度更新次数
+```
+
+---
+
+## 数据流与控制流
+
+### 数据流图
+
+```
+输入数据流:
+TMP0 (float) → [归一化] → state[0] (0~1)
+    ↓
+    ├─ last_L_s → state[1]
+    ├─ last_t_bw_s → state[2]
+    └─ max_TMP → state[3]
+    
+    state[4] → Q_network → q_values[185]
+              ↓
+          argmax → action (int)
+              ↓
+          decode → (L_s, t_bw_s)
+              ↓
+    simulate_one_supercycle()
+              ↓
+        ┌─────┴──────┐
+        │  model_fp   │ → ΔTMP
+        │  model_bw   │ → φ
+        └─────┬──────┘
+              ↓
+       约束检查 → feasible (bool)
+              ↓
+       指标计算 → {recovery, net_rate, ...}
+              ↓
+       _score() → reward (float)
+              ↓
+       ReplayBuffer
+```
+
+### 控制流图
+
+```
+main() 入口
+    ↓
+set_global_seed(2025)
+    ↓
+创建UFParams
+    ↓
+创建UFSuperCycleEnv
+    ↓
+包装Monitor & DummyVecEnv
+    ↓
+创建DQN模型
+    ↓
+┌─────────────────────────┐
+│   model.learn(50000)    │
+│                         │
+│   For step in range:    │
+│       ├─ select_action  │
+│       ├─ env.step()     │
+│       ├─ buffer.add()   │
+│       ├─ train_network  │← 每4步
+│       ├─ update_target  │← 每1步(软更新)
+│       └─ callback()     │
+│                         │
+│   If done: env.reset()  │
+└─────────────────────────┘
+    ↓
+model.save("dqn_model.zip")
+    ↓
+打印统计信息
+    ↓
+结束
+```
+
+### 并发与同步
+
+```
+主线程:
+    ├─ DQN训练循环
+    │   ├─ 网络前向传播
+    │   ├─ 环境交互
+    │   └─ 网络反向传播
+    │
+    ├─ 回调线程(可选)
+    │   └─ TensorBoard写入
+    │
+    └─ Monitor包装
+        └─ 统计信息累积
+
+注意:
+- DummyVecEnv是单进程向量化(伪并行)
+- 如需真正并行,应使用SubprocVecEnv
+- 当前代码未使用多进程/多线程
+```
+
+### 内存管理
+
+```
+主要内存占用:
+1. 经验回放池:10000 × (4+1+1+4+1) × 4字节 ≈ 440KB
+2. Q网络参数:16505 × 4字节 ≈ 66KB
+3. 目标网络参数:16505 × 4字节 ≈ 66KB
+4. 梯度缓存:约等于参数量 ≈ 66KB
+5. 训练batch:32 × (4+4+1) × 4字节 ≈ 1.2KB
+
+总计:约650KB(非常小)
+
+峰值内存:
+- 反向传播时临时张量 +100KB
+- TensorBoard缓冲 +1MB
+- 总峰值 < 2MB
+```
+
+---
+
+## 训练监控与调试
+
+### TensorBoard可视化
+
+启动方式:
+```bash
+tensorboard --logdir=./uf_dqn_tensorboard
+```
+
+关键曲线:
+1. **rollout/ep_rew_mean**:episode平均奖励(核心指标)
+   - 期望:从负值逐渐上升到正值
+   - 收敛标志:曲线稳定在0.5以上
+
+2. **train/loss**:TD误差(训练稳定性)
+   - 期望:从高值逐渐下降
+   - 警告:如果持续震荡或发散,说明学习不稳定
+
+3. **rollout/ep_len_mean**:episode平均长度
+   - 期望:保持在10-20之间
+   - 异常:突然下降说明策略变差(频繁违反约束)
+
+### 调试技巧
+
+#### 检查奖励分布
+```python
+# 在callback中添加
+rewards_hist = []
+def _on_step(self):
+    rewards_hist.append(self.locals["rewards"][0])
+    if len(rewards_hist) == 1000:
+        print(f"Reward分布:min={min(rewards_hist)}, "
+              f"mean={np.mean(rewards_hist)}, "
+              f"max={max(rewards_hist)}")
+        rewards_hist.clear()
+```
+
+#### 检查约束违反率
+```python
+constraint_violations = 0
+total_steps = 0
+
+def _on_step(self):
+    global constraint_violations, total_steps
+    total_steps += 1
+    if self.locals["rewards"][0] == -20:
+        constraint_violations += 1
+    
+    if total_steps % 1000 == 0:
+        violation_rate = constraint_violations / total_steps
+        print(f"约束违反率:{violation_rate:.2%}")
+```
+
+#### 检查Q值范围
+```python
+# 每1000步记录Q值统计
+if step % 1000 == 0:
+    with torch.no_grad():
+        sample_states = buffer.sample_states(100)
+        q_values = model.q_network(sample_states)
+        print(f"Q值范围:[{q_values.min():.2f}, {q_values.max():.2f}]")
+```
+
+---
+
+## 总结
+
+### 训练流程核心要点
+
+1. **环境模拟**:基于简化的数学模型,不是真实物理数据
+2. **状态表示**:4维向量,信息相对简单
+3. **动作空间**:185个离散动作(37×5网格)
+4. **奖励设计**:多目标加权+非线性变换
+5. **算法选择**:DQN(经典RL算法)
+6. **训练策略**:ε-贪心探索,经验回放,软更新目标网络
+
+### 关键超参数
+
+| 参数 | 值 | 影响 |
+|------|-----|------|
+| total_timesteps | 50000 | 训练总步数 |
+| buffer_size | 10000 | 经验池大小 |
+| learning_rate | 1e-4 | 学习速度 |
+| gamma | 0.95 | 长期规划能力 |
+| exploration | 1.0→0.02 | 探索能力 |
+
+### 预期训练效果
+
+**良好训练的标志**:
+- Episode奖励从负值上升到0.5+
+- 约束违反率从80%降到5%以下
+- 回收率稳定在0.96+
+- Q值逐渐收敛(不再剧烈波动)
+
+**训练失败的标志**:
+- 奖励曲线持续震荡
+- 约束违反率居高不下
+- Q值爆炸或消失
+- Episode长度急剧下降
+

+ 0 - 0
models/uf-rl/训练/uf_train/__init__.py


+ 0 - 0
models/uf-rl/训练/uf_train/data_to_rl/__init__.py


+ 83 - 0
models/uf-rl/训练/uf_train/data_to_rl/data_splitter.py

@@ -0,0 +1,83 @@
+import pandas as pd
+from pathlib import Path
+from typing import Tuple
+
+
+class ResetStatePoolLoader:
+    """
+    Reset 初始状态池加载与划分类
+
+    功能:
+    - 从 CSV 文件读取 RL-ready 的 reset 状态池
+    - 做基础数据合法性检查
+    - 按比例划分为 train / val 两个状态池
+
+    设计原则:
+    - 不涉及 RL 算法
+    - 不涉及环境逻辑
+    - 仅负责「数据 → reset 可用状态池」
+    """
+
+    def __init__(
+        self,
+        csv_path: str | Path,
+        train_ratio: float = 0.8,
+        shuffle: bool = True,
+        random_state: int = 42,
+    ):
+        self.csv_path = Path(csv_path)
+        self.train_ratio = train_ratio
+        self.shuffle = shuffle
+        self.random_state = random_state
+
+        self._validate_path()
+
+    def _validate_path(self):
+        if not self.csv_path.exists():
+            raise FileNotFoundError(
+                f"Reset state pool 文件不存在: {self.csv_path}"
+            )
+
+    def load(self) -> pd.DataFrame:
+        """
+        读取 reset 状态池 CSV
+        """
+        df = pd.read_csv(self.csv_path)
+
+        if df.empty:
+            raise ValueError("reset_state_pool.csv 为空")
+
+        # 基础合法性检查
+        if df.isnull().any().any():
+            raise ValueError(
+                "reset_state_pool.csv 中存在 NaN,请在 data_to_rl 阶段处理"
+            )
+
+        return df
+
+    def split(self) -> Tuple[pd.DataFrame, pd.DataFrame]:
+        """
+        按比例划分 train / val reset 状态池
+
+        返回:
+            train_pool, val_pool
+        """
+        df = self.load()
+
+        if self.shuffle:
+            df = df.sample(
+                frac=1.0,
+                random_state=self.random_state
+            ).reset_index(drop=True)
+
+        split_idx = int(len(df) * self.train_ratio)
+
+        train_pool = df.iloc[:split_idx].reset_index(drop=True)
+        val_pool = df.iloc[split_idx:].reset_index(drop=True)
+
+        if len(train_pool) == 0 or len(val_pool) == 0:
+            raise ValueError(
+                f"数据量不足以划分 train/val,样本数={len(df)}"
+            )
+
+        return train_pool, val_pool

+ 221 - 0
models/uf-rl/训练/uf_train/data_to_rl/data_to_rl_config.yaml

@@ -0,0 +1,221 @@
+# ============================================================
+# 项目级路径配置
+# ============================================================
+
+Paths:
+  project_root: "E:/Greentech"
+  __comment__: >
+    项目根目录,所有路径均相对于该目录展开。
+    不同工程师只需修改这一项即可迁移环境。
+
+  raw_data:
+    filtered_cycles_dir: "models/uf-rl/datasets/processed/filter_segments"
+    cycle_file_pattern: "UF{unit}_filtered_cycles.csv"
+
+    enabled_units: [1, 2, 4]
+    disabled_units: [3]
+
+    __comment__: >
+      已完成清洗与周期切分的真实工厂数据。
+      每个 CSV 对应一个机组,文件内每一行是一个【物理周期】。
+      data_to_rl 阶段会:
+        - 读取 enabled_units 中所有机组
+        - 合并为统一数据集
+        - 机组编号仅用于筛选,不进入状态空间
+
+  data_to_rl:
+    output_dir: "models/uf-rl/datasets/rl_ready/output"
+    cache_dir: "models/uf-rl/datasets/rl_ready/cache"
+
+    __comment__: >
+      data_to_rl 模块输出内容:
+        - 全机组合并后的状态空间范围
+        - reset 初始状态分布
+        - reward 统计量
+
+
+# ============================================================
+# 数据层级定义(非常重要)
+# ============================================================
+
+DataHierarchy:
+
+  physical_cycle:
+    id_column: "seg_id"
+    time_columns:
+      start: "start_time"
+      end: "end_time"
+    __comment__: >
+      物理周期是最细粒度的数据层级:
+      - 用于物理模型验证
+      - 用于环境内部子步模拟
+      - 不直接暴露给强化学习算法
+
+  chemical_cycle:
+    id_column: "chem_cycle_id"
+    validity_column: "chem_cycle_valid"
+    step_definition: "one_rl_step_per_chemical_cycle"
+    __comment__: >
+      化学周期是强化学习的【唯一 step 单位】。
+      强化学习中:
+        - 一个 step = 一个化学周期
+        - 一个状态 = 一个化学周期的整体状态
+
+# ============================================================
+# 强化学习状态定义(工程师重点关注)
+# ============================================================
+
+RLState:
+  level: chemical_cycle
+  dimension: 8
+  __comment__: >
+    强化学习 observation 的定义。
+    所有变量在一个 step(化学周期)内保持不变。
+
+  variables:
+
+    q_UF:
+      source: "flow_mean"
+      aggregation: "mean_within_cycle"
+      unit: "m3_per_h"
+      description: "化学周期代表性进水流量"
+      __comment__: >
+        原始数据在物理周期层级变化,
+        这里使用化学周期内的平均值作为状态。
+
+    temp:
+      source: "temp_mean"
+      aggregation: "mean_within_cycle"
+      unit: "celsius"
+      description: "化学周期代表性水温"
+
+    TMP:
+      source: "tmp_start"
+      selection: "first_physical_cycle"
+      unit: "MPa"
+      description: "化学周期起始跨膜压差"
+      __comment__: >
+        虽然 TMP 在物理周期内会变化,
+        但强化学习只关心化学周期开始时的状态。
+
+    R:
+      source: "R_scaled_start"
+      selection: "first_physical_cycle"
+      unit: "scaled_resistance"
+      description: "化学周期起始膜阻力"
+
+    nuK:
+      source: "cycle_nuK"
+      unit: "scaled"
+      description: "短期污染增长系数"
+      __comment__: >
+        在同一化学周期内视为常数,
+        后续 step (下一化学周期)中根据动作进行经验性更新。
+
+    slope:
+      source: "cycle_long_a"
+      unit: "scaled"
+      description: "长期不可逆污染幂律系数 a"
+
+    power:
+      source: "cycle_long_b"
+      unit: "dimensionless"
+      description: "长期不可逆污染幂律指数 b"
+
+    ceb_removal:
+      source: "cycle_R_removed"
+      unit: "scaled"
+      description: "CEB 可去除的膜阻力"
+
+# ============================================================
+# 状态空间范围提取(reset 使用)
+# ============================================================
+
+StateSpaceExtraction:
+  enabled: true
+  level: chemical_cycle
+  include_only_valid_cycles: true
+
+  __comment__: >
+    用真实工厂数据统计【合理状态空间范围】,
+    用于:
+      - reset 初始状态采样
+      - 状态边界 sanity check
+
+  statistics:
+    method: "empirical"
+    percentiles: [1, 5, 50, 95, 99]
+    __comment__: >
+      使用经验分布统计,避免假设正态分布。
+
+  output:
+    save_csv: true
+    filename: "state_space_bounds.csv"
+
+# ============================================================
+# 物理周期层级的用途声明
+# ============================================================
+
+PhysicalCycleUsage:
+  enabled: true
+
+  purposes:
+    - internal_simulation
+    - parameter_sanity_check
+    - post_training_validation
+
+  variables_available:
+    - R_scaled_start
+    - R_scaled_end
+    - tmp_start
+    - tmp_end
+    - flow_mean
+    - flow_std
+    - temp_mean
+    - temp_std
+
+  __comment__: >
+    物理周期数据:
+      ✔ 可用于环境内部计算
+      ✔ 可用于训练后验证
+      ✘ 不可作为 RL observation
+
+# ============================================================
+# 真实数据在强化学习中的角色(工程边界声明)
+# ============================================================
+
+RealDataUsagePolicy:
+
+  reset_initialization:
+    enabled: true
+    source: "chemical_cycle_state_distribution"
+    __comment__: >
+      reset 时从真实化学周期状态分布中采样,
+      确保训练从“现实可达状态”开始。
+
+  training:
+    use_real_transitions: false
+    __comment__: >
+      真实数据不用于:
+        - 动力学拟合
+        - 监督策略
+      强化学习仍在模拟环境中进行。
+
+  validation:
+    enabled: true
+    compare_with_real_cycles: true
+    __comment__: >
+      训练完成后:
+        - 将策略运行结果
+        - 与真实化学周期统计特性进行对比
+      用于评估策略的现实可行性与安全性。
+
+# ============================================================
+# 方法论与工程风险声明
+# ============================================================
+
+MethodologyNotes:
+  - "强化学习的 step 定义严格等同于一个化学周期"
+  - "所有 RL 状态变量在一个 step 内视为常数"
+  - "物理周期层级仅用于环境内部模拟"
+  - "真实数据用于分布约束与验证,而非动力学建模"

+ 34 - 0
models/uf-rl/训练/uf_train/data_to_rl/get_reset_pool.py

@@ -0,0 +1,34 @@
+import yaml
+from pathlib import Path
+
+from loader import load_all_units_cycles, resolve_path
+from state_construction import build_chem_cycle_state, STATE_COLUMNS
+
+
+def load_config(path: Path) -> dict:
+    with open(path, "r", encoding="utf-8") as f:
+        return yaml.safe_load(f)
+
+
+def generate_reset_state_pool(config_path: Path):
+    cfg = load_config(config_path)
+    project_root = Path(cfg["Paths"]["project_root"])
+
+    output_dir = resolve_path(
+        project_root,
+        cfg["Paths"]["data_to_rl"]["output_dir"]
+    )
+    output_dir.mkdir(parents=True, exist_ok=True)
+
+    df = load_all_units_cycles(cfg)
+    df = df[df["chem_cycle_valid"] == True]
+
+    state_df = build_chem_cycle_state(df)
+
+    # 严格完整性过滤
+    state_df = state_df.dropna(subset=STATE_COLUMNS)
+
+    state_df.to_csv(
+        output_dir / "reset_state_pool.csv",
+        index=False
+    )

+ 33 - 0
models/uf-rl/训练/uf_train/data_to_rl/loader.py

@@ -0,0 +1,33 @@
+import pandas as pd
+from pathlib import Path
+
+
+def resolve_path(project_root: Path, relative_path: str) -> Path:
+    return project_root / relative_path
+
+
+def load_all_units_cycles(cfg: dict) -> pd.DataFrame:
+    """
+    读取所有启用 UF 机组的 filtered_cycles 数据,
+    返回【物理周期层级】的合并 DataFrame。
+    """
+    project_root = Path(cfg["Paths"]["project_root"])
+    raw_cfg = cfg["Paths"]["raw_data"]
+
+    all_dfs = []
+
+    for unit in raw_cfg["enabled_units"]:
+        file_name = raw_cfg["cycle_file_pattern"].format(unit=unit)
+        file_path = resolve_path(
+            project_root,
+            raw_cfg["filtered_cycles_dir"]
+        ) / file_name
+
+        if not file_path.exists():
+            raise FileNotFoundError(f"未找到 UF{unit} 数据文件: {file_path}")
+
+        df_unit = pd.read_csv(file_path)
+        df_unit["unit_id"] = unit  # 仅用于分组,不进入状态
+        all_dfs.append(df_unit)
+
+    return pd.concat(all_dfs, ignore_index=True)

+ 79 - 0
models/uf-rl/训练/uf_train/data_to_rl/run_data_to_rl_pipeline.py

@@ -0,0 +1,79 @@
+"""
+============================================================
+ data_to_rl | 全流程入口脚本
+------------------------------------------------------------
+ 功能:
+     串行执行 data_to_rl 模块的全部数据准备流程,包括:
+       1. 强化学习状态空间范围提取
+       2. 强化学习 reset 状态先验池生成
+
+ 设计原则:
+     - 只负责“调度”,不包含任何业务逻辑
+     - 所有参数均来自 data_to_rl_config.yaml
+     - 工程师只需运行本脚本即可完成全部准备工作
+
+ 使用方式:
+     python run_data_to_rl_pipeline.py
+============================================================
+"""
+
+from pathlib import Path
+import time
+
+from state_space_bounds import extract_state_space_bounds
+from get_reset_pool import generate_reset_state_pool
+
+
+def main():
+    # --------------------------------------------------------
+    # 配置文件路径(统一入口)
+    # --------------------------------------------------------
+    config_path = Path(__file__).parent / "data_to_rl_config.yaml"
+
+    print("======================================================")
+    print(" data_to_rl 数据准备流水线启动 ")
+    print("------------------------------------------------------")
+    print(f" 配置文件: {config_path}")
+    print("======================================================\n")
+
+    start_time = time.time()
+
+    # --------------------------------------------------------
+    # Step 1: 提取状态空间经验范围
+    # --------------------------------------------------------
+    print(">>> Step 1 / 2:提取强化学习状态空间范围")
+    step_start = time.time()
+
+    extract_state_space_bounds(config_path)
+
+    print(
+        f">>> Step 1 完成,用时 {time.time() - step_start:.2f} 秒\n"
+    )
+
+    # --------------------------------------------------------
+    # Step 2: 生成 reset 状态先验池
+    # --------------------------------------------------------
+    print(">>> Step 2 / 2:生成 RL reset 状态先验池")
+    step_start = time.time()
+
+    generate_reset_state_pool(config_path)
+
+    print(
+        f">>> Step 2 完成,用时 {time.time() - step_start:.2f} 秒\n"
+    )
+
+    # --------------------------------------------------------
+    # 总结
+    # --------------------------------------------------------
+    print("======================================================")
+    print(" data_to_rl 数据准备流水线执行完成 ")
+    print("------------------------------------------------------")
+    print(f" 总耗时: {time.time() - start_time:.2f} 秒")
+    print(" 输出内容包括:")
+    print("   - state_space_bounds.csv   (状态空间统计)")
+    print("   - reset_state_pool.csv     (reset 初始状态池)")
+    print("======================================================")
+
+
+if __name__ == "__main__":
+    main()

+ 28 - 0
models/uf-rl/训练/uf_train/data_to_rl/state_construction.py

@@ -0,0 +1,28 @@
+import pandas as pd
+
+
+STATE_COLUMNS = [
+    "q_UF", "temp", "TMP", "R",
+    "nuK", "slope", "power", "ceb_removal"
+]
+
+
+def build_chem_cycle_state(df: pd.DataFrame) -> pd.DataFrame:
+    """
+    从【物理周期层级】数据中构造
+    【化学周期层级】的 RL 状态。
+    """
+    grouped = df.groupby(["unit_id", "chem_cycle_id"])
+
+    state_df = pd.DataFrame({
+        "q_UF": grouped["flow_mean"].mean(),
+        "temp": grouped["temp_mean"].mean(),
+        "TMP": grouped["tmp_start"].first(),
+        "R": grouped["R_scaled_start"].first(),
+        "nuK": grouped["cycle_nuK"].first(),
+        "slope": grouped["cycle_long_a"].first(),
+        "power": grouped["cycle_long_b"].first(),
+        "ceb_removal": grouped["cycle_R_removed"].first(),
+    })
+
+    return state_df.reset_index(drop=True)

+ 47 - 0
models/uf-rl/训练/uf_train/data_to_rl/state_space_bounds.py

@@ -0,0 +1,47 @@
+import yaml
+import pandas as pd
+from pathlib import Path
+
+from loader import load_all_units_cycles, resolve_path
+from state_construction import build_chem_cycle_state, STATE_COLUMNS
+
+
+def load_config(path: Path) -> dict:
+    with open(path, "r", encoding="utf-8") as f:
+        return yaml.safe_load(f)
+
+
+def extract_state_space_bounds(config_path: Path):
+    cfg = load_config(config_path)
+    project_root = Path(cfg["Paths"]["project_root"])
+
+    output_dir = resolve_path(
+        project_root,
+        cfg["Paths"]["data_to_rl"]["output_dir"]
+    )
+    output_dir.mkdir(parents=True, exist_ok=True)
+
+    df = load_all_units_cycles(cfg)
+    df = df[df["chem_cycle_valid"] == True]
+
+    state_df = build_chem_cycle_state(df)
+
+    percentiles = cfg["StateSpaceExtraction"]["statistics"]["percentiles"]
+    stats = {}
+
+    for col in STATE_COLUMNS:
+        desc = state_df[col].describe(
+            percentiles=[p / 100 for p in percentiles]
+        )
+
+        stats[col] = {
+            "min": desc["min"],
+            "max": desc["max"],
+            "mean": desc["mean"],
+            "std": desc["std"],
+            **{f"p{p}": desc[f"{p}%"] for p in percentiles}
+        }
+
+    pd.DataFrame(stats).T.to_csv(
+        output_dir / "state_space_bounds.csv"
+    )

+ 0 - 0
models/uf-rl/训练/uf_train/env/__init__.py


+ 138 - 0
models/uf-rl/训练/uf_train/env/check_initial_state.py

@@ -0,0 +1,138 @@
+# check_initial_state.py
+"""
+检查初始状态是否为“必死状态”(conservatively dead):
+1) 实例化 base_params(优先使用 rl_dqn_env 中提供的 base_params 或 UFParams)
+2) 实例化环境类 UFSuperCycleEnv(base_params)
+3) 调用 env.generate_initial_state() 生成 env.current_params(不调用 reset())
+4) 用最保守策略 (L_s=3600s, t_bw_s=60s) 连续模拟 max_steps 次,
+   若任意一次 is_dead_cycle(info) 返回 False 则判定为必死(返回 True),否则返回 False。
+"""
+
+from typing import Any
+import copy
+import traceback
+
+# 从 rl_dqn_env 导入必需项
+try:
+    from uf_env import (
+        simulate_one_supercycle,
+        is_dead_cycle,
+        UFSuperCycleEnv,
+        UFParams,       # 如果模块里有 UFParams 类就导入
+        base_params     # 如果模块直接提供 base_params 实例也尝试导入
+    )
+except Exception:
+    # 有可能某些名字不存在 —— 我们会稍后用回退方案处理
+    # 先导入模块并再尝试访问属性,确保错误信息更友好
+    import importlib
+    rl = importlib.import_module("rl_dqn_env")
+    simulate_one_supercycle = getattr(rl, "simulate_one_supercycle", None)
+    is_dead_cycle = getattr(rl, "is_dead_cycle", None)
+    UFSuperCycleEnv = getattr(rl, "UFSuperCycleEnv", None)
+    UFParams = getattr(rl, "UFParams", None)
+    base_params = getattr(rl, "base_params", None)
+
+# 检查导入完整性
+_missing = []
+if simulate_one_supercycle is None:
+    _missing.append("simulate_one_supercycle")
+if is_dead_cycle is None:
+    _missing.append("is_dead_cycle")
+if UFSuperCycleEnv is None:
+    _missing.append("UFSuperCycleEnv")
+if _missing:
+    raise ImportError(f"无法从 rl_dqn_env 导入以下必要项: {', '.join(_missing)}")
+
+def is_dead_initial_state_env(env: UFSuperCycleEnv, max_steps: int = 15,
+                              L_s: int = 4200, t_bw_s: int = 50,
+                              verbose: bool = True) -> bool:
+    """
+    使用 env.current_params 作为初始状态判断是否为必死状态(保守策略)。
+
+    参数:
+        env: 已实例化的 UFSuperCycleEnv(必须包含 generate_initial_state() 与 current_params)
+        max_steps: 模拟步数(默认 15)
+        L_s: 过滤时长(s),保守值 3600
+        t_bw_s: 物理反洗时长(s),保守值 60
+        verbose: 是否打印每步结果
+
+    返回:
+        True 表示必死(conservatively dead)
+        False 表示可行
+    """
+    # 1) 确保 env 有 current_params,并且 generate_initial_state 可用
+    if not hasattr(env, "generate_initial_state"):
+        raise AttributeError("env 缺少 generate_initial_state() 方法。")
+    # 生成初始状态(不会调用 reset)
+    env.generate_initial_state()
+
+    if not hasattr(env, "current_params"):
+        raise AttributeError("env.generate_initial_state() 未设置 env.current_params。")
+
+    curr_p = copy.deepcopy(env.current_params)
+
+    for step in range(1, max_steps + 1):
+        try:
+            info, next_params = simulate_one_supercycle(curr_p, L_s, t_bw_s)
+        except Exception as e:
+            # 如果 simulate 出错,把异常视为“失败”(保守处理)
+            if verbose:
+                print(f"[Step {step}] simulate_one_supercycle 抛出异常,视为失败。异常信息:{e}")
+                traceback.print_exc()
+            return True
+
+        success = is_dead_cycle(info)  # True 表示成功循环
+
+        if verbose:
+            print(f"[Step {step}] 循环结果:{'成功' if success else '失败'}")
+            # 如果 info 中有关键诊断字段,打印简要信息
+            try:
+                print(f"     TMP0: {info.get('TMP0')},max_TMP: {info.get('max_TMP_during_filtration')}, recovery: {info.get('recovery')}, "
+                      f"R0: {info.get('R0')}, R_after_ceb: {info.get('R_after_ceb')}")
+            except Exception:
+                pass
+
+        if not success:
+            if verbose:
+                print(f"在第 {step} 步检测到失败,判定为必死初始状态(conservatively dead)。")
+            return True
+
+        # 否则继续,用 next_params 作为下一步起始参数
+        curr_p = next_params
+
+    if verbose:
+        print(f"{max_steps} 步均成功,初始状态判定为可行(non-dead)。")
+    return False
+
+
+if __name__ == "__main__":
+    print("=== check_initial_state.py: 使用 env.generate_initial_state() 检查初始状态是否为必死 ===")
+
+    try:
+        # 1) 构造 base_params
+        if base_params is not None:
+            bp = base_params
+            print("使用 rl_dqn_env 中提供的 base_params。")
+        elif UFParams is not None:
+            bp = UFParams()  # 使用默认构造
+            print("使用 UFParams() 构造 base_params 的实例。")
+        else:
+            raise ImportError("无法构造 base_params:rl_dqn_env 中既无 base_params 也无 UFParams。")
+
+        # 2) 实例化环境类(将 base_params 传入构造器)
+        env = UFSuperCycleEnv(bp)
+        print("已实例化 UFSuperCycleEnv 环境。")
+
+        # 3) 调用 env.generate_initial_state() 并检查 env.current_params 是否为必死
+        dead = is_dead_initial_state_env(env, max_steps=getattr(env, "max_episode_steps", 15),
+                                        L_s=6000, t_bw_s=40, verbose=True)
+
+        print("\n=== 判定结果 ===")
+        if dead:
+            print("当前生成的初始状态为【必死状态】(conservatively dead)。")
+        else:
+            print("当前生成的初始状态为【可行状态】(non-dead)。")
+
+    except Exception as e:
+        print("脚本执行出现错误:", e)
+        traceback.print_exc()

+ 350 - 0
models/uf-rl/训练/uf_train/env/env_params.py

@@ -0,0 +1,350 @@
+"""
+env_params.py
+
+超滤(UF)强化学习环境的参数定义文件。
+
+本文件集中定义 UF 强化学习环境中使用的所有【参数类(Parameter Schemas)】。
+这些类仅用于描述系统配置、状态语义和约束边界,不包含任何数值计算、
+物理模型、奖励逻辑或 Gym 接口实现。
+
+========================
+参数类总览
+========================
+
+1. UFState
+------------------------
+【环境动态状态】
+
+- 描述超滤系统在某一时刻的运行状态
+- 这些变量会在 env.step() 中随动作不断演化
+- 构成强化学习算法可观测的 observation
+- 生命周期:每个 step 更新一次
+
+包含内容:
+- 实时运行工况(流量、温度、TMP)
+- 膜污染状态参数(短期污染、长期污染)
+- CEB 去除能力等可随 episode 演化的量
+
+使用方:
+- 强化学习环境(step / reset)
+- 膜阻力物理模型(作为 forward 的 state 输入)
+- reward 计算逻辑
+
+
+2. UFPhysicsParams
+------------------------
+【物理与工艺固定参数】
+
+- 描述超滤系统的客观工艺与设备配置
+- 在整个 episode 甚至整个训练生命周期中保持不变
+- 典型为“物理常数 / 工艺设定值”
+
+设计约束:
+- 使用 frozen dataclass,防止运行时被意外修改
+
+包含内容:
+- 物理反洗效率时间尺度
+- 反洗流量
+- CEB 间隔与用水量
+- 膜组件面积等硬件参数
+
+使用方:
+- 膜阻力模型(上升 / 下降)
+- 回收率、能耗等物理量计算
+- 环境内部的物理约束判断
+
+
+3. UFActionSpec
+------------------------
+【动作空间定义】
+
+- 描述智能体“可以选什么动作”,而不是“如何执行动作”
+- 仅定义动作的物理含义、取值范围和离散粒度
+- 不负责构造 Gym action_space
+
+包含内容:
+- 过滤时长(L)范围与步长
+- 物理反洗时长(t_bw)范围与步长
+
+使用方:
+- 环境初始化时构建离散动作空间
+- 不同算法(DQN / Dueling / PPO)共用同一动作语义
+
+
+4. UFRewardSpec
+------------------------
+【奖励函数与安全约束参数】
+
+- 集中管理 reward 计算所需的权重、阈值与约束
+- 同时用于 episode 终止(terminated)判断
+- 不包含 reward 的计算公式本身
+
+包含内容:
+- TMP 硬 / 软安全上限
+- 回收率与污染惩罚的敏感度系数
+- 回收率合理区间与参考污染比例
+
+使用方:
+- reward 函数
+- 环境终止条件判断
+
+
+5. UFStateBounds
+------------------------
+【状态初始化约束】
+
+- 仅用于 reset() 阶段的随机初始化
+- 不参与 step() 中的状态演化
+- 用于保证初始状态物理合理、覆盖足够多工况
+
+包含内容:
+- TMP、流量、温度的初始化范围
+- 污染模型参数的合理区间
+- CEB 去除能力的上下限
+
+使用方:
+- env.reset()
+- 用于构造多样化初始工况,增强策略鲁棒性
+
+
+========================
+设计原则(请勿破坏)
+========================
+
+1. 本文件只描述“参数是什么”,不描述“参数如何使用”
+2. 禁止在本文件中出现:
+   - torch / numpy 数值计算
+   - Gym / Gymnasium space 定义
+   - step / forward / reward 等逻辑
+3. 若需要新增参数,请优先判断其生命周期:
+   - 是否随 step 变化 → UFState
+   - 是否全局不变 → UFPhysicsParams / Spec 类
+4. 若发现需要在此文件中写计算代码,说明模块边界已经被破坏,应重构调用方
+
+"""
+
+
+from dataclasses import dataclass, field
+from typing import Dict
+
+
+"""
+【说明:拆分 UFParams】
+
+原 UFParams 类同时承担了多种不同职责,包括:
+1. 环境状态(会在 step 中动态变化)
+2. reset 时的初始状态约束
+3. 超滤系统的物理 / 工艺固定参数
+4. 强化学习动作空间定义
+5. 奖励函数与安全约束参数
+
+在强化学习工程中,这种参数结构会带来以下问题:
+
+- 状态与参数混杂,容易在 step() 中被误修改
+- reset 初始化逻辑与运行逻辑强耦合,难以调试
+- 奖励函数修改会影响环境动力学,增加不可控风险
+- 不利于算法切换(DQN / Dueling Q / PPO)
+
+因此,现将 UFParams 拆分为多个“职责单一”的参数类,
+每个类只负责一类明确的功能,从而实现:
+
+- 语义清晰
+- 边界明确
+- 易于维护
+- 算法无关(一个环境,多种算法共用)
+
+【注意】
+本次拆分不改变任何物理意义、数值含义与默认参数,仅做结构重组。
+"""
+
+# ==================== 超滤系统参数配置类 ====================
+@dataclass
+class UFState:
+    """
+    【环境动态状态】
+    描述超滤系统在当前时刻的运行状态。
+    这些变量会在 step() 中随动作不断演化,并构成强化学习的 observation。
+    """
+
+    # ========== 膜动态运行参数 ==========
+    # 这些参数描述超滤膜的实时运行状态,在环境模拟中会动态变化
+
+    q_UF: float = 360.0
+    # 过滤进水流量(m³/h)
+    # 说明:影响膜通量,进而影响污染速率
+    # 典型范围:250-400 m³/h
+
+    TMP: float = 0.03
+    # 跨膜压差(MPa,兆帕)
+    # 说明:反映膜阻力状态,TMP 越高表示膜污染越严重
+    # 正常范围:0.01-0.035 MPa,超过 0.08 MPa 需停机检修
+
+    temp: float = 25.0
+    # 水温(摄氏度)
+    # 说明:影响水的粘度,进而影响跨膜压差
+    # 典型范围:10-40℃,25℃为标准温度
+
+    R: float = 200.0
+    # 膜阻力(缩放至1e2)
+    # 说明:实际上的膜阻力应该根据q_UF/TMP0/temp动态计算,此处仅为构造状态类占位使用
+
+    # ========== 膜阻力模型参数 ==========
+    # 这些参数描述膜污染的物理化学特性,基于历史数据拟合得到
+
+    nuK: float = 1.7e+02
+    # 过滤阶段膜阻力增长系数(缩放后单位)
+    # 说明:反映水质污染特性,nuK 越大表示水质越差、膜污染越快
+    # 物理意义:单位膜通量、单位时间的阻力增长速率
+
+    slope: float = 2
+    # 全周期不可逆污染增长斜率
+    # 说明:描述长期不可逆污染的累积速率(幂律模型的系数)
+
+    power: float = 1.032
+    # 全周期不可逆污染增长幂次
+    # 说明:描述长期污染的非线性特性(幂律模型的指数)
+    # power > 1 表示污染加速累积,power < 1 表示污染增速放缓
+
+    ceb_removal: float = 100
+    # 化学增强反洗(CEB)可去除的膜阻力(缩放后单位)
+    # 说明:CEB 比物理反洗更彻底,可去除部分不可逆污染
+
+@dataclass(frozen=True)
+class UFPhysicsParams:
+    """
+    【物理与工艺固定参数】
+    描述超滤系统的客观工艺条件,在整个 episode 中保持不变。
+    """
+    global_TMP_hard_limit: float = 0.08
+    # TMP 硬上限(MPa)
+    # 说明:超过此值将导致episode失败,需立即停机
+
+    global_TMP_soft_limit: float = 0.06
+    # TMP 软上限 (MPa)
+    # 说明:此上限用于指导奖励函数中膜阻力允许上升值,越接近该上限,系统对膜阻力上升控制的更严格
+
+    # ========== 反洗参数(固定配置) ==========
+    tau_bw_s: float = 20.0
+    # 物理反洗时长影响的时间尺度(秒)
+    # 说明:反洗效率的特征时间,当反洗时长 = tau 时,达到约 63% 效率
+
+    gamma_t: float = 1.0
+    # 物理反洗时长作用指数(保留参数,当前未使用)
+
+    q_bw_m3ph: float = 1000.0
+    # 物理反洗流量(m³/h)
+    # 说明:反洗流量通常为正常过滤流量的 2-3 倍
+
+    # ========== CEB 化学反洗参数 ==========
+    T_ceb_interval_h: float = 48.0
+    # CEB 间隔时间(小时)
+    # 说明:每运行约 60 小时执行一次化学增强反洗 TODO: PARAM 当前采用保守测试参数48h
+
+    v_ceb_m3: float = 20.0
+    # CEB 用水体积(m³)
+
+    t_ceb_s: float = 40 * 60.0
+    # CEB 时长(秒,这里为 40 分钟)
+
+    # ========== 新增:膜面积 ==========
+    # 膜有效面积(锡山水厂配置:128组膜,每组40m²)
+    A = 128 * 40.0  # [m²]
+
+    # 吨水电耗查找表
+    energy_lookup: Dict[int, float] = field(default_factory=lambda: {
+        3600: 0.1034, 3660: 0.1031, 3720: 0.1029, 3780: 0.1026,
+        3840: 0.1023, 3900: 0.1021, 3960: 0.1019, 4020: 0.1017,
+        4080: 0.1015, 4140: 0.1012, 4200: 0.1011, 4260: 0.1008,
+        4320: 0.1007, 4380: 0.1005, 4440: 0.1003, 4500: 0.1001,
+        4560: 0.0999, 4620: 0.0998, 4680: 0.0996, 4740: 0.0995,
+        4800: 0.0993,
+    })
+
+@dataclass(frozen=True)
+class UFActionSpec:
+    """
+    【动作空间定义】
+    描述智能体可选的过滤-反洗策略组合。
+    """
+    # ========== 强化学习动作空间搜索范围 ==========
+    # 定义智能体可选择的动作范围(离散化)
+
+    L_min_s: float = 3800.0  # 过滤时长下限(秒,约 63 分钟)
+    L_max_s: float = 4800.0  # 过滤时长上限,当前为 4800s 80分钟
+    t_bw_min_s: float = 40.0  # 物理反洗时长下限(秒)
+    t_bw_max_s: float = 60.0  # 物理反洗时长上限(秒)
+
+    # ========== 动作离散化网格 ==========
+    L_step_s: float = 60.0  # 过滤时长步长(秒)
+    t_bw_step_s: float = 5.0  # 物理反洗时长步长(秒)
+
+@dataclass(frozen=True)
+class UFRewardParams:
+    """
+    【奖励函数与安全约束参数】用于 reward 计算和 episode 终止判断。
+    """
+    # TMP 限值
+    global_TMP_hard_limit: float = 0.08
+    # TMP 硬上限(MPa)
+    # 说明:超过此值将导致 episode 失败,需立即停机
+    global_TMP_soft_limit: float = 0.06     # TMP 软上限 (MPa)
+    w_tmp_hard: float = 5.0      # TMP超硬限固定惩罚
+    w_tmp: float = 1.5            # TMP软限惩罚
+    p: float = 3                  # TMP软限非线性指数
+    w_trend: float = 1.0          # TMP趋势惩罚权重
+
+    # 回收率
+    k_rec: float = 5.0
+    rec_low: float = 0.92
+    rec_high: float = 0.99
+    w_rec: float = 1.0             # 回收率权重
+
+    # 残余污染
+    k_res: float = 10.0
+    residual_ref_ratio: float = None   # 动态=1/max_episode_steps
+    w_res: float = 2.0                 # 残余污染权重
+
+    # 吨水电耗
+    k_energy: float = 5.0
+    energy_low: float = 0.0993
+    energy_high: float = 0.1034
+    energy_ref: float = 0.1011
+    w_energy: float = 1.0              # 能耗权重
+
+
+
+@dataclass(frozen=True)
+class UFStateBounds:
+    """
+    【状态初始化约束】
+    仅用于 reset() 时随机初始化环境状态,
+    不参与 step() 中的状态演化。
+    """
+    # --- 流量约束 ---
+    q_UF_max: float = 380.0  # 进水流量上限(m³/h)
+    q_UF_min: float = 210.0  # 进水流量下限(m³/h)
+
+    # --- 温度约束 ---
+    temp_max: float = 32.0  # 温度上限(℃)
+    temp_min: float = 16.0  # 温度下限(℃)
+
+    # --- 初始TMP约束 ---
+    TMP0_max: float = 0.045  # 初始TMP上限(MPa)
+    TMP0_min: float = 0.01  # 初始TMP下限(MPa)
+
+    # --- TMP上限约束 ---
+    global_TMP_hard_limit: float = 0.08
+
+    # --- 短期污染模型参数约束 ---
+    nuK_max: float = 2.6e+02  # 阻力增长系数上限
+    nuK_min: float = 4e+01  # 阻力增长系数下限
+
+    # --- 长期污染模型参数约束 ---
+    slope_max: float = 27  # 不可逆污染斜率上限
+    slope_min: float = 0.03  # 不可逆污染斜率下限
+    power_max: float = 2.2  # 不可逆污染幂次上限
+    power_min: float = 0.4  # 不可逆污染幂次下限
+
+    # --- CEB去除能力约束 ---
+    ceb_removal_max: float = 250  # CEB去除阻力上限(缩放后)
+    ceb_removal_min: float = 40  # CEB去除阻力下限(缩放后)

+ 185 - 0
models/uf-rl/训练/uf_train/env/env_reset.py

@@ -0,0 +1,185 @@
+import pandas as pd
+import numpy as np
+from typing import Optional
+
+class ResetSampler:
+    # ============================================================
+    # 状态定义(唯一真源)
+    # ============================================================
+    FREE_STATE_KEYS = [
+        "q_UF",
+        "temp",
+        "TMP",
+        "nuK",
+        "slope",
+        "power",
+        "ceb_removal",
+    ]
+
+    FULL_STATE_KEYS = [
+        "q_UF",
+        "temp",
+        "TMP",
+        "R",
+        "nuK",
+        "slope",
+        "power",
+        "ceb_removal",
+    ]
+
+    IDX_Q_UF = 0
+    IDX_TEMP = 1
+    IDX_TMP = 2
+
+    def __init__(
+        self,
+        bounds: "UFStateBounds",
+        physics,
+        real_state_pool=None,
+        max_resample_attempts: int = 50,
+        random_state=None,
+    ):
+        self.bounds = bounds
+        self.physics = physics
+        self.max_resample_attempts = max_resample_attempts
+        self.rng = random_state or np.random.RandomState()
+
+        # --- 自由变量边界(顺序必须与 FREE_STATE_KEYS 一致)---
+        self.low = np.array([
+            bounds.q_UF_min,
+            bounds.temp_min,
+            bounds.TMP0_min,
+            bounds.nuK_min,
+            bounds.slope_min,
+            bounds.power_min,
+            bounds.ceb_removal_min,
+        ])
+
+        self.high = np.array([
+            bounds.q_UF_max,
+            bounds.temp_max,
+            bounds.TMP0_max,
+            bounds.nuK_max,
+            bounds.slope_max,
+            bounds.power_max,
+            bounds.ceb_removal_max,
+        ])
+
+        self.state_dim = len(self.low)
+
+        # ========================================================
+        # 统一 real_state_pool 为 DataFrame(不含 R)
+        # ========================================================
+        if real_state_pool is not None:
+            if isinstance(real_state_pool, pd.DataFrame):
+                df = real_state_pool.copy()
+            else:
+                df = pd.DataFrame(real_state_pool, columns=self.FULL_STATE_KEYS)
+
+            if "R" in df.columns:
+                df = df.drop(columns=["R"])
+
+            df = df[self.FREE_STATE_KEYS]
+            self.real_state_pool = df.reset_index(drop=True)
+        else:
+            self.real_state_pool = None
+
+    # ============================================================
+    # 对外接口
+    # ============================================================
+    def sample(self, progress: float) -> pd.Series:
+        cfg = self._get_sampling_config(progress)
+
+        sources, weights = [], []
+
+        if self.real_state_pool is not None:
+            sources += ["real", "perturb"]
+            weights += [cfg["w_real"], cfg["w_perturb"]]
+
+        sources.append("virtual")
+        weights.append(cfg["w_virtual"])
+
+        source = self.rng.choice(sources, p=self._normalize(weights))
+
+        # ========================================================
+        # 1. 采样自由变量(DataFrame 单行)
+        # ========================================================
+        if source == "real":
+            state_df = self._sample_real()  # 必须返回 DataFrame(1 row)
+
+        elif source == "perturb":
+            base = self._sample_real()
+            noise = self.rng.normal(0.0, cfg["perturb_scale"], size=self.state_dim)
+            vec = base.values.squeeze() + noise
+            vec = np.clip(vec, self.low, self.high)
+            state_df = pd.DataFrame([vec], columns=self.FREE_STATE_KEYS)
+
+        else:
+            vec = self.rng.uniform(self.low, self.high)
+            state_df = pd.DataFrame([vec], columns=self.FREE_STATE_KEYS)
+
+        # ========================================================
+        # 2. 物理派生:计算 R
+        # ========================================================
+        TMP = state_df.at[state_df.index[0], "TMP"]
+        q_UF = state_df.at[state_df.index[0], "q_UF"]
+        temp = state_df.at[state_df.index[0], "temp"]
+
+        R = self.physics.resistance_from_tmp(tmp=TMP, q_UF=q_UF, temp=temp)
+
+        if not np.isfinite(R):
+            raise RuntimeError("Invalid resistance computed during reset sampling.")
+
+        # ========================================================
+        # 3. 插入 R,形成完整状态(仍是 DataFrame)
+        # ========================================================
+        state_df.insert(3, "R", R)
+        state_df = state_df[self.FULL_STATE_KEYS]
+
+        # ========================================================
+        # 4. DataFrame(1 row) → Series(对外唯一返回)
+        # ========================================================
+        state = state_df.iloc[0]
+        state.name = "reset_state"  # 可选,但强烈推荐
+
+        return state
+
+    # ============================================================
+    # 内部方法
+    # ============================================================
+    def _sample_real(self) -> pd.DataFrame:
+        for _ in range(self.max_resample_attempts):
+            idx = self.rng.randint(len(self.real_state_pool))
+            row = self.real_state_pool.iloc[[idx]].reset_index(drop=True)
+
+            vec = row.values.squeeze()
+            if np.all(vec >= self.low) and np.all(vec <= self.high):
+                return row
+
+        raise RuntimeError("No valid real reset state within bounds.")
+
+    @staticmethod
+    def _normalize(w):
+        s = sum(w)
+        return [x / s for x in w]
+
+    def _get_sampling_config(self, progress: float) -> dict:
+        progress = np.clip(progress, 0.0, 1.0)
+
+        # -------------------------
+        # 阶段权重设计(非线性 + 提高虚拟工况)
+        # -------------------------
+        w_real = (1.0 - progress) ** 1.2  # 历史工况逐渐衰减
+        w_perturb = 0.5 * progress  # 周边扰动按线性增加
+        w_virtual = 0.3 * progress ** 1.5  # 虚拟工况加快增长,后期最大约 0.3
+
+        # perturb 扰动幅度
+        perturb_scale = 0.02 + 0.04 * progress
+
+        return dict(
+            w_real=w_real,
+            w_perturb=w_perturb,
+            w_virtual=w_virtual,
+            perturb_scale=perturb_scale,
+        )
+

+ 107 - 0
models/uf-rl/训练/uf_train/env/env_visual.py

@@ -0,0 +1,107 @@
+import numpy as np
+from stable_baselines3.common.callbacks import BaseCallback
+
+
+
+class UFEpisodeRecorder:
+    """记录episode中的决策和结果"""
+
+    def __init__(self):
+        self.episode_data = []
+        self.current_episode = []
+
+    def record_step(self, obs, action, reward, done, info):
+        """记录单步信息"""
+        step_data = {
+            "obs": obs.copy(),
+            "action": action.copy(),
+            "reward": reward,
+            "done": done,
+            "info": info.copy() if info else {}
+        }
+        self.current_episode.append(step_data)
+
+        if done:
+            self.episode_data.append(self.current_episode)
+            self.current_episode = []
+
+    def get_episode_stats(self, episode_idx=-1):
+        """获取episode统计信息"""
+        if not self.episode_data:
+            return {}
+
+        episode = self.episode_data[episode_idx]
+        total_reward = sum(step["reward"] for step in episode)
+        avg_recovery = np.mean([step["info"].get("recovery", 0) for step in episode if "recovery" in step["info"]])
+        feasible_steps = sum(1 for step in episode if step["info"].get("feasible", False))
+
+        return {
+            "total_reward": total_reward,
+            "avg_recovery": avg_recovery,
+            "feasible_steps": feasible_steps,
+            "total_steps": len(episode)
+        }
+
+
+# ==== 定义强化学习训练回调器 ====
+class UFTrainingCallback(BaseCallback):
+    """
+    强化学习训练回调,用于记录每一步的数据到 recorder。
+    1. 不依赖环境内部 last_* 属性
+    2. 使用环境接口提供的 obs、actions、rewards、dones、infos
+    3. 自动处理 episode 结束时的统计
+    """
+
+    def __init__(self, recorder, verbose=0):
+        super(UFTrainingCallback, self).__init__(verbose)
+        self.recorder = recorder
+
+    def _on_step(self) -> bool:
+        try:
+            new_obs = self.locals.get("new_obs")
+            actions = self.locals.get("actions")
+            rewards = self.locals.get("rewards")
+            dones = self.locals.get("dones")
+            infos = self.locals.get("infos")
+
+            if len(new_obs) > 0:
+                step_obs = new_obs[0]
+                step_action = actions[0] if actions is not None else None
+                step_reward = rewards[0] if rewards is not None else 0.0
+                step_done = dones[0] if dones is not None else False
+                step_info = infos[0] if infos is not None else {}
+                L_s = step_info["L_s"]
+                t_bw_s = step_info["t_bw_s"]
+                initial_tmp = step_info["initial_tmp"]
+                max_TMP_during_filtration = step_info["max_TMP_during_filtration"]
+                tmp_after_ceb = step_info["tmp_after_ceb"]
+                residual_ratio =step_info["residual_ratio"]
+                rec_reward = step_info["rec_reward"]
+                energy_reward = step_info["energy_reward"]
+                recovery = step_info["recovery"]
+                res_penalty = step_info["res_penalty"]
+
+                # 打印当前 step 的信息
+                if self.verbose:
+                    print(f"[Step {self.num_timesteps}] 动作={step_action}, 奖励={step_reward:.3f}, Done={step_done}, L_s={L_s}, t_bw_s={t_bw_s},"
+                          f"residual_ratio = {residual_ratio:.4f},recovery = {recovery:.4f},"
+                          f"rec_reward = {rec_reward:.4f}, energy_reward = {energy_reward:.4f}, res_penalty = {res_penalty:.4f},"
+                          f"initial_tmp = {initial_tmp:.4f}, max_TMP_during_filtration ={max_TMP_during_filtration:.4f}, tmp_after_ceb = {tmp_after_ceb:.4f} ")
+
+                # 记录数据
+                self.recorder.record_step(
+                    obs=step_obs,
+                    action=step_action,
+                    reward=step_reward,
+                    done=step_done,
+                    info=step_info,
+                )
+
+        except Exception as e:
+            if self.verbose:
+                print(f"[Callback Error] {e}")
+
+        return True
+
+
+

BIN
models/uf-rl/训练/uf_train/env/resistance_model_bw.pth


BIN
models/uf-rl/训练/uf_train/env/resistance_model_fp.pth


+ 472 - 0
models/uf-rl/训练/uf_train/env/uf_env.py

@@ -0,0 +1,472 @@
+"""
+超滤强化学习环境模块
+========================
+本模块定义了超滤系统的强化学习环境,包括:
+1. UFParams: 超滤系统参数配置类
+2. 膜阻力与跨膜压差转换函数
+3. simulate_one_supercycle: 超级周期模拟函数
+4. calculate_reward: 奖励函数
+5. is_dead_cycle: 失败判定函数
+6. UFSuperCycleEnv: Gymnasium环境类
+
+模块设计说明:
+- 基于 Gymnasium (原OpenAI Gym) 标准接口
+- 模拟超滤膜的"超级周期"运行(多次物理反洗 + 一次化学反洗)
+- 强化学习智能体通过优化过滤时长和反洗时长来最大化回收率并控制污染累积
+"""
+
+import os
+import torch
+import numpy as np
+import gymnasium as gym
+from gymnasium import spaces
+from uf_train.env.env_params import UFState, UFPhysicsParams, UFStateBounds, UFRewardParams, UFActionSpec
+from uf_train.env.uf_physics import UFPhysicsModel
+from uf_train.env.env_reset import ResetSampler
+import copy
+
+
+
+
+
+class UFSuperCycleEnv(gym.Env):
+    """
+    超滤系统强化学习环境(Gymnasium标准接口)
+    
+    功能:
+    - 模拟超滤膜的超级周期运行
+    - 智能体在每个超级周期选择过滤时长和反洗时长
+    - 目标:最大化回收率同时控制污染累积
+    
+    状态空间 (8维,归一化到 [0,1]):
+        1. TMP0: 初始跨膜压差
+        2. q_UF: 过滤流量
+        3. temp: 水温
+        4. R0: 初始膜阻力
+        5. nuK: 短期污染系数
+        6. slope: 长期污染斜率
+        7. power: 长期污染幂次
+        8. ceb_removal: CEB去除能力
+    
+    动作空间 (离散):
+        - 二维离散动作组合:(过滤时长, 反洗时长)
+        - 过滤时长: L_min_s ~ L_max_s,步长 L_step_s
+        - 反洗时长: t_bw_min_s ~ t_bw_max_s,步长 t_bw_step_s
+        - 总动作数 = len(L_values) × len(t_bw_values)
+    
+    奖励机制:
+        - 基于回收率和残余污染的平衡
+        - 失败 (TMP超限、回收率过低、污染过快) 时给予大负奖励 (-10)
+    
+    终止条件:
+        - terminated: 违反运行约束(失败)
+        - truncated: 达到最大步数 (max_episode_steps)
+    """
+
+    metadata = {"render_modes": ["human"]}
+
+    def __init__(
+            self,
+            physics: UFPhysicsModel,
+            reward_params: UFRewardParams,
+            action_spec:UFActionSpec,
+            statebounds:UFStateBounds,
+            real_state_pool,
+            max_episode_steps: int = 45,
+            RANDOM_SEED = 1024
+    ):
+        """
+        超滤强化学习环境
+
+        参数:
+            physics(UFPhysicsModel): 超滤物理模型
+            reward_params(UFRewardParams): 奖励函数参数
+            max_episode_steps (int): 每个episode的最大步数,默认45
+                注:每步代表一个超级周期(约2-3天),45步约三个月
+        """
+
+        super(UFSuperCycleEnv, self).__init__()
+
+        self.RANDOM_SEED = RANDOM_SEED
+        self.physics = physics
+        self.reward_params = reward_params
+        self.max_episode_steps = max_episode_steps
+
+        self.current_step = 0
+
+
+        # -------- 动作空间 --------
+        self.action_spec = action_spec
+
+        self.L_values = np.arange(
+            self.action_spec.L_min_s,
+            self.action_spec.L_max_s + self.action_spec.L_step_s,
+            self.action_spec.L_step_s,
+        )
+
+        self.t_bw_values = np.arange(
+            self.action_spec.t_bw_min_s,
+            self.action_spec.t_bw_max_s + self.action_spec.t_bw_step_s,
+            self.action_spec.t_bw_step_s,
+        )
+
+        self.num_L = len(self.L_values)
+        self.num_bw = len(self.t_bw_values)
+
+        self.action_space = spaces.Discrete(self.num_L * self.num_bw)
+
+        # -------- 状态空间 --------
+        self.observation_space = spaces.Box(
+            low=0.0,
+            high=1.0,
+            shape=(8,),
+            dtype=np.float32,
+        )
+
+        self.state_bounds = statebounds # 状态边界
+        self.real_state_pool = real_state_pool
+
+        self.reset_sampler = ResetSampler(
+            bounds=self.state_bounds,
+            physics=physics,
+            real_state_pool=self.real_state_pool,
+            max_resample_attempts=50,
+            random_state=np.random.RandomState(RANDOM_SEED)
+        )
+
+
+    def _generate_initial_state(self) -> UFState | None:
+        """
+        在 UFStateBounds 定义的范围内采样一个【合法】初始状态。
+        若采样失败(约束不满足)返回 None,由 reset() 负责重试。
+        """
+
+        b = self.state_bounds
+        A = 128 * 40.0  # 有效膜面积
+
+        # ---- 1. 基础工况 ----
+        # ---- 随机生成 TMP、q_UF、温度 ----
+        TMP0 = np.random.uniform(b.TMP0_min, b.TMP0_max)
+        q_UF = np.random.uniform(b.q_UF_min, b.q_UF_max)
+        temp = np.random.uniform(b.temp_min, b.temp_max)
+
+        # ---- 2. 污染增长参数 ----
+        slope = np.random.uniform(b.slope_min, b.slope_max)
+        power = np.random.uniform(b.power_min, b.power_max)
+
+        # ---- 3. 约束:污染增长速率可实现 ----
+        t_max = 60 if power >= 1 else 1
+        required_nuK_min = slope * power * (t_max ** (power - 1)) * (A / q_UF)
+
+        # 若 required_nuK_min 超过可选范围 → 初始状态非法
+        if required_nuK_min > b.nuK_max:
+            return None
+        # 在可行范围中采样 nuK
+        nuK = np.random.uniform(
+            max(required_nuK_min, b.nuK_min),
+            b.nuK_max
+        )
+
+        # ---- 4. CEB 去除率 ----
+        ceb_removal = np.random.uniform(
+            b.ceb_removal_min,
+            b.ceb_removal_max
+        )
+
+        # ---- 5. 初始膜阻力(物理模型) ----
+        R0 = self.physics.calculate_initial_resistance(
+            TMP=TMP0,
+            q_UF=q_UF,
+            temp=temp
+        )
+
+        return UFState(
+            TMP=TMP0,
+            q_UF=q_UF,
+            temp=temp,
+            R=R0,
+            slope=slope,
+            power=power,
+            nuK=nuK,
+            ceb_removal=ceb_removal,
+        )
+
+    def _get_training_progress(self) -> float:
+        """
+        返回训练进度,用于 reset_sampler 的 curriculum sampling
+        """
+        return min(1.0, self.current_step / self.max_episode_steps )
+
+    def reset(self, seed=None, options=None, max_attempts: int = 1000):
+        super().reset(seed=seed)
+
+        progress = self._get_training_progress()
+
+        for _ in range(max_attempts):
+            state = self.reset_sampler.sample(progress)
+            if state is None:
+                continue
+
+            ok_run = self.physics.check_dead_initial_state(
+                init_state=state,
+                max_steps=self.max_episode_steps,
+                L_s=self.action_spec.L_min_s,
+                t_bw_s=self.action_spec.t_bw_max_s
+            )
+
+            if ok_run:
+                self.state = state
+                break
+        else:
+            raise RuntimeError("无法生成可行初始状态")
+
+        self.current_step = 0
+        self.tmp_over_limit_flag = False
+        self.last_action = None
+        self.max_TMP_during_filtration = self.state.TMP
+
+        return self.get_obs(self.state), {}
+
+    def _get_state_copy(self):
+        return copy.deepcopy(self.state)
+
+    def get_obs(self, state):
+        """
+        构建当前环境归一化状态向量
+        """
+        # === 1. 从 state 读取动态参数 ===
+        TMP = state.TMP
+        q_UF = state.q_UF
+        temp = state.temp
+
+        # === 2. 计算本周期初始膜阻力 ===
+        R = state.R
+
+        # === 3. 从 self.state 读取膜阻力增长模型参数 ===
+        nuk = state.nuK
+        slope = state.slope
+        power = state.power
+        ceb_removal = state.ceb_removal
+
+        # === 4. 从 current_params 动态读取上下限 ===
+        TMP0_min, TMP0_max = self.state_bounds.TMP0_min, self.state_bounds.global_TMP_hard_limit
+        q_UF_min, q_UF_max = self.state_bounds.q_UF_min, self.state_bounds.q_UF_max
+        temp_min, temp_max = self.state_bounds.temp_min, self.state_bounds.temp_max
+        nuK_min, nuK_max = self.state_bounds.nuK_min, self.state_bounds.nuK_max
+        slope_min, slope_max = self.state_bounds.slope_min, self.state_bounds.slope_max
+        power_min, power_max = self.state_bounds.power_min, self.state_bounds.power_max
+        ceb_min, ceb_max = self.state_bounds.ceb_removal_min, self.state_bounds.ceb_removal_max
+
+        # === 5. 归一化计算(clip防止越界) ===
+        TMP0_norm = np.clip((TMP - TMP0_min) / (TMP0_max - TMP0_min), 0, 1)
+        q_UF_norm = np.clip((q_UF - q_UF_min) / (q_UF_max - q_UF_min), 0, 1)
+        temp_norm = np.clip((temp - temp_min) / (temp_max - temp_min), 0, 1)
+
+        # R0 不在 current_params 中定义上下限,设定经验范围
+        R0_norm = np.clip((R - 100.0) / (800.0 - 100.0), 0, 1)
+
+        short_term_norm = np.clip((nuk - nuK_min) / (nuK_max - nuK_min), 0, 1)
+        long_term_slope_norm = np.clip((slope - slope_min) / (slope_max - slope_min), 0, 1)
+        long_term_power_norm = np.clip((power - power_min) / (power_max - power_min), 0, 1)
+        ceb_removal_norm = np.clip((ceb_removal - ceb_min) / (ceb_max - ceb_min), 0, 1)
+
+        # === 6. 构建观测向量 ===
+        obs = np.array([
+            TMP0_norm,
+            q_UF_norm,
+            temp_norm,
+            R0_norm,
+            short_term_norm,
+            long_term_slope_norm,
+            long_term_power_norm,
+            ceb_removal_norm
+        ], dtype=np.float32)
+
+        return obs
+
+    def get_action_values(self, action):
+        """
+        将动作还原为实际时长
+        """
+        L_idx = action // self.num_bw
+        t_bw_idx = action % self.num_bw
+        return self.L_values[L_idx], self.t_bw_values[t_bw_idx]
+
+    def step(self, action):
+        self.current_step += 1
+        L_s, t_bw_s = self.get_action_values(action)
+        L_s = np.clip(L_s, self.action_spec.L_min_s, self.action_spec.L_max_s)
+        t_bw_s = np.clip(t_bw_s, self.action_spec.t_bw_min_s, self.action_spec.t_bw_max_s)
+
+        # 模拟超级周期
+        info, next_state = self.physics.simulate_one_supercycle(state=self.state,L_s=L_s, t_bw_s=t_bw_s)
+        # 根据 info 判断是否成功
+        feasible = self.physics.is_dead_cycle(info)  # True 表示成功循环,False 表示失败
+
+        if info["max_TMP_during_filtration"] >= self.reward_params.global_TMP_hard_limit:
+            self.tmp_over_limit_flag = True
+
+        # ================== 孤立观察下一周期 ==================
+        info_next = None
+        if info["max_TMP_during_filtration"] > self.reward_params.global_TMP_soft_limit:
+            info_next, _ = self.physics.simulate_one_supercycle(state=next_state,L_s=L_s,t_bw_s=t_bw_s)
+
+
+        reward, tmp_penalty, rec_reward, energy_reward, res_penalty = self._calculate_reward(info, info_next)
+        info["tmp_penalty"] = tmp_penalty
+        info["rec_reward"] = rec_reward
+        info["energy_reward"] = energy_reward
+        info["res_penalty"] = res_penalty
+
+        self.state = next_state
+        terminated = False
+
+        # 判断是否到达最大步数
+        truncated = self.current_step >= self.max_episode_steps
+
+        self.last_action = (L_s, t_bw_s)
+        next_obs = self.get_obs(next_state)
+
+        info["feasible"] = feasible
+        info["step"] = self.current_step
+        info["L_s"] = L_s.copy()
+        info["t_bw_s"] = t_bw_s.copy()
+
+        # # ===================== 测试终末奖励:鼓励 TMP 接近初始状态 =====================
+        # # 仅在 episode 自然结束(满步但未提前失败)时触发
+        # if truncated and not terminated:
+        #     TMP_initial = self.TMP0  # reset 时记录的初始 TMP
+        #     TMP_final = next_obs[0]  # next_obs 提供的最终 TMP
+        #
+        #     delta_ratio = abs((TMP_final - TMP_initial) / TMP_initial)
+        #
+        #     alpha = 4.0  # TMP 偏差敏感度
+        #     gamma = 5.0  # 奖励幅度
+        #     stability_reward = gamma * (np.exp(-alpha * delta_ratio) - 1) # 量级在0到-5之间
+        #
+        #     reward += stability_reward
+        #     terminated = True  # episode 正式结束
+
+        # # ===================== 测试结果 =====================
+        # 增加该奖励后强化学习依然能保证奖励收敛,但是损失函数在2-3之间反复震荡,无法降低,见reward_test&loss_test
+        # 原设想是只能听在大额偏移发生前能通过该奖励学习到提前减小偏移步伐,但是实际训练时该惩罚反复被触发
+        # 推测是终末的大额奖惩无法有效传递回过往时间步引导智能体学习,可能由于状态中缺少预测值,智能体会将其观测为不可控事件,暂时不添加该奖励,TODO:等待优化
+
+        return next_obs, reward, terminated, truncated, info
+
+    def _calculate_reward(self, info: dict, info_next) -> float:
+        """
+        计算强化学习奖励函数(扩展版)
+
+        功能:
+        - 平衡回收率、残余污染和吨水电耗三个目标
+        - TMP不直接参与奖励计算(通过失败判定间接影响)
+        - 使用 tanh 函数实现平滑的非线性奖励
+
+        参数:
+            info (dict): 周期性能指标字典,需包含
+                - recovery: 回收率 [0-1]
+                - R_after_ceb: 本周期结束膜阻力
+                - initial_R: 本周期初始膜阻力
+                - delta_R_allow: 本周期允许最大阻力上升
+                - ton_water_energy_kWh_per_m3: 本周期吨水电耗
+
+        返回:
+            float: 奖励值(通常在 -3 到 +3 之间)
+
+        设计思想:
+        - 高回收率 → 水资源利用率高 → 正奖励
+        - 低残余污染 → 膜长期稳定运行 → 正奖励
+        - 低吨水电耗 → 节能 → 正奖励
+        - 三者需要权衡:过短的过滤时间提高回收率但污染去除不彻底;过长时间污染控制好但回收率下降,过高功率增加耗能
+
+        参考点设计:
+        - 残余污染:
+            - 高污染参考点 = 1 / self.max_episode_steps
+            - 平衡点 = 0.5 / self.max_episode_steps
+        - 吨水电耗:
+            - 高点 = 0.1034 kWh/m³
+            - 平衡点 = 0.1011 kWh/m³
+            - 低点 = 0.0993 kWh/m³
+        - 回收率参考点保持原有设计
+        """
+
+        #新增:将TMP超限改为持续惩罚
+        # ========== TMP 超标风险惩罚项 ==========
+        tmp = info["max_TMP_during_filtration"]
+        tmp_soft = self.reward_params.global_TMP_soft_limit
+        tmp_hard = self.reward_params.global_TMP_hard_limit
+
+        if self.tmp_over_limit_flag:
+            tmp_state_penalty = -self.reward_params.w_tmp_hard
+        elif tmp <= tmp_soft:
+            tmp_state_penalty = 0.0
+        elif tmp < tmp_hard:
+            x = (tmp - tmp_soft) / (tmp_hard - tmp_soft)
+            tmp_state_penalty = -self.reward_params.w_tmp * x ** self.reward_params.p
+
+        # -------- TMP 趋势惩罚 --------
+        tmp_trend_penalty = 0.0
+        if info_next is not None:
+            delta_tmp = info_next["max_TMP_during_filtration"] - tmp
+            tmp_trend_penalty = -self.reward_params.w_trend * delta_tmp
+
+        tmp_penalty = tmp_state_penalty + tmp_trend_penalty
+
+        # ========== 提取性能指标 ==========
+        recovery = info["recovery"]  # 回收率 [0-1]
+
+        # 污染比例:实际上升的阻力 / 允许上升的阻力
+        # 允许上升的阻力值 = 当前阻力值软上限 - 当前阻力
+        residual_ratio = info['residual_ratio']
+
+        # 吨水电耗指标
+        energy = info["ton_water_energy_kWh_per_m3"]
+
+        # ========== 回收率奖励项 ==========
+        # 将回收率归一化到 [0, 1] 区间(基于预期范围)
+        rec_norm = (recovery - self.reward_params.rec_low) / (self.reward_params.rec_high - self.reward_params.rec_low)
+
+        # 使用 tanh 函数构建平滑的 S 型奖励曲线
+        # - rec_norm = 0.5 时(回收率处于中间值),rec_reward = 0
+        # - rec_norm > 0.5 时,rec_reward > 0(鼓励高回收率)
+        # - rec_norm < 0.5 时,rec_reward < 0(惩罚低回收率)
+        # - k_rec 控制曲线陡峭程度,越大变化越陡
+        rec_reward = np.clip(np.tanh(self.reward_params.k_rec * (rec_norm - 0.5)), -1, 1)
+
+        # ========== 残余污染惩罚项 ==========
+        # 新参考点:每步允许上升比例 = 1 / max_episode_steps
+        # 平衡点 = 0.5 / max_episode_steps
+        ref_residual = 0.5 / self.max_episode_steps
+
+        # 使用 tanh 构建惩罚曲线
+        # - residual_ratio < 平衡点时,res_penalty > 0(奖励低污染)
+        # - residual_ratio > 平衡点时,res_penalty < 0(惩罚高污染)
+        # - k_res 控制曲线陡峭程度
+        res_penalty = -np.tanh(self.reward_params.k_res * (residual_ratio / ref_residual - 1))
+
+        # ========== 吨水电耗奖励项 ==========
+        # 设置高/平衡/低点
+        energy_low = 0.0993
+        energy_high = 0.1034
+
+        # 将能耗归一化到 [0, 1],平衡点对应 energy_norm = 0.5
+        energy_norm = (energy - energy_low) / (energy_high - energy_low)
+
+        # 使用 tanh 构建平滑奖励
+        # - energy_norm < 0.5 时,energy_reward > 0(节能奖励)
+        # - energy_norm > 0.5 时,energy_reward < 0(高能耗惩罚)
+        # - k_energy 控制曲线陡峭程度
+        energy_reward = -np.tanh(self.reward_params.k_energy * (energy_norm - 0.5))
+
+        # ========== 组合奖励 ==========
+        # 简单线性组合三项(为污染项加权)
+        total_reward = rec_reward + 2.0 * res_penalty + energy_reward + tmp_penalty
+
+
+        # 可选:添加平移项使特定点的奖励为零(当前未使用)
+        # total_reward -= offset
+
+        return total_reward, tmp_penalty, rec_reward, energy_reward, res_penalty
+
+

+ 529 - 0
models/uf-rl/训练/uf_train/env/uf_physics.py

@@ -0,0 +1,529 @@
+"""
+uf_physics.py
+
+超滤(UF)系统物理模型与确定性计算规则模块。
+
+本模块定义:
+- 与强化学习算法无关的物理规律
+- 与环境状态更新相关的确定性计算
+- 超滤系统中的基础物理量转换关系
+
+设计原则:
+- 不依赖 env / agent / trainer
+- 不包含强化学习语义
+- 不负责参数加载策略(由上层控制)
+
+该模块应可被:
+- 强化学习环境
+- 离线仿真
+- 工艺分析脚本
+独立复用。
+"""
+
+import numpy as np
+import copy
+from uf_train.env.env_params import UFState, UFPhysicsParams
+
+
+class UFPhysicsModel:
+    """
+    超滤系统无状态物理模型(Physical Rules)
+
+    说明:
+    - 本类不保存任何动态状态
+    - 所有计算结果仅依赖输入参数
+    - 表达的是“物理规律”,而不是“设备实例”
+    """
+
+    def __init__(
+            self,
+            phys_params: UFPhysicsParams,
+            resistance_model_fp=None,
+            resistance_model_bw=None,
+    ):
+        """
+        参数:
+            phys_params: 物理/工艺固定参数
+            resistance_model_fp: 过滤阶段阻力增长模型
+            resistance_model_bw: 反洗阶段阻力下降模型
+        """
+        self.p = phys_params
+        self.model_fp = resistance_model_fp
+        self.model_bw = resistance_model_bw
+
+    # ==========================================================
+    # 水温-粘度关系
+    # ==========================================================
+    def viscosity(self, temp: float) -> float:
+        """
+        锡山水厂水温粘度修正公式
+
+        功能:根据水温计算水的动力粘度(考虑温度影响)
+
+        参数:
+            temp (float): 水温(摄氏度)
+
+        返回:
+            float: 水的动力粘度 μ (Pa·s)
+
+        原理:
+        - 水的粘度随温度升高而降低
+        - 25℃时纯水粘度约为 0.00089 Pa·s
+        - 本公式基于锡山水厂PLC系统的经验修正因子
+
+        注意:
+        - 本公式基于纯水粘度修正
+        - 实际水厂水质与纯水有差异,对粘度有一定影响
+        - 未来可根据实际水质进一步校准
+        """
+        # 温度归一化(相对于300K)
+        x = (temp + 273.15) / 300  # 摄氏度转开尔文
+
+        # 温度修正因子(经验公式,基于锡山水厂PLC)
+        factor = 890 / (
+                280.68 * x ** -1.9 +
+                511.45 * x ** -7.7 +
+                61.131 * x ** -19.6 +
+                0.45903 * x ** -40
+        )
+
+        # 计算修正后的粘度(25℃标准粘度 / 修正因子)
+        mu = 0.00089 / factor  # [Pa·s]
+
+        return mu
+
+    # ==========================================================
+    # TMP → 膜阻力
+    # ==========================================================
+    def resistance_from_tmp(
+        self,
+        tmp: float,
+        q_UF: float,
+        temp: float
+    ) -> float:
+        """
+        由跨膜压差计算膜阻力
+
+        功能:根据 Darcy 定律,由跨膜压差反推膜阻力
+
+        参数:
+            tmp (float): 跨膜压差 TMP (MPa)
+            q_UF (float): 过滤流量 (m³/h)
+            temp (float): 水温 (℃)
+
+        返回:
+            float: 膜阻力 R(已缩放 1e10)
+
+        原理:
+            Darcy 定律:J = TMP / (μ × R)
+            其中:
+            - J: 膜通量 [m/s]
+            - TMP: 跨膜压差 [Pa]
+            - μ: 水的动力粘度 [Pa·s]
+            - R: 膜阻力 [m⁻¹]
+
+            反解得:R = TMP / (J × μ)
+
+        注意:
+            - 超滤膜阻力实际量级为 1e12 m⁻¹
+            - 为便于数值计算,已缩放 1e10 倍至 1e2 量级
+        """
+        # 温度修正后的水粘度
+        mu = self.viscosity(temp) # [Pa·s]
+        # 膜有效面积(在参数文件中配置)
+        A = self.p.A
+
+        # 跨膜压差单位转换:MPa → Pa
+        TMP_Pa = tmp * 1e6  # [Pa]
+
+        # 计算膜通量:流量 / 面积
+        J = q_UF / A / 3600  # [m³/h] → [m³/(m²·s)] = [m/s]
+
+        # 物理约束检查:通量和粘度必须为正
+        if J <= 0 or mu <= 0:
+            return np.nan
+
+        # 根据 Darcy 定律计算膜阻力并缩放
+        R = TMP_Pa / (J * mu) / 1e10  # [m⁻¹] → [缩放单位]
+
+        return float(R)
+
+    # ==========================================================
+    # 膜阻力 → TMP
+    # ==========================================================
+    def tmp_from_resistance(
+        self,
+        R: float,
+        q_UF: float,
+        temp: float
+    ) -> float:
+        """
+        由膜阻力计算跨膜压差
+
+        功能:根据 Darcy 定律,由膜阻力计算跨膜压差(_calculate_resistance 的逆运算)
+
+        参数:
+            R (float): 膜阻力(已缩放 1e10)
+            q_UF (float): 过滤流量 (m³/h)
+            temp (float): 水温 (℃)
+
+        返回:
+            float: 跨膜压差 TMP (MPa)
+
+        原理:
+            Darcy 定律:TMP = J × μ × R
+            其中:
+            - J: 膜通量 [m/s]
+            - μ: 水的动力粘度 [Pa·s]
+            - R: 膜阻力 [m⁻¹]
+        """
+        # 温度修正后的水粘度
+        mu = self.viscosity(temp) # [Pa·s]
+        # 膜有效面积(在参数文件中配置)
+        A = self.p.A
+
+        # 计算膜通量
+        J = q_UF / A / 3600  # [m/s]
+
+        # 根据 Darcy 定律计算跨膜压差(还原缩放)
+        TMP_Pa = R * J * mu * 1e10  # [缩放单位] → [Pa]
+
+        # 单位转换:Pa → MPa
+        tmp = TMP_Pa / 1e6  # [MPa]
+
+        return float(tmp)
+
+    def delta_resistance_filter(
+        self,
+        state,
+        L_s: float
+    ) -> float:
+        """
+        过滤阶段膜阻力上升量(数据驱动模型)
+
+        参数:
+            state (UFState): 当前运行状态
+            L_s (float): 过滤时长 [s]
+
+        返回:
+            float: 阻力增量 ΔR
+        """
+        if self.model_fp is None:
+            raise RuntimeError("过滤阶段阻力模型未注入")
+
+        return float(self.model_fp(state, L_s))
+
+    def delta_resistance_backwash(
+        self,
+        state,
+        R0: float,
+        R_end: float,
+        L_h_next_start: float,
+        t_bw_s: float
+    ) -> float:
+        """
+        物理反洗可去除的膜阻力(数据驱动模型)
+
+        返回:
+            float: 可去除阻力
+        """
+        if self.model_bw is None:
+            raise RuntimeError("反洗阶段阻力模型未注入")
+
+        return float(
+            self.model_bw(
+                state,
+                R0,
+                R_end,
+                L_h_next_start,
+                t_bw_s
+            )
+        )
+
+    def backwash_water_volume(self, t_bw_s: float) -> float:
+        """
+        计算物理反洗水耗
+
+        参数:
+            t_bw_s (float): 反洗时长 [s]
+
+        返回:
+            float: 反洗水耗 [m³]
+        """
+        return float(self.p.q_bw_m3ph * t_bw_s / 3600.0)
+
+    def simulate_one_supercycle(self, state: UFState, L_s: float, t_bw_s: float):
+        """
+        模拟一个超级周期(Super Cycle)
+        返回 info 字典 + 更新后的 UFState
+        """
+        # ========== 初始化周期参数 ==========
+        L_h = float(L_s) / 3600.0  # 过滤时长转换:秒 → 小时
+
+        # 初始状态(统一用 TMP / R,与当前 state 保持一致)
+        initial_tmp = state.TMP # 记录周期初始跨膜压差
+        initial_R = state.R # 记录周期初始膜阻力
+        tmp = initial_tmp # 当前跨膜压差
+
+
+        # 跟踪变量(用于记录周期内的极值)
+        max_tmp_during_filtration = tmp  # 周期内最大TMP
+        min_tmp_during_filtration = tmp  # 周期内最小TMP
+        max_residual_increase = 0.0  # 周期内最大残余污染增量
+
+        # ========== 计算小周期数量 ==========
+        # 小周期时长 = 过滤时长 + 物理反洗时长
+        t_small_cycle_h = (L_s + t_bw_s) / 3600.0  # [小时]
+
+        # 计算一个超级周期内包含多少个小周期
+        # k = floor(CEB间隔时间 / 小周期时长)
+        k_bw_per_ceb = int(np.floor(self.p.T_ceb_interval_h / t_small_cycle_h))
+        if k_bw_per_ceb < 1:
+            k_bw_per_ceb = 1  # 至少包含1个小周期
+
+        # ========== 循环模拟每个小周期(过滤 + 物理反洗) ==========
+        for idx in range(k_bw_per_ceb):
+            # --- 小周期开始状态 ---
+            tmp_run_start = tmp # 本次过滤开始时的TMP
+            q_UF = state.q_UF # 过滤流量
+            temp = state.temp # 水温
+
+            # --- 过滤阶段:膜阻力上升 ---
+            R_run_start = self.resistance_from_tmp(tmp_run_start, q_UF, temp) # 过滤开始时的膜阻力
+            d_R = self.delta_resistance_filter(state, L_s) # 过滤阶段膜阻力增量
+            R_peak = R_run_start + d_R # 过滤结束时的膜阻力(峰值)
+            tmp_peak = self.tmp_from_resistance(R_peak, q_UF, temp) # 过滤结束时的TMP(峰值)
+
+            # 更新TMP极值记录
+            max_tmp_during_filtration = max(max_tmp_during_filtration, tmp_peak)
+            min_tmp_during_filtration = min(min_tmp_during_filtration, tmp_run_start)
+
+            # --- 物理反洗阶段:膜阻力下降 ---
+            # 计算累积运行时间(用于长期污染模型)
+            L_h_next_start = (L_s + t_bw_s) / 3600.0 * (idx + 1)  # 下一小周期起始时间
+            # 调用膜阻力下降模型,计算物理反洗可去除的阻力
+            reversible_R = self.delta_resistance_backwash(state, initial_R, R_peak, L_h_next_start, t_bw_s)
+
+            # 物理反洗后的膜阻力
+            R_after_bw = R_peak - reversible_R
+            tmp_after_bw = self.tmp_from_resistance(R_after_bw, q_UF, temp)
+
+            # 计算残余污染增量(反洗后的TMP相对本次开始的增加)
+            residual_inc = tmp_after_bw - tmp_run_start
+            max_residual_increase = max(max_residual_increase, residual_inc)
+            # 更新TMP(作为下一小周期的起始TMP)
+            tmp = tmp_after_bw
+
+        # ========== 化学增强反洗 (CEB) ==========
+        # CEB比物理反洗更彻底,可去除部分不可逆污染
+        R_after_ceb = R_peak - state.ceb_removal # CEB后的膜阻力
+        tmp_after_ceb = self.tmp_from_resistance(R_after_ceb, q_UF, temp) # CEB后的TMP
+
+        # ============================================================
+        # 计算周期性能指标
+        # ============================================================
+
+        # ========== 水量平衡计算 ==========
+        # 进水总量(所有小周期的过滤进水之和)
+        V_feed_super = k_bw_per_ceb * state.q_UF * L_h
+
+        # 损失水量(物理反洗 + 化学反洗)
+        V_loss_super = k_bw_per_ceb * self.backwash_water_volume(t_bw_s) + self.p.v_ceb_m3
+        V_net = max(0.0, V_feed_super - V_loss_super)
+
+        # 回收率(净产水 / 进水总量)
+        # 加1e-12避免除零,max确保非负
+        recovery = max(0.0, V_net / max(V_feed_super, 1e-12))
+
+        # ========== 时间与能耗计算 ==========
+        # 超级周期总时长
+        T_super_h = k_bw_per_ceb * (L_s + t_bw_s) / 3600.0 + self.p.t_ceb_s / 3600.0  # [小时]
+
+        # 日均产水时间(24小时内实际产水的时间)
+        daily_prod_time_h = k_bw_per_ceb * L_h / T_super_h * 24.0  # [小时]
+
+        # 吨水电耗(从查找表获取最接近的值)
+        # 从物理参数类中获取查找表
+        closest_L = min(self.p.energy_lookup.keys(), key=lambda x: abs(x - L_s))
+        ton_water_energy = self.p.energy_lookup[closest_L]  # [kWh/m³]
+
+        # ===== 新指标:膜阻力允许上升空间 =====
+        # 该指标根据当前最大跨膜压差距离软约束跨膜压差的距离,动态计算当前周期允许上升的膜阻力值,用于后续清洗效果奖励计算
+
+        delta_R_allow = max(
+            self.resistance_from_tmp(self.p.global_TMP_soft_limit, state.q_UF, state.temp) -
+            self.resistance_from_tmp(max_tmp_during_filtration, state.q_UF, state.temp),
+            1e-6
+        )
+        residual_ratio = (R_after_ceb - initial_R) / delta_R_allow
+
+        # ========== 构建性能指标字典 ==========
+        info = {
+            # 运行参数
+            "q_UF": state.q_UF,  # 过滤流量
+            "temp": state.temp,  # 水温
+
+            # 水量指标
+            "recovery": recovery,  # 回收率
+            "V_feed_super_m3": V_feed_super,  # 进水总量
+            "V_loss_super_m3": V_loss_super,  # 损失水量
+            "V_net_super_m3": V_net,  # 净产水量
+
+            # 时间指标
+            "supercycle_time_h": T_super_h,  # 超级周期时长
+            "daily_prod_time_h": daily_prod_time_h,  # 日均产水时间
+            "k_bw_per_ceb": k_bw_per_ceb,  # 小周期数量
+
+            # TMP指标
+            "max_TMP_during_filtration": max_tmp_during_filtration,  # 周期内最大TMP
+            "min_TMP_during_filtration": min_tmp_during_filtration,  # 周期内最小TMP
+            "initial_tmp": initial_tmp,  # 周期初始TMP
+            "tmp_after_ceb": tmp_after_ceb,  # CEB后TMP
+
+            # 膜阻力指标
+            "initial_R": initial_R,  # 周期初始膜阻力
+            "R_after_ceb": R_after_ceb,  # CEB后膜阻力
+            "max_residual_increase_per_run": max_residual_increase,  # 最大残余污染增量
+            "delta_R_allow": delta_R_allow,  # 污染允许增长空间
+            "residual_ratio" : residual_ratio, # 污染上升比例
+
+            # 能耗指标
+            "ton_water_energy_kWh_per_m3": ton_water_energy,  # 吨水电耗
+        }
+
+        # 更新 state
+        next_state = copy.deepcopy(state)
+        next_state.TMP = tmp_after_ceb
+        next_state.R = R_after_ceb
+
+        # ========== 可选更新的参数 ==========
+        # 这些参数可根据实际情况动态调整,预留扩展接口
+        next_state.nuK = state.nuK  # 短期污染系数
+        next_state.slope = state.slope  # 长期污染斜率
+        next_state.power = state.power  # 长期污染幂次
+        next_state.ceb_removal = state.ceb_removal  # CEB去除能力
+        next_state.q_UF = state.q_UF  # 过滤流量
+        next_state.temp = state.temp  # 水温
+
+        return info, next_state
+
+    def is_dead_cycle(self, info: dict) -> bool:
+        """
+            判断当前超级周期是否成功(可行)
+
+            功能:
+            - 检查超级周期是否违反运行约束
+            - 用于强化学习环境单步step的失败判定(terminated条件)
+            - True表示成功,False表示失败
+
+            参数:
+                info (dict): simulate_one_supercycle() 返回的性能指标字典
+
+            返回:
+                bool: True表示成功周期,False表示失败周期
+
+            失败条件(任一满足即失败):
+            1. TMP超限:max_TMP > global_TMP_limit
+               - 原因:TMP过高会损坏膜或影响产水质量
+               - 阈值:0.08 MPa(可配置)
+
+            2. 回收率过低:recovery < 0.75
+               - 原因:回收率太低说明反洗水耗过大,经济性差
+               - 阈值:75%(可调整)
+
+            3. 残余污染累积过快:(R_after_ceb - R0) / R0 > 0.1
+               - 原因:单个超级周期污染增长超过10%,长期运行不可持续
+               - 阈值:10%(可调整)
+            """
+        # ========== 获取关键指标 ==========
+        TMP_limit = self.p.global_TMP_hard_limit  # TMP硬约束上限
+        max_tmp = info.get("max_TMP_during_filtration", 0)  # 周期内最大TMP
+        recovery = info.get("recovery", 1.0)  # 回收率
+        R_after_ceb = info.get("R_after_ceb", 0)  # CEB后膜阻力
+        R0 = info.get("initial_R", 1e-6)  # 初始膜阻力
+        delta_R_allow = info.get("delta_R_allow", 1e-6)  # 允许上升的膜阻力(加小值避免除零)
+
+        # ========== 失败条件检查 ==========
+        # 条件1:TMP超限
+        if max_tmp > TMP_limit:
+            return False  # 失败
+
+        # 条件2:回收率过低
+        if recovery < 0.75:
+            return False  # 失败
+
+        # 条件3:污染增长比例超过容许范围
+        residual_increase = (R_after_ceb - R0) / delta_R_allow
+        if residual_increase > 1 / 30:
+            return False  # 失败
+
+        # 所有条件通过
+        return True  # 成功
+
+    def check_dead_initial_state(
+            self,
+            init_state: UFState,
+            max_steps: int = 45,
+            L_s: float = 3800.0,
+            t_bw_s: float = 60.0
+    ) -> bool:
+        """
+        判断给定初始状态在指定动作策略下,是否为物理可行(non-dead)。
+
+        从 init_state 出发,使用固定动作 (L_s, t_bw_s)
+        连续模拟 max_steps 个 supercycle:
+
+            - 任意一次 is_dead_cycle(info) 为 False → 必死
+            - 任意一步 TMP0 < 0 → 必死
+            - 任意异常 → 必死
+
+        参数:
+            init_state:
+                初始 UFState(不会被原地修改)
+            max_steps:
+                前向模拟的最大步数
+            L_s:
+                过滤时长(秒)
+            t_bw_s:
+                物理反洗时长(秒)
+
+        返回:
+            bool:
+                True  → 物理可行(non-dead)
+                False → 必死状态
+        """
+        import copy
+
+        # 使用局部副本,确保 physics 无副作用
+        curr_state = copy.deepcopy(init_state)
+
+        for step in range(max_steps):
+            try:
+                info, next_state = self.simulate_one_supercycle(
+                    curr_state,
+                    L_s=L_s,
+                    t_bw_s=t_bw_s
+                )
+            except Exception:
+                # 任何异常都视为物理不可行
+                return False
+
+            # 单步物理可行性判定
+            if not self.is_dead_cycle(info):
+                return False
+
+            # 物理硬约束:TMP 不允许为负
+            if next_state.TMP < 0:
+                return False
+
+            # 进入下一步
+            curr_state = next_state
+
+        return True
+
+
+
+
+
+
+

+ 169 - 0
models/uf-rl/训练/uf_train/env/uf_resistance_models_define.py

@@ -0,0 +1,169 @@
+"""
+超滤膜阻力模型模块
+====================
+本模块定义了超滤膜阻力的动态变化模型,包括:
+1. ResistanceIncreaseModel: 过滤阶段膜阻力上升模型
+2. ResistanceDecreaseModel: 反洗阶段膜阻力下降模型
+
+这些模型用于模拟超滤膜在运行过程中的阻力变化,是强化学习环境的核心组件。
+"""
+
+import torch
+import numpy as np
+from uf_train.env.env_params import UFState, UFPhysicsParams
+
+
+# ==================== 膜阻力上升模型 ====================
+class ResistanceIncreaseModel(torch.nn.Module):
+    """
+    过滤阶段膜阻力上升模型
+    
+    功能说明:
+    - 计算在过滤阶段膜阻力的增长量 ΔR
+    - 膜阻力上升主要由污染物在膜表面的累积引起
+    - 阻力增长速率与膜通量(J)和过滤时长(L_s)相关
+    
+    模型公式:
+        ΔR = nuK × J × L_s
+        其中:
+        - nuK: 膜阻力增长系数(反映水质污染特性)
+        - J: 膜通量 = q_UF / A / 3600 [m/s]
+        - L_s: 过滤时长 [秒]
+    """
+
+    def __init__(self, phys: UFPhysicsParams):
+        """初始化膜阻力上升模型"""
+        super().__init__()
+        self.phys = phys
+
+    def forward(self, state: UFState, L_s: float) -> float:
+        """
+        前向传播:计算膜阻力上升量
+        
+        参数:
+            state (UFState): 超滤运行状态变量对象,包含:
+                - q_UF: 过滤进水流量 [m³/h]
+                - nuK: 膜阻力增长系数 [m⁻¹/s]
+            L_s (float): 过滤时长 [秒]
+        
+        返回:
+            float: 膜阻力上升量 ΔR(已缩放1e10)
+        
+        注意:
+            - 实际膜阻力量级为1e12,为便于数值计算已缩放至1e2量级
+            - 膜面积 A = 128组 × 40 m²/组 = 5120 m²,现已优化为 UFPhysicsParams类配置
+        """
+        # 加载膜有效面积(锡山水厂配置:128组膜,每组40m²)
+        A = self.phys.A
+        
+        # 计算膜通量 J = 流量 / 面积 / 时间单位转换
+        # q_UF [m³/h] → J [m³/(m²·s)]
+        J = state.q_UF / A / 3600.0  # [m/s]
+        
+        # 膜阻力上升模型(线性模型,已缩放)
+        # nuK: 阻力增长速率,反映水质污染特性
+        # J: 膜通量,通量越大污染速率越快
+        # L_s: 过滤时间,时间越长累积污染越多
+        dR = state.nuK * J * L_s  # [缩放后的阻力单位]
+        
+        return float(dR)
+
+
+# ==================== 膜阻力下降模型 ====================
+class ResistanceDecreaseModel(torch.nn.Module):
+    """
+    反洗阶段膜阻力下降模型
+    
+    功能说明:
+    - 计算物理反冲洗能够去除的膜阻力量
+    - 区分可逆污染和不可逆污染
+    - 反洗时长影响去除效率
+    
+    模型原理:
+    1. 膜污染分为两类:
+       - 可逆污染:可通过物理反洗去除(如表面颗粒物)
+       - 不可逆污染:无法通过物理反洗去除(如孔内吸附污染)
+    
+    2. 不可逆污染累积模型:
+       R_irr = R0 + slope × t^power
+       其中 t 为累积运行时间
+    
+    3. 反洗效率模型:
+       time_gain = 1 - exp(-t_bw / τ)
+       反洗时间越长,去除效率越高,但存在上限
+    """
+
+    def __init__(self, phys: UFPhysicsParams):
+        """初始化膜阻力下降模型(无需训练参数)"""
+        super().__init__()
+        self.phys = phys
+
+    def forward(self,state: UFState, R0, R_end, L_h_next_start, t_bw_s):
+        """
+        前向传播:计算物理反洗能够去除的膜阻力
+        
+        参数:
+            state (UFState): 超滤运行状态变量对象,包含:
+                - slope: 不可逆污染增长斜率
+                - power: 不可逆污染增长幂次
+            phys  (UFPhysicsParams): 超滤运行固定参数对象,包含:
+                - tau_bw_s: 反洗时长影响的时间尺度
+
+            R0 (float): 本超级周期初始膜阻力
+            R_end (float): 过滤结束时的膜阻力(峰值)
+            L_h_next_start (float): 下一小周期起始时的累积运行时间 [小时]
+            t_bw_s (float): 物理反洗时长 [秒]
+        
+        返回:
+            float: 物理反洗实际去除的膜阻力量
+        
+        计算步骤:
+            1. 基于长期污染模型计算本周期的不可逆污染增量
+            2. 计算可逆污染量 = 当前总污染 - 不可逆污染
+            3. 应用时间因子(反洗时长的影响)
+            4. 返回实际去除的阻力(不超过可逆污染量)
+        """
+        # ========== 步骤1:计算不可逆污染累积 ==========
+        # 使用幂律模型描述长期不可逆污染的累积
+        # R_irr(t) = R0 + slope × t^power
+
+        # 下一小周期开始时的理论膜阻力
+        delta_R = state.slope * (L_h_next_start ** state.power)
+        R_next_start = R0 + delta_R
+
+        # ========== 步骤2:计算可逆污染 ==========
+        # 可逆污染 = 当前污染 - 理论长期污染
+        reversible_R = max(R_end - R_next_start, 0.0)
+        
+        # ========== 步骤3:计算反洗时间效率因子 ==========
+        # 使用指数衰减模型:time_gain = 1 - exp(-t_bw / τ)
+        # τ (tau_bw_s): 时间尺度参数
+        # - 反洗时间 t_bw = 0 时,time_gain = 0(无去除效果)
+        # - 反洗时间 t_bw → ∞ 时,time_gain → 1(达到最大效率)
+        # - 反洗时间 t_bw = τ 时,time_gain ≈ 0.632(去除63.2%)
+        # τ参考计算: 以60s去除效率高于95%的要求推算得τ取20s
+        # 该取值下:
+        # - 反洗时间 t_bw = 40s 时,time_gain ≈ 0.865(去除86.5%)
+        # - 反洗时间 t_bw = 60s 时,time_gain ≈ 0.950(去除95.0%)
+        tau = self.phys.tau_bw_s
+        time_gain = 1.0 - np.exp(-t_bw_s / tau)
+        
+        # ========== 步骤4:计算实际去除的膜阻力 ==========
+        # 实际去除量 = 可逆污染量 × 时间效率因子
+        dR_bw = reversible_R * time_gain
+        
+        # 确保去除量不超过可逆污染总量(物理约束)
+        return float(np.clip(dR_bw, 0.0, reversible_R))
+
+
+# ===== 主程序 =====
+if __name__ == "__main__":
+    phys = UFPhysicsParams()
+
+    model_fp = ResistanceIncreaseModel(phys)
+    model_bw = ResistanceDecreaseModel(phys)
+
+    torch.save(model_fp.state_dict(), "resistance_model_fp.pth")
+    torch.save(model_bw.state_dict(), "resistance_model_bw.pth")
+
+    print("模型已安全保存为 resistance_model_fp.pth、resistance_model_bw.pth")

+ 59 - 0
models/uf-rl/训练/uf_train/env/uf_resistance_models_load.py

@@ -0,0 +1,59 @@
+import os
+import torch
+from pathlib import Path
+from uf_train.env.uf_resistance_models_define import ResistanceDecreaseModel, ResistanceIncreaseModel
+
+
+# ==================== 膜阻力模型加载函数 ====================
+def load_resistance_models(phys):
+    """
+    加载膜阻力预测模型(单例模式)
+
+    功能:
+    - 加载预训练的膜阻力上升模型和下降模型
+    - 使用全局变量实现单例模式,避免重复加载
+    - 仅在首次调用时执行加载操作
+
+    返回:
+        tuple: (resistance_model_fp, resistance_model_bw)
+            - resistance_model_fp: 过滤阶段阻力上升模型
+            - resistance_model_bw: 反洗阶段阻力下降模型
+
+    注意:
+    - 模型文件必须与本脚本位于同一目录
+    - 模型已设置为推理模式(eval),不会更新参数
+    """
+    # 声明全局变量(实现单例模式)
+    global resistance_model_fp, resistance_model_bw
+
+    # 检查模型是否已加载(避免重复加载)
+    if "resistance_model_fp" in globals() and resistance_model_fp is not None:
+        return resistance_model_fp, resistance_model_bw
+
+    print("🔄 正在加载膜阻力模型...")
+
+    # 初始化模型对象
+    resistance_model_fp = ResistanceIncreaseModel(phys)
+    resistance_model_bw = ResistanceDecreaseModel(phys)
+
+    # 获取当前脚本所在目录
+    base_dir = Path(__file__).resolve().parent
+
+    # 构造模型文件路径
+    fp_path = base_dir / "resistance_model_fp.pth"   # 过滤阶段模型
+    bw_path = base_dir / "resistance_model_bw.pth"   # 反洗阶段模型
+
+    # 检查模型文件是否存在
+    assert fp_path.exists(), f"缺少膜阻力上升模型文件: {fp_path.name}"
+    assert bw_path.exists(), f"缺少膜阻力下降模型文件: {bw_path.name}"
+
+    # 加载模型权重(map_location="cpu" 确保在没有GPU的环境也能运行)
+    resistance_model_fp.load_state_dict(torch.load(fp_path, map_location="cpu"))
+    resistance_model_bw.load_state_dict(torch.load(bw_path, map_location="cpu"))
+
+    # 设置为推理模式(禁用 dropout、batchnorm 等训练特性)
+    resistance_model_fp.eval()
+    resistance_model_bw.eval()
+
+    print("✅ 膜阻力模型加载成功!")
+    return resistance_model_fp, resistance_model_bw

+ 0 - 0
models/uf-rl/训练/uf_train/rl_model/DQN/__init__.py


+ 186 - 0
models/uf-rl/训练/uf_train/rl_model/DQN/dqn_decider.py

@@ -0,0 +1,186 @@
+"""
+UF 超滤系统 DQN 决策脚本(与当前 DQNTrainer 严格对齐)
+
+功能定位:
+- 加载已训练好的 DQN 模型
+- 构造与训练阶段完全一致的环境
+- 执行单步动作推理(predict)
+- 输出模型建议的工程动作参数(L_s, t_bw_s)
+
+注意:
+- 本脚本【不 step 环境】
+- 不计算 reward
+- 不进行 episode rollout
+"""
+
+from pathlib import Path
+import numpy as np
+
+# ============================================================
+# 1. UF 环境与物理模型
+# ============================================================
+from uf_train.env.uf_env import UFSuperCycleEnv
+from uf_train.env.uf_physics import UFPhysicsModel
+from uf_train.env.uf_resistance_models_load import load_resistance_models
+from uf_train.env.env_params import (
+    UFPhysicsParams,
+    UFRewardParams,
+    UFActionSpec,
+    UFStateBounds,
+)
+
+# ============================================================
+# 2. Stable-Baselines3
+# ============================================================
+from stable_baselines3 import DQN
+
+
+# ============================================================
+# 3. DQN 决策器
+# ============================================================
+class UFDQNDecider:
+    """
+    UF 超滤 DQN 决策器(Inference Only)
+
+    设计原则:
+    1. 与训练环境参数级一致
+    2. 决策侧不推进环境
+    3. 不依赖 Trainer 内部状态
+    """
+
+    def __init__(
+        self,
+        physics,
+        model_path,
+        seed: int = 0,
+    ):
+        """
+        Parameters
+        ----------
+        model_path
+            dqn_model.zip 的路径
+        reset_state_pool :
+            ResetStatePoolLoader.split() 得到的 pool(train / val 均可)
+        seed : int
+            随机种子(推理阶段主要用于 env.reset)
+        """
+
+        self.action_spec = UFActionSpec()
+        reward_params = UFRewardParams()
+        state_bounds = UFStateBounds()
+
+        self.env = UFSuperCycleEnv(
+            physics=physics,
+            reward_params=reward_params,
+            action_spec=self.action_spec,
+            statebounds=state_bounds,
+            real_state_pool=None,
+            RANDOM_SEED=seed,
+        )
+
+        model_path = Path(model_path)
+        if not model_path.exists():
+            raise FileNotFoundError(f"DQN 模型不存在: {model_path}")
+
+        self.model = DQN.load(
+            path=str(model_path),
+            env=self.env,          # ⚠ 必须提供 env
+        )
+
+    # ========================================================
+    # 对外决策接口
+    # ========================================================
+    def decide(self, state: np.ndarray | None = None) -> dict:
+        """
+        单步决策(不 step 环境)
+
+        Parameters
+        ----------
+        state : np.ndarray | None
+            - None:env.reset() 从 reset_state_pool 抽样状态
+            - 非 None:使用外部系统提供的状态
+
+        Returns
+        -------
+        dict
+            {
+                "action_id": int,
+                "L_s": float,
+                "t_bw_s": float,
+            }
+        """
+
+        # ----------------------------------------------------
+        # 4.1 获取观测状态
+        # ----------------------------------------------------
+        if state is None:
+            obs = self.env.reset()
+        else:
+            obs = self.env.get_obs(state) # 获取归一化状态作为策略网络输入
+
+        # ----------------------------------------------------
+        # 4.2 DQN 推理(确定性)
+        # ----------------------------------------------------
+        action, _ = self.model.predict(obs, deterministic=True)
+        action_id = int(action)
+
+
+        # ----------------------------------------------------
+        # 4.3 动作解码(工程语义)
+        # ----------------------------------------------------
+        L_s, t_bw_s = self.env.get_action_values(action_id)
+
+        return {
+            "action_id": action_id,
+            "L_s": L_s,
+            "t_bw_s": t_bw_s,
+        }
+
+
+# ============================================================
+# 5. 示例调用(调试用)
+# ============================================================
+if __name__ == "__main__":
+
+    from uf_train.data_to_rl.data_splitter import ResetStatePoolLoader
+
+    # --------------------------------------------------------
+    # 模型路径(来自 Trainer.save())
+    # --------------------------------------------------------
+    MODEL_PATH = Path(
+        "models/uf-rl/model_result/uf_dqn_tensorboard/xxx/dqn_model.zip"
+    )
+
+    # --------------------------------------------------------
+    # Reset state pool
+    # --------------------------------------------------------
+    RESET_STATE_CSV = Path(
+        "datasets/rl_ready/output/reset_state_pool.csv"
+    )
+
+    loader = ResetStatePoolLoader(
+        csv_path=RESET_STATE_CSV,
+        train_ratio=0.8,
+        shuffle=False,
+        random_state=2025,
+    )
+
+    _, val_pool = loader.split()
+
+    # --------------------------------------------------------
+    # 初始化决策器
+    # --------------------------------------------------------
+    decider = UFDQNDecider(
+        model_path=MODEL_PATH,
+        reset_state_pool=val_pool,
+    )
+
+    # --------------------------------------------------------
+    # 执行一次决策
+    # --------------------------------------------------------
+    decision = decider.decide()
+
+    print("===== DQN 决策结果 =====")
+    print(f"Action ID : {decision['action_id']}")
+    print(f"L_s       : {decision['L_s']} s")
+    print(f"t_bw_s    : {decision['t_bw_s']} s")

+ 94 - 0
models/uf-rl/训练/uf_train/rl_model/DQN/dqn_params.py

@@ -0,0 +1,94 @@
+from dataclasses import dataclass
+
+# ==================== DQN超参数配置类 ====================
+@dataclass
+class DQNParams:
+    """
+    DQN 超参数配置类
+
+    功能:统一管理DQN算法的所有超参数
+
+    超参数说明:
+    - learning_rate: 神经网络学习率,控制梯度下降的步长
+    - buffer_size: 经验回放缓冲区大小,存储历史经验
+    - learning_starts: 开始训练前先收集的经验数量(warm-up)
+    - batch_size: 每次训练采样的batch大小
+    - gamma: 折扣因子,权衡即时奖励和长期奖励
+    - train_freq: 训练频率,每隔多少步训练一次
+    - target_update_interval: 目标网络更新频率
+    - tau: 软更新系数(soft update)
+    - exploration_*: ε-贪心策略的探索率参数
+    """
+    # ========== 神经网络参数 ==========
+    learning_rate: float = 1e-4
+    # 学习率,控制神经网络权重更新的步长
+    # 典型范围:1e-5 ~ 1e-3
+    # 过大:训练不稳定;过小:收敛慢
+
+    # ========== 经验回放参数 ==========
+    buffer_size: int = 100000
+    # 经验回放缓冲区大小(可存储的transition数量)
+    # 作用:打破样本间的时间相关性,提高训练稳定性
+    # 建议:至少存储几个完整episode的经验
+
+    learning_starts: int = 10000
+    # 开始训练前先收集的步数(预填充缓冲区)
+    # 作用:确保缓冲区有足够的多样性样本再开始训练
+    # 建议:设为buffer_size的10%-20%
+
+    batch_size: int = 32
+    # 每次训练从缓冲区采样的样本数量
+    # 典型值:32, 64, 128, 256
+    # 过大:显存占用高,训练慢;过小:梯度估计不准确
+
+    # ========== 强化学习参数 ==========
+    gamma: float = 0.95
+    # 折扣因子(discount factor),γ ∈ [0, 1]
+    # 作用:权衡即时奖励和长期奖励
+    # γ=0:只考虑当前奖励(短视)
+    # γ=1:完全考虑未来奖励(长视)
+    # 通常设为0.9-0.99
+
+    train_freq: int = 4
+    # 训练频率:每收集多少步执行一次训练
+    # 作用:平衡数据收集和网络更新
+    # 典型值:1(每步训练)或4-16(批量训练)
+
+    # ========== 目标网络参数 ==========
+    target_update_interval: int = 1
+    # 目标网络更新间隔(硬更新)
+    # 作用:目标网络每隔多少次训练更新一次
+    # 注:使用软更新(tau)时此参数通常设为1
+
+    tau: float = 0.005
+    # 软更新系数(soft update)
+    # θ_target = τ×θ + (1-τ)×θ_target
+    # τ=1:硬更新(完全复制)
+    # τ<<1:软更新(平滑过渡,更稳定)
+    # 典型值:0.001 - 0.01
+
+    # ========== 探索策略参数(ε-greedy) ==========
+    exploration_initial_eps: float = 1.0
+    # 初始探索率 ε_0
+    # ε=1:完全随机探索
+    # ε=0:完全利用已学知识
+
+    exploration_fraction: float = 0.3
+    # 探索率衰减比例
+    # 表示训练总步数的前30%进行ε衰减
+    # 例:总共10万步,前3万步ε从1.0衰减到0.02
+
+    exploration_final_eps: float = 0.02
+    # 最终探索率 ε_final
+    # 衰减结束后保持此值(保留小概率探索)
+    # 典型值:0.01 - 0.05
+
+    # ========== 日志参数 ==========
+    remark: str = "default"
+    # 实验备注,用于区分不同训练实验
+    # 会自动添加到TensorBoard日志目录名中
+
+
+
+
+

+ 261 - 0
models/uf-rl/训练/uf_train/rl_model/DQN/dqn_statebuilder.py

@@ -0,0 +1,261 @@
+import re
+from pathlib import Path
+from typing import Dict, Any
+import yaml
+import numpy as np
+import pandas as pd
+from oauthlib.uri_validate import segment
+
+# -------------------------------
+# 引入环境状态模板(最终输出)
+# -------------------------------
+from uf_train.env.env_params import UFState
+
+# -------------------------------
+# 引入分析类
+# -------------------------------
+from uf_data_process.load import UFConfigLoader
+from uf_data_process.label import UFEventClassifier, PostBackwashInletMarker
+from uf_data_process.filter import ConstantFlowFilter, EventQualityFilter, InletSegmentFilter
+from uf_data_process.calculate import UFResistanceCalculator, UFResistanceAnalyzer
+from uf_data_process.fit import ShortTermCycleFoulingFitter, LongTermFoulingFitter
+
+class DQNStateBuilder:
+    """
+    在 DQN 决策前构建状态的工具类
+
+    相关数据:
+        * CSV1 = 上一完整化学周期
+        * CSV2 = 新周期初始进水段
+    """
+
+    def __init__(self, config_path: str):
+        """
+        Parameters
+        ----------
+        config_path : str
+            uf_analyze_config.yaml 路径
+        """
+        self.cfg = UFConfigLoader(config_path)
+        self.uf_cfg = self.cfg.uf
+        self.params = self.cfg.params
+
+        self.units = self.uf_cfg["units"]
+        self.area_m2 = self.uf_cfg["area_m2"]
+
+        self.scale_factor = self.params.get("scale_factor", 1e10)
+        self.segment_head_n = self.params.get("segment_head_n", 10)
+        self.segment_tail_n = self.params.get("segment_tail_n", 10)
+
+    # ======================================================================
+    # 对外主接口
+    # ======================================================================
+
+    def build_from_csv_pair(
+        self,
+        prev_cycle_csv: str,
+        init_cycle_csv: str,
+    ) -> UFState:
+        """
+        使用【上一完整化学周期 CSV】+【当前周期初始 CSV】构建 UFState
+        """
+
+        df_prev = pd.read_csv(prev_cycle_csv)
+        df_init = pd.read_csv(init_cycle_csv)
+
+        # 自动识别 UF 单元编号(UF1 / UF2 / ...)
+        unit_id = self._infer_unit_id(df_prev)
+
+        # 分别处理两个 CSV
+        prev_features = self._analyze_previous_cycle_csv(df_prev, unit_id)
+        init_features = self._analyze_init_cycle_csv(df_init, unit_id)
+
+        # 化学清洗去除阻力(上一周期末 - 当前初始)
+        ceb_removal = max(
+            prev_features["R_end"] - init_features["R_start"],
+            0.0
+        )
+
+        # 构建 UFState
+        state = UFState(
+            q_UF=init_features["q_mean"],
+            TMP=init_features["tmp_mean"],
+            temp=init_features["temp_mean"],
+            R=init_features["R_start"],
+            nuK=prev_features["nuK"],
+            slope=prev_features["slope"],
+            power=prev_features["power"],
+            ceb_removal=ceb_removal,
+        )
+
+        return state
+
+    # ======================================================================
+    # 上一完整化学周期分析
+    # ======================================================================
+
+    def _analyze_previous_cycle_csv(
+        self,
+        df: pd.DataFrame,
+        unit_id: str,
+    ) -> Dict[str, float]:
+        """
+        上一完整化学周期分析逻辑
+
+        步骤:
+        1. 事件标注
+        2. 进水段过滤(质量过滤)
+        3. 膜阻力计算
+        4. 提取周期末稳定阻力
+        5. 拟合 nuK
+        6. 拟合长期不可逆污染(slope / power)
+        """
+
+        # -------- 事件标注 --------
+        df = self._label_events(df, unit_id)
+
+
+        # -------- 保留过滤进水段 --------
+        inlet_filter = InletSegmentFilter(
+            control_col=f"C.M.{unit_id}_DB@word_control",
+            stable_value=self.uf_cfg["stable_inlet_code"],
+            min_points=self.params["min_stable_points"],
+        )
+        segments = inlet_filter.extract(df)
+
+        quality_filter = EventQualityFilter(
+            min_points=self.params["min_stable_points"]
+        )
+        segments = quality_filter.filter(segments)
+
+        if len(segments) == 0:
+            raise ValueError("上一周期无有效稳定进水段,无法构建状态")
+
+        # -------- 3️⃣ 膜阻力计算 --------
+        res_calc = UFResistanceCalculator(
+            units=[unit_id],
+            area_m2=self.area_m2,
+            scale_factor=self.scale_factor,
+        )
+        segments = res_calc.calculate_for_segments(
+            segments,
+            temp_col=self.uf_cfg["temp_col"],
+            flow_col=self.uf_cfg["flow_col_template"].format(unit=unit_id),
+        )
+
+
+
+        # -------- 膜阻力统计 --------
+        res_col = f"{unit_id}_R_scaled"
+        ura = UFResistanceAnalyzer(
+            resistance_col=res_col,
+            head_n=self.segment_head_n,
+            tail_n=self.segment_tail_n
+        )
+        segments = ura.analyze_segments(segments)
+        df_all = segments[-1]
+        R_end = df_all["R_scaled_end"].iloc[0]
+
+        # ===== 确保 time 为 datetime =====
+        for i, seg in enumerate(segments):
+            if not pd.api.types.is_datetime64_any_dtype(seg["time"]):
+                seg = seg.copy()
+                seg["time"] = pd.to_datetime(seg["time"], errors="coerce")
+                seg = seg.dropna(subset=["time"])
+                segments[i] = seg
+
+        # -------- 5️⃣ 短期污染拟合(nuK)--------
+        st_fitter = ShortTermCycleFoulingFitter(unit_id)
+        nuK, _ = st_fitter.fit_cycle(segments)
+
+        # -------- 6️⃣ 长期不可逆污染拟合 --------
+        lt_fitter = LongTermFoulingFitter(unit_id)
+        slope, power, _ = lt_fitter.fit_cycle(segments)
+
+        return {
+            "R_end": R_end,
+            "nuK": float(nuK),
+            "slope": float(slope),
+            "power": float(power),
+        }
+
+    # ======================================================================
+    # 当前周期初始进水段分析
+    # ======================================================================
+
+    def _analyze_init_cycle_csv(
+        self,
+        df: pd.DataFrame,
+        unit_id: str,
+    ) -> Dict[str, float]:
+        """
+        当前周期初始进水段分析
+
+        特点:
+        - 不切段
+        - 不过滤
+        - 只计算均值
+        """
+
+        # -------- 1️⃣ 事件标注 --------
+        df = self._label_events(df, unit_id)
+
+        # -------- 2️⃣ 仅保留进水行 --------
+        df = df[df["event_type"] == "inlet"].copy()
+
+        if df.empty:
+            raise ValueError("初始 CSV 中无进水数据")
+
+        # -------- 3️⃣ 膜阻力计算 --------
+        res_calc = UFResistanceCalculator(
+            units=[unit_id],
+            area_m2=self.area_m2,
+            scale_factor=self.scale_factor,
+        )
+        segments = [df]
+        segments = res_calc.calculate_for_segments(
+            segments,
+            temp_col=self.uf_cfg["temp_col"],
+            flow_col=self.uf_cfg["flow_col_template"].format(unit=unit_id),
+        )
+        df = segments[-1]
+
+        flow_col = self.uf_cfg["flow_col_template"].format(unit=unit_id)
+        temp_col = self.uf_cfg["temp_col"]
+        press_col = f"C.M.{unit_id}_DB@press_PV"
+        res_col = f"{unit_id}_R_scaled"
+
+        return {
+            "q_mean": float(df[flow_col].mean()),
+            "tmp_mean": float(df[press_col].mean()),
+            "temp_mean": float(df[temp_col].mean()),
+            "R_start": float(df[res_col].mean()),
+        }
+
+    # ======================================================================
+    # 工具函数
+    # ======================================================================
+
+    def _infer_unit_id(self, df: pd.DataFrame) -> str:
+        """
+        根据列名自动识别 UF 单元编号
+        """
+        for unit in self.units:
+            key = f"C.M.{unit}_FT_JS@out"
+            if key in df.columns:
+                return unit
+        raise ValueError("无法从 CSV 列名识别 UF 单元编号")
+
+    def _label_events(self, df: pd.DataFrame, unit_id: str) -> pd.DataFrame:
+        """
+        为 DataFrame 标注 event_type
+        """
+        clf = UFEventClassifier(
+            unit_name=unit_id,
+            inlet_codes=self.uf_cfg["inlet_codes"],
+            physical_code=self.uf_cfg["physical_bw_code"],
+            chemical_code=self.uf_cfg["chemical_bw_code"],
+        )
+        df = clf.classify(df)
+        df = clf.segment(df)
+        return df

+ 168 - 0
models/uf-rl/训练/uf_train/rl_model/DQN/dqn_trainer.py

@@ -0,0 +1,168 @@
+import os
+import time
+from stable_baselines3 import DQN
+
+
+class DQNTrainer:
+    """
+    DQN训练器封装类
+
+    功能:
+    - 创建训练模型
+    - 训练智能体
+    - 保存与加载模型
+    - 在测试集环境上评估策略
+    """
+
+    def __init__(self, env, params, callback=None,PROJECT_ROOT=None):
+        """
+        初始化训练器
+
+        参数:
+            env: Gym环境实例(向量化环境)
+            params: DQNParams超参数对象
+            callback: 可选训练回调
+        """
+        self.env = env
+        self.params = params
+        self.callback = callback
+        self.PROJECT_ROOT = PROJECT_ROOT
+        self.log_dir = self._create_log_dir()  # 创建TensorBoard日志目录
+        self.model = self._create_model()      # 创建DQN模型
+
+    # ------------------- 私有方法 -------------------
+    def _create_log_dir(self):
+        """
+        创建 TensorBoard 日志目录(固定在 PROJECT_ROOT/model_result/uf_dqn_tensorboard 下)
+        """
+        import os
+        import time
+
+        # 1️⃣ 时间戳,用于区分每次训练
+        timestamp = time.strftime("%Y%m%d-%H%M%S")
+
+        # 2️⃣ 将浮点参数转成整数便于命名
+        lr_int = int(self.params.learning_rate * 1e4)
+        gamma_int = int(self.params.gamma * 100)
+        exp_int = int(self.params.exploration_fraction * 100)
+
+        # 3️⃣ 构建日志目录名称
+        log_name = (
+            f"DQN_lr{lr_int}_buf{self.params.buffer_size}_bs{self.params.batch_size}"
+            f"_gamma{gamma_int}_exp{exp_int}_{self.params.remark}_{timestamp}"
+        )
+
+        # 4️⃣ 固定日志存放位置:PROJECT_ROOT/model_result/uf_dqn_tensorboard
+        # 假设在 run_dqn_train.py 中定义了 PROJECT_ROOT = "models/uf-rl"
+        base_dir = os.path.join(self.PROJECT_ROOT, "model_result", "uf_dqn_tensorboard")
+        os.makedirs(base_dir, exist_ok=True)
+
+        # 5️⃣ 完整日志目录路径
+        log_dir = os.path.join(base_dir, log_name)
+        os.makedirs(log_dir, exist_ok=True)
+
+        return log_dir
+
+    def _create_model(self):
+        """
+        创建Stable-Baselines3 DQN模型
+        """
+        model = DQN(
+            policy="MlpPolicy",
+            env=self.env,
+            learning_rate=self.params.learning_rate,
+            buffer_size=self.params.buffer_size,
+            learning_starts=self.params.learning_starts,
+            batch_size=self.params.batch_size,
+            gamma=self.params.gamma,
+            train_freq=self.params.train_freq,
+            target_update_interval=self.params.target_update_interval,
+            tau=self.params.tau,
+            exploration_initial_eps=self.params.exploration_initial_eps,
+            exploration_fraction=self.params.exploration_fraction,
+            exploration_final_eps=self.params.exploration_final_eps,
+            verbose=1,
+            tensorboard_log=self.log_dir
+        )
+        return model
+
+    # ------------------- 公共方法 -------------------
+    def train(self, total_timesteps: int):
+        """
+        执行训练
+
+        参数:
+            total_timesteps: 总训练步数
+        """
+        if self.callback:
+            self.model.learn(total_timesteps=total_timesteps, callback=self.callback)
+        else:
+            self.model.learn(total_timesteps=total_timesteps)
+
+        print(f"✅ 模型训练完成!")
+        print(f"📊 日志保存在:{self.log_dir}")
+        print(f"💡 使用以下命令查看TensorBoard:")
+        print(f"   tensorboard --logdir={self.log_dir}")
+
+    def save(self, path=None):
+        """
+        保存模型
+
+        参数:
+            path: 可选路径,默认保存到日志目录下 dqn_model.zip
+        """
+        if path is None:
+            path = os.path.join(self.log_dir, "dqn_model.zip")
+        self.model.save(path)
+        print(f"💾 模型已保存到:{path}")
+
+    def load(self, path):
+        """
+        加载模型
+
+        参数:
+            path: 模型文件路径
+        """
+        self.model = DQN.load(path, env=self.env)
+        print(f"📥 模型已从 {path} 加载")
+
+    def evaluate(self, test_env, n_episodes=10, deterministic=True):
+        """
+        在测试环境上评估模型
+
+        参数:
+            test_env: 测试用Gym环境(非向量化)
+            n_episodes: 测试episode数量
+            deterministic: 是否使用确定性策略
+
+        返回:
+            list[dict]: 每个episode的统计信息,包括总奖励、步数、TMP序列等
+        """
+        results = []
+
+        for ep in range(n_episodes):
+            obs = test_env.reset()
+            done = False
+            total_reward = 0
+            steps = 0
+            tmp_after_ceb_list = []
+
+            while not done:
+                action, _ = self.model.predict(obs, deterministic=deterministic)
+                obs, reward, done, info = test_env.step(action)
+
+                total_reward += reward
+                steps += 1
+
+                # 可根据需要记录TMP、回收率等
+                tmp_after_ceb_list.append(info.get("tmp_after_ceb", 0))
+
+            ep_result = {
+                "episode": ep,
+                "total_reward": total_reward,
+                "steps": steps,
+                "tmp_after_ceb_sequence": tmp_after_ceb_list
+            }
+            results.append(ep_result)
+
+        return results

BIN
models/uf-rl/训练/uf_train/rl_model/DQN/model/dqn_model.zip


BIN
models/uf-rl/训练/uf_train/rl_model/DQN/model/loss.png


+ 11 - 0
models/uf-rl/训练/uf_train/rl_model/DQN/online_datasets/UF1_init_cycle.csv

@@ -0,0 +1,11 @@
+time,C.M.UF1_DB@press_PV,C.M.UF1_FT_JS@out,C.M.UF1_DB@word_control,C.M.RO_TT_ZJS@out
+2024-09-28 08:47:00,0.0366965667,258.2537652833,26.0,29.85100995
+2024-09-28 08:48:00,0.0377313667,265.0496630667,26.0,29.8446019
+2024-09-28 08:49:00,0.0458058333,309.5450154333,26.0,29.8467378333
+2024-09-28 08:50:00,0.0513884237,342.4652041017,26.0,29.8405016102
+2024-09-28 08:51:00,0.0512495667,340.76577755,26.0,29.8419317333
+2024-09-28 08:52:00,0.0514033333,340.2851627667,26.0,29.8397956
+2024-09-28 08:53:00,0.0513394333,340.5895517333,26.0,29.8323191333
+2024-09-28 08:54:00,0.0513937333,339.8750400667,26.0,29.8328531667
+2024-09-28 08:55:00,0.0514371356,340.3113914237,26.0,29.8361569322
+2024-09-28 08:56:00,0.0514675167,339.8237709833,26.0,29.8323191333

+ 2511 - 0
models/uf-rl/训练/uf_train/rl_model/DQN/online_datasets/UF1_prev_cycle.csv

@@ -0,0 +1,2511 @@
+time,C.M.UF1_DB@press_PV,C.M.UF1_FT_JS@out,C.M.UF1_DB@word_control,C.M.RO_TT_ZJS@out
+2024-09-26 14:39:00,0.0339027167,277.7282948167,26.0,29.6598306833
+2024-09-26 14:40:00,0.0337968833,277.1739793667,26.0,29.6614328833
+2024-09-26 14:41:00,0.0336879167,275.6007716833,26.0,29.6667731333
+2024-09-26 14:42:00,0.0336815833,275.9532182667,26.0,29.6576946667
+2024-09-26 14:43:00,0.0366570847,295.6646874746,26.0,29.6650894915
+2024-09-26 14:44:00,0.04137775,327.3245774167,26.0,29.6603648333
+2024-09-26 14:45:00,0.04115985,326.0012781333,26.0,29.6587627667
+2024-09-26 14:46:00,0.0412655667,326.84716195,26.0,29.6566266333
+2024-09-26 14:47:00,0.0412047667,325.33482965,26.0,29.6571606833
+2024-09-26 14:48:00,0.0411183333,324.8990706,26.0,29.6560926333
+2024-09-26 14:49:00,0.0411438333,325.2483147833,26.0,29.6598308
+2024-09-26 14:50:00,0.0409836167,323.7359857,26.0,29.6625009667
+2024-09-26 14:51:00,0.0412368333,325.3572580333,26.0,29.6550246
+2024-09-26 14:52:00,0.0412273,324.96635575,26.0,29.66410295
+2024-09-26 14:53:00,0.0413552667,325.1938470333,26.0,29.6592967667
+2024-09-26 14:54:00,0.04136815,325.3188079,26.0,29.6635689
+2024-09-26 14:55:00,0.04119835,324.6427406833,26.0,29.66730715
+2024-09-26 14:56:00,0.0413649333,324.6523552833,26.0,29.66356905
+2024-09-26 14:57:00,0.0414387,324.7580896167,26.0,29.6705113167
+2024-09-26 14:58:00,0.0415475167,326.1262384833,26.0,29.6630349833
+2024-09-26 14:59:00,0.0413842,324.9919915833,26.0,29.6721132833
+2024-09-26 15:00:00,0.0519833167,389.4264657333,26.0,29.6603648833
+2024-09-26 15:01:00,0.0543095167,405.7769921333,26.0,29.6592967667
+2024-09-26 15:02:00,0.04679275,359.62512195,26.0,29.6715793667
+2024-09-26 15:03:00,0.0413393167,322.6914438333,26.0,29.6603647833
+2024-09-26 15:04:00,0.0413681167,323.2745939333,26.0,29.6592967833
+2024-09-26 15:05:00,0.04134895,322.9477731333,26.0,29.6576947167
+2024-09-26 15:06:00,0.0413426,322.4319101667,26.0,29.6608988667
+2024-09-26 15:07:00,0.0414225833,323.0887530667,26.0,29.6683752167
+2024-09-26 15:08:00,0.04149625,323.4283889167,26.0,29.6731815167
+2024-09-26 15:09:00,0.04141625,322.57289325,26.0,29.66623915
+2024-09-26 15:10:00,0.0413393333,322.0506226167,26.0,29.6726474167
+2024-09-26 15:11:00,0.0530054167,392.4927917667,26.0,29.6683752
+2024-09-26 15:12:00,0.0571803333,419.9903853333,26.0,29.6619669167
+2024-09-26 15:13:00,0.0471979831,358.8289236271,26.0,29.6667188814
+2024-09-26 15:14:00,0.0414546,320.8522895333,26.0,29.6662390333
+2024-09-26 15:15:00,0.041461,320.6312053167,26.0,29.6576947167
+2024-09-26 15:16:00,0.041493,320.66004245,26.0,29.6646370667
+2024-09-26 15:17:00,0.0413970167,320.0897146,26.0,29.6678411667
+2024-09-26 15:18:00,0.0414371356,320.7902684068,26.0,29.6667187966
+2024-09-26 15:19:00,0.0491519483,366.7690493966,26.0,29.6727395517
+2024-09-26 15:20:00,0.0570521667,418.9298290167,26.0,29.6694432833
+2024-09-26 15:21:00,0.0523069833,391.7270125333,26.0,29.6635689833
+2024-09-26 15:22:00,0.041429,318.6318538333,26.0,29.6763854833
+2024-09-26 15:23:00,0.0413777667,319.2053844333,26.0,29.6715793833
+2024-09-26 15:24:00,0.041461,319.6251207,26.0,29.6678410667
+2024-09-26 15:25:00,0.0413842,318.71195225,26.0,29.67051125
+2024-09-26 15:26:00,0.0414482333,319.2566493833,26.0,29.6673071
+2024-09-26 15:27:00,0.0414706333,318.8529353833,26.0,29.6763855667
+2024-09-26 15:28:00,0.041531678,318.9492715593,26.0,29.6764941017
+2024-09-26 15:29:00,0.04154745,319.0419780833,26.0,29.6715792833
+2024-09-26 15:30:00,0.0416210833,319.0964479,26.0,29.67531745
+2024-09-26 15:31:00,0.0415795667,319.1252818833,26.0,29.6721132833
+2024-09-26 15:32:00,0.0416948333,319.5930803,26.0,29.6646371167
+2024-09-26 15:33:00,0.041688,318.8580400847,26.0,29.6748648983
+2024-09-26 15:34:00,0.0415283667,317.89490505,26.0,29.6785216
+2024-09-26 15:35:00,0.04158585,317.38545525,26.0,29.67264735
+2024-09-26 15:36:00,0.041682,318.7728318,26.0,29.6721133167
+2024-09-26 15:37:00,0.0417301333,318.971486,26.0,29.6779875333
+2024-09-26 15:38:00,0.0418552,319.2470384,26.0,29.6806576667
+2024-09-26 15:39:00,0.04173015,318.4492167333,26.0,29.6785216
+2024-09-26 15:40:00,0.0419224333,318.8753642833,26.0,29.6801236333
+2024-09-26 15:41:00,0.0417910167,318.59980775,26.0,29.6827938
+2024-09-26 15:42:00,0.0419321333,318.7568110833,26.0,29.6795897
+2024-09-26 15:43:00,0.0418777167,318.5036891333,26.0,29.6758515667
+2024-09-26 15:44:00,0.04183275,318.46844335,26.0,29.6801236333
+2024-09-26 15:45:00,0.0420345333,318.9362401667,26.0,29.6817257333
+2024-09-26 15:46:00,0.0336207333,261.9064431667,29.7,29.6817257833
+2024-09-26 15:47:00,-0.0870234,0.1249599,44.9833333333,29.6817257667
+2024-09-26 15:48:00,-0.06835305,0.8522908833,40.3833333333,29.6827938333
+2024-09-26 15:49:00,0.0384587667,302.5728953667,25.95,29.6753175333
+2024-09-26 15:50:00,0.0410860833,327.0746537,26.0,29.6763855
+2024-09-26 15:51:00,0.0412464,325.5879510833,26.0,29.6811917833
+2024-09-26 15:52:00,0.0413457167,326.8055053667,26.0,29.6758514833
+2024-09-26 15:53:00,0.04130095,325.38289025,26.0,29.6795897667
+2024-09-26 15:54:00,0.0413426,325.7353398167,26.0,29.6779875667
+2024-09-26 15:55:00,0.0411469833,324.4633167167,26.0,29.6769196167
+2024-09-26 15:56:00,0.0415219833,325.5591165833,26.0,29.6854639333
+2024-09-26 15:57:00,0.04142905,325.1489892333,26.0,29.6833278667
+2024-09-26 15:58:00,0.04161485,325.4661961167,26.0,29.6795896333
+2024-09-26 15:59:00,0.0415059,325.0849084333,26.0,29.6908041333
+2024-09-26 16:00:00,0.0415635833,325.2931741333,26.0,29.6908042
+2024-09-26 16:01:00,0.04153155,325.0400506333,26.0,29.6876001
+2024-09-26 16:02:00,0.0417077667,324.71643505,26.0,29.6806576667
+2024-09-26 16:03:00,0.0416884333,325.1938454833,26.0,29.6795896
+2024-09-26 16:04:00,0.0417045667,325.0176212167,26.0,29.68386185
+2024-09-26 16:05:00,0.04174295,325.38929895,26.0,29.6865319333
+2024-09-26 16:06:00,0.0417942833,325.3476434333,26.0,29.6902702
+2024-09-26 16:07:00,0.0415634667,323.34828785,26.0,29.6806577
+2024-09-26 16:08:00,0.0409291,318.8529307667,26.0,29.6854639833
+2024-09-26 16:09:00,0.0399133667,311.8583825833,26.0,29.6876000833
+2024-09-26 16:10:00,0.0390931,305.7801971833,26.0,29.6827938
+2024-09-26 16:11:00,0.0384555333,301.42582225,26.0,29.6892021
+2024-09-26 16:12:00,0.0475841333,358.91381025,26.0,29.69027015
+2024-09-26 16:13:00,0.05041315,377.3021494333,26.0,29.6886681167
+2024-09-26 16:14:00,0.0459532667,350.5703312167,26.0,29.69080415
+2024-09-26 16:15:00,0.0401345833,311.7878871333,26.0,29.6870660167
+2024-09-26 16:16:00,0.0408715333,316.03652445,26.0,29.6881340667
+2024-09-26 16:17:00,0.0413585167,318.5773814333,26.0,29.6924062
+2024-09-26 16:18:00,0.04175585,322.2044193333,26.0,29.6934743167
+2024-09-26 16:19:00,0.0418520333,323.0503050333,26.0,29.6913382667
+2024-09-26 16:20:00,0.0417974333,322.6433833,26.0,29.6950764333
+2024-09-26 16:21:00,0.0418807,322.6882450833,26.0,29.69507635
+2024-09-26 16:22:00,0.0419578,322.0410102333,26.0,29.6892021167
+2024-09-26 16:23:00,0.0558218667,407.1964079667,26.0,29.6934743833
+2024-09-26 16:24:00,0.0577636,418.9746871333,26.0,29.6913382
+2024-09-26 16:25:00,0.04628645,350.4902273833,26.0,29.6929402
+2024-09-26 16:26:00,0.04183605,320.3524517833,26.0,29.6918722833
+2024-09-26 16:27:00,0.0416307833,319.3367503833,26.0,29.6881341333
+2024-09-26 16:28:00,0.04188395,319.5674480833,26.0,29.687066
+2024-09-26 16:29:00,0.0418167,319.9487362167,26.0,29.6929403167
+2024-09-26 16:30:00,0.04194175,320.3396333833,26.0,29.6897361333
+2024-09-26 16:31:00,0.0505382333,372.508814,26.0,29.6961444
+2024-09-26 16:32:00,0.0574593833,417.91092785,26.0,29.68813405
+2024-09-26 16:33:00,0.0506943898,377.1770241695,26.0,29.6938725593
+2024-09-26 16:34:00,0.0418391167,318.3530976,26.0,29.6913383
+2024-09-26 16:35:00,0.0418680167,318.1896845333,26.0,29.6950763167
+2024-09-26 16:36:00,0.0418872,317.8756836,26.0,29.6934742833
+2024-09-26 16:37:00,0.0418647833,317.6097431667,26.0,29.6870659833
+2024-09-26 16:38:00,0.0420040847,318.9916320508,26.0,29.6971309661
+2024-09-26 16:39:00,0.0419994333,318.971486,26.0,29.6982806
+2024-09-26 16:40:00,0.0419961333,318.3562999167,26.0,29.6913380833
+2024-09-26 16:41:00,0.0420185667,317.8981129667,26.0,29.69454235
+2024-09-26 16:42:00,0.0420217833,317.8180104167,26.0,29.6966784667
+2024-09-26 16:43:00,0.0420507167,317.9045176333,26.0,29.6972124167
+2024-09-26 16:44:00,0.04186475,316.61006365,26.0,29.6961444
+2024-09-26 16:45:00,0.0422652333,318.1832738,26.0,29.69400835
+2024-09-26 16:46:00,0.0421916,318.2794022,26.0,29.6918722333
+2024-09-26 16:47:00,0.0421852,318.1800709667,26.0,29.6913381667
+2024-09-26 16:48:00,0.0421409831,318.0304027966,26.0,29.6982172203
+2024-09-26 16:49:00,0.0422748333,318.0358876167,26.0,29.6961443333
+2024-09-26 16:50:00,0.0423293333,318.66388855,26.0,29.6897361333
+2024-09-26 16:51:00,0.04238055,318.5933995667,26.0,29.6902701833
+2024-09-26 16:52:00,0.0423645333,318.5517508,26.0,29.69400845
+2024-09-26 16:53:00,0.0423966333,318.4588292667,26.0,29.6924062667
+2024-09-26 16:54:00,0.0423325167,318.0999715167,26.0,29.6950763167
+2024-09-26 16:55:00,0.0424798833,318.1640538667,26.0,29.6924061833
+2024-09-26 16:56:00,0.04202495,315.3700720333,26.0,29.69400835
+2024-09-26 16:57:00,0.0411309333,309.8237726833,26.0,29.69828055
+2024-09-26 16:58:00,0.0327202,251.0829866667,29.6,29.70148475
+2024-09-26 16:59:00,-0.0878532667,0.1409804,45.0,29.6998826667
+2024-09-26 17:00:00,-0.06536375,24.7741116667,41.1833333333,29.7046887833
+2024-09-26 17:01:00,0.0401698333,316.3857732333,26.0,29.7004166667
+2024-09-26 17:02:00,0.0409900667,319.6379355167,26.0,29.69507645
+2024-09-26 17:03:00,0.04148025,321.8647849,26.0,29.6988145
+2024-09-26 17:04:00,0.0418200167,323.7135546833,26.0,29.7030867667
+2024-09-26 17:05:00,0.0419449333,324.4344791167,26.0,29.6950763333
+2024-09-26 17:06:00,0.04193215,324.0243534167,26.0,29.7052229167
+2024-09-26 17:07:00,0.04215955,324.70682665,26.0,29.6988145667
+2024-09-26 17:08:00,0.0420796333,323.9218234167,26.0,29.7036207833
+2024-09-26 17:09:00,0.0421531667,324.527397,26.0,29.6972125333
+2024-09-26 17:10:00,0.0419769667,322.1691753,26.0,29.69988265
+2024-09-26 17:11:00,0.0415154333,319.45530255,26.0,29.6988145333
+2024-09-26 17:12:00,0.0408074833,314.7612950833,26.0,29.7046889167
+2024-09-26 17:13:00,0.0401569667,309.41685285,26.0,29.7036208333
+2024-09-26 17:14:00,0.03938155,305.1586025833,26.0,29.7052228833
+2024-09-26 17:15:00,0.03891695,301.1246389,26.0,29.7004166667
+2024-09-26 17:16:00,0.03874395,300.2178787667,26.0,29.70949505
+2024-09-26 17:17:00,0.0387277833,299.4585088333,26.0,29.706825
+2024-09-26 17:18:00,0.0387086333,299.4969564333,26.0,29.7025527167
+2024-09-26 17:19:00,0.0386861833,299.2246109667,26.0,29.7036207833
+2024-09-26 17:20:00,0.0386701333,299.2854854333,26.0,29.7126993333
+2024-09-26 17:21:00,0.03876635,299.5642396,26.0,29.71109715
+2024-09-26 17:22:00,0.0387856167,299.3880168333,26.0,29.7116312167
+2024-09-26 17:23:00,0.03868615,299.3655889333,26.0,29.7132333167
+2024-09-26 17:24:00,0.0469529,349.1733388167,26.0,29.7062908
+2024-09-26 17:25:00,0.05118545,377.0201821333,26.0,29.71002915
+2024-09-26 17:26:00,0.0480357,358.2505599833,26.0,29.7068249667
+2024-09-26 17:27:00,0.0409003667,311.9512984333,26.0,29.7089610833
+2024-09-26 17:28:00,0.0416243667,316.7510431667,26.0,29.7084271
+2024-09-26 17:29:00,0.0421532,320.6247962667,26.0,29.7036208833
+2024-09-26 17:30:00,0.0424317333,320.9804520833,26.0,29.70468885
+2024-09-26 17:31:00,0.0425792167,321.84235495,26.0,29.7009507
+2024-09-26 17:32:00,0.04248945,321.3841692333,26.0,29.7052229333
+2024-09-26 17:33:00,0.0426112833,322.0442139167,26.0,29.7046888833
+2024-09-26 17:34:00,0.0428933833,322.2813192667,26.0,29.7089610667
+2024-09-26 17:35:00,0.0575651,411.2592127333,26.0,29.71056325
+2024-09-26 17:36:00,0.0585358,417.53604735,26.0,29.708427
+2024-09-26 17:37:00,0.0448156,337.0906800167,26.0,29.7073589833
+2024-09-26 17:38:00,0.04242225,319.0291581,26.0,29.7062909333
+2024-09-26 17:39:00,0.0425055667,319.214998,26.0,29.706825
+2024-09-26 17:40:00,0.0425151167,318.9939154167,26.0,29.7078930167
+2024-09-26 17:41:00,0.0423932833,318.9041987833,26.0,29.7094950833
+2024-09-26 17:42:00,0.0425664,319.1060563,26.0,29.70629085
+2024-09-26 17:43:00,0.0531912833,382.7523253833,26.0,29.7089609833
+2024-09-26 17:44:00,0.0581417,415.3668685,26.0,29.70095075
+2024-09-26 17:45:00,0.0491509333,360.7657764167,26.0,29.7110971833
+2024-09-26 17:46:00,0.04247035,317.5648833833,26.0,29.7126992667
+2024-09-26 17:47:00,0.0423966833,316.9913501167,26.0,29.7110970833
+2024-09-26 17:48:00,0.0424382333,317.1803908667,26.0,29.7110971833
+2024-09-26 17:49:00,0.0423932833,316.7414290167,26.0,29.7110972167
+2024-09-26 17:50:00,0.0426210167,317.4527403667,26.0,29.7100292167
+2024-09-26 17:51:00,0.0427395833,317.9077255,26.0,29.7126993
+2024-09-26 17:52:00,0.0426146167,317.1355314333,26.0,29.7148353667
+2024-09-26 17:53:00,0.042419,316.3985921,26.0,29.7068249667
+2024-09-26 17:54:00,0.0425985167,317.1932093,26.0,29.7100291667
+2024-09-26 17:55:00,0.0426498333,317.0169818667,26.0,29.7148354
+2024-09-26 17:56:00,0.0427460333,316.9400847833,26.0,29.7153694167
+2024-09-26 17:57:00,0.04280045,318.0294785167,26.0,29.7073591167
+2024-09-26 17:58:00,0.04281335,317.0426142,26.0,29.7137672833
+2024-09-26 17:59:00,0.0429542,317.6193516667,26.0,29.7153693833
+2024-09-26 18:00:00,0.04288055,316.97212515,26.0,29.7126991833
+2024-09-26 18:01:00,0.0429189,317.0682478667,26.0,29.7137673167
+2024-09-26 18:02:00,0.04293815,317.3373910333,26.0,29.7094951667
+2024-09-26 18:03:00,0.0427363,315.9564260167,26.0,29.7078930667
+2024-09-26 18:04:00,0.04295735,316.60365235,26.0,29.7094951
+2024-09-26 18:05:00,0.0430854833,317.3213699333,26.0,29.7068250833
+2024-09-26 18:06:00,0.0430310833,316.8824069667,26.0,29.7062909167
+2024-09-26 18:07:00,0.0430182,316.4690820833,26.0,29.7105631
+2024-09-26 18:08:00,0.04228445,312.0698538667,26.0,29.7132332667
+2024-09-26 18:09:00,0.0413328833,306.4786947333,26.0,29.7137673333
+2024-09-26 18:10:00,0.0225536833,199.6155089667,32.7666666667,29.7041550333
+2024-09-26 18:11:00,-0.1177156,0.1153476,45.0,29.71163125
+2024-09-26 18:12:00,-0.0648125333,46.2960589,39.1666666667,29.7143013
+2024-09-26 18:13:00,0.0412431,317.9942306,26.0,29.7201755167
+2024-09-26 18:14:00,0.0423293667,323.1720605667,26.0,29.7153693667
+2024-09-26 18:15:00,0.0425183667,323.98590525,26.0,29.7148353667
+2024-09-26 18:16:00,0.0426915667,323.1880842167,26.0,29.7143013167
+2024-09-26 18:17:00,0.0425344667,322.9445709167,26.0,29.71002915
+2024-09-26 18:18:00,0.0425697167,322.9477721333,26.0,29.72177755
+2024-09-26 18:19:00,0.0426434333,323.5469424167,26.0,29.7148353667
+2024-09-26 18:20:00,0.0428292,324.1781487667,26.0,29.7180395
+2024-09-26 18:21:00,0.0426916333,323.6751031167,26.0,29.7148353
+2024-09-26 18:22:00,0.04275555,322.8965041667,26.0,29.7217776167
+2024-09-26 18:23:00,0.0427075167,322.3582178833,26.0,29.7207095333
+2024-09-26 18:24:00,0.04297015,322.6978530167,26.0,29.7143013
+2024-09-26 18:25:00,0.0427845,322.8324268333,26.0,29.71002925
+2024-09-26 18:26:00,0.0426144833,321.00929275,26.0,29.7180394667
+2024-09-26 18:27:00,0.0420217167,316.6549192833,26.0,29.7212435
+2024-09-26 18:28:00,0.0409163,309.7180369333,26.0,29.7217775667
+2024-09-26 18:29:00,0.0399358167,303.54694215,26.0,29.7191074833
+2024-09-26 18:30:00,0.0393655333,298.7920578333,26.0,29.7169714667
+2024-09-26 18:31:00,0.0393429333,298.4812577333,26.0,29.7132333
+2024-09-26 18:32:00,0.0392052667,297.850049,26.0,29.7255156167
+2024-09-26 18:33:00,0.0392821167,297.8949088667,26.0,29.7223115833
+2024-09-26 18:34:00,0.0392212667,297.4110866167,26.0,29.72818565
+2024-09-26 18:35:00,0.0393431,298.2217245,26.0,29.7185735167
+2024-09-26 18:36:00,0.0462159167,338.6638868833,26.0,29.71964145
+2024-09-26 18:37:00,0.05177815,374.8702320833,26.0,29.7126992333
+2024-09-26 18:38:00,0.0499327167,365.33482875,26.0,29.7169713667
+2024-09-26 18:39:00,0.0410348833,307.6898432,26.0,29.7153693833
+2024-09-26 18:40:00,0.04176545,312.0794611667,26.0,29.7153693833
+2024-09-26 18:41:00,0.0422331667,315.32841745,26.0,29.7132332833
+2024-09-26 18:42:00,0.0428164833,317.9077224,26.0,29.7153694167
+2024-09-26 18:43:00,0.0429894333,319.40724195,26.0,29.7233795667
+2024-09-26 18:44:00,0.0431944,320.1217534833,26.0,29.7169714333
+2024-09-26 18:45:00,0.0432490333,319.4264634,26.0,29.7164374167
+2024-09-26 18:46:00,0.0445177667,326.1326498333,26.0,29.71483535
+2024-09-26 18:47:00,0.0591926667,416.3601401167,26.0,29.7126991
+2024-09-26 18:48:00,0.0593271833,415.96283225,26.0,29.7148352833
+2024-09-26 18:49:00,0.0445338167,329.9423247667,26.0,29.7164374167
+2024-09-26 18:50:00,0.0430054,318.1416239333,26.0,29.7153694167
+2024-09-26 18:51:00,0.04301505,317.97500965,26.0,29.7137673167
+2024-09-26 18:52:00,0.0431785167,317.79237355,26.0,29.7100291333
+2024-09-26 18:53:00,0.0431207667,317.5360483167,26.0,29.7217775333
+2024-09-26 18:54:00,0.0431847,318.2729893667,26.0,29.7201755
+2024-09-26 18:55:00,0.0562448833,395.32842095,26.0,29.7084271
+2024-09-26 18:56:00,0.0592247333,414.98878675,26.0,29.7260495333
+2024-09-26 18:57:00,0.04775395,347.6065346667,26.0,29.7207095333
+2024-09-26 18:58:00,0.0430150333,315.7032988833,26.0,29.7191075
+2024-09-26 18:59:00,0.042967,315.9596282,26.0,29.7132332667
+2024-09-26 19:00:00,0.0428836833,314.8894561667,26.0,29.7217775333
+2024-09-26 19:01:00,0.0430952167,315.98205625,26.0,29.7228456
+2024-09-26 19:02:00,0.0430759,315.44376675,26.0,29.7217775
+2024-09-26 19:03:00,0.0431080167,314.7292509833,26.0,29.7212435167
+2024-09-26 19:04:00,0.0432489167,316.1518706667,26.0,29.7223115333
+2024-09-26 19:05:00,0.0434220167,316.5684106167,26.0,29.72284555
+2024-09-26 19:06:00,0.0432970333,315.6167896333,26.0,29.72551555
+2024-09-26 19:07:00,0.0431751833,314.9759704,26.0,29.7276517
+2024-09-26 19:08:00,0.0432808667,314.68439685,26.0,29.7271176333
+2024-09-26 19:09:00,0.0434828333,316.2704221833,26.0,29.72070955
+2024-09-26 19:10:00,0.04329055,315.2739501833,26.0,29.71964155
+2024-09-26 19:11:00,0.04339635,316.1807102833,26.0,29.7196415
+2024-09-26 19:12:00,0.0433098333,315.6135849167,26.0,29.7281857667
+2024-09-26 19:13:00,0.04344765,315.9051563667,26.0,29.72604955
+2024-09-26 19:14:00,0.0434445167,315.8442793667,26.0,29.7271176
+2024-09-26 19:15:00,0.0435406333,316.1775044,26.0,29.71643745
+2024-09-26 19:16:00,0.0436784,316.4306323,26.0,29.7233796167
+2024-09-26 19:17:00,0.043547,315.9692355667,26.0,29.7153693833
+2024-09-26 19:18:00,0.0435694167,315.2835611167,26.0,29.7169714
+2024-09-26 19:19:00,0.0431272333,312.3806493833,26.0,29.7260496167
+2024-09-26 19:20:00,0.0422171667,306.9432902,26.0,29.7271175667
+2024-09-26 19:21:00,0.04119835,300.0512651,26.0,29.7271176
+2024-09-26 19:22:00,0.01734385,183.04069215,33.4833333333,29.7260495833
+2024-09-26 19:23:00,-0.1085229667,0.1473886,45.0,29.7276516833
+2024-09-26 19:24:00,-0.0373406,89.2886904667,36.2666666667,29.7233795167
+2024-09-26 19:25:00,0.0411149167,311.5091318667,26.0,29.7249815667
+2024-09-26 19:26:00,0.04176875,314.1525142167,26.0,29.72711755
+2024-09-26 19:27:00,0.0425921833,318.4396052833,26.0,29.7292537
+2024-09-26 19:28:00,0.0429446667,320.29798035,26.0,29.72070945
+2024-09-26 19:29:00,0.0431430833,321.6052502167,26.0,29.72070955
+2024-09-26 19:30:00,0.0432649667,321.2784318333,26.0,29.7175054333
+2024-09-26 19:31:00,0.0432618667,321.2079408,26.0,29.7217774667
+2024-09-26 19:32:00,0.04336115,321.31367705,26.0,29.7265835167
+2024-09-26 19:33:00,0.0423549167,315.1489895,26.0,29.7276517
+2024-09-26 19:34:00,0.03438325,263.8288978333,26.0,29.7276516667
+2024-09-26 19:35:00,0.0331433167,254.9247032,26.0,29.7287195333
+2024-09-26 19:36:00,0.0325857,251.1694965667,26.0,29.7276517333
+2024-09-26 19:37:00,0.0319641667,245.8250562167,26.0,29.7271174833
+2024-09-26 19:38:00,0.03124325,241.5636043,26.0,29.7377978
+2024-09-26 19:39:00,0.03058635,237.0842688167,26.0,29.7319236833
+2024-09-26 19:40:00,0.02981085,232.3870574667,26.0,29.7367298
+2024-09-26 19:41:00,0.0297628,231.7238105833,26.0,29.7313897333
+2024-09-26 19:42:00,0.0299326,231.7494425667,26.0,29.73245785
+2024-09-26 19:43:00,0.0298653667,231.00608975,26.0,29.72925365
+2024-09-26 19:44:00,0.0299101333,232.12752625,26.0,29.7383317167
+2024-09-26 19:45:00,0.0299935,232.0025665667,26.0,29.7431378333
+2024-09-26 19:46:00,0.0302402333,233.9186167667,26.0,29.7308556333
+2024-09-26 19:47:00,0.0316308667,244.4152501667,26.0,29.7335257667
+2024-09-26 19:48:00,0.03527705,266.3985898167,26.0,29.7329918
+2024-09-26 19:49:00,0.0407689333,303.4219838667,26.0,29.73779785
+2024-09-26 19:50:00,0.0408938333,304.4408854,26.0,29.73779785
+2024-09-26 19:51:00,0.03980125,295.0881134833,26.0,29.7260495833
+2024-09-26 19:52:00,0.0428067667,314.8317874833,26.0,29.7335257
+2024-09-26 19:53:00,0.0433963667,318.3018259667,26.0,29.72765165
+2024-09-26 19:54:00,0.0437072,320.0384451667,26.0,29.72818565
+2024-09-26 19:55:00,0.0437969,320.0128170833,26.0,29.7233795667
+2024-09-26 19:56:00,0.0438256667,319.91989765,26.0,29.72391355
+2024-09-26 19:57:00,0.0438161,319.79173295,26.0,29.7233795833
+2024-09-26 19:58:00,0.04631855,332.5152192333,26.0,29.7185734667
+2024-09-26 19:59:00,0.06023075,416.3505275167,26.0,29.7185735167
+2024-09-26 20:00:00,0.0593367,413.7359822667,26.0,29.7212435667
+2024-09-26 20:01:00,0.0438609833,318.7760328833,26.0,29.7175054333
+2024-09-26 20:02:00,0.0435726333,317.0778588167,26.0,29.7329916
+2024-09-26 20:03:00,0.0436847167,317.1291237333,26.0,29.7260496
+2024-09-26 20:04:00,0.0437392,317.09387915,26.0,29.7244475333
+2024-09-26 20:05:00,0.0437648333,318.1480352167,26.0,29.7292537
+2024-09-26 20:06:00,0.0441877,319.0900380667,26.0,29.72444755
+2024-09-26 20:07:00,0.0590836667,408.6254429167,26.0,29.7297876833
+2024-09-26 20:08:00,0.0598815333,413.4059615333,26.0,29.72498155
+2024-09-26 20:09:00,0.04623525,333.0727311333,26.0,29.7233796
+2024-09-26 20:10:00,0.0437423833,315.7161166167,26.0,29.7287195667
+2024-09-26 20:11:00,0.0438001167,315.7641784833,26.0,29.7244476333
+2024-09-26 20:12:00,0.0433642167,313.2713925333,26.0,29.7207094833
+2024-09-26 20:13:00,0.0438096667,315.8282573333,26.0,29.7196415
+2024-09-26 20:14:00,0.0438160833,315.6648498667,26.0,29.7239136333
+2024-09-26 20:15:00,0.0439250167,316.53315755,26.0,29.72444755
+2024-09-26 20:16:00,0.0436815,314.1076604667,26.0,29.7255156167
+2024-09-26 20:17:00,0.0439057167,315.86030325,26.0,29.7201754667
+2024-09-26 20:18:00,0.0439345333,315.9852609333,26.0,29.7196414833
+2024-09-26 20:19:00,0.043816,315.3668661167,26.0,29.7228455333
+2024-09-26 20:20:00,0.0439634333,315.56552065,26.0,29.7217775333
+2024-09-26 20:21:00,0.0440691833,315.8763218667,26.0,29.72604955
+2024-09-26 20:22:00,0.0440692167,315.7385450167,26.0,29.72391355
+2024-09-26 20:23:00,0.044082,315.4758112,26.0,29.7223115
+2024-09-26 20:24:00,0.0439858333,314.9727641333,26.0,29.7217775
+2024-09-26 20:25:00,0.0442295,315.6328097833,26.0,29.72284545
+2024-09-26 20:26:00,0.0441814,315.9596246667,26.0,29.7217775333
+2024-09-26 20:27:00,0.044159,315.7161147833,26.0,29.7207095
+2024-09-26 20:28:00,0.0443447667,316.3889789833,26.0,29.7265836167
+2024-09-26 20:29:00,0.0441974,315.40852145,26.0,29.7292537167
+2024-09-26 20:30:00,0.0437425,311.90964295,26.0,29.7233796333
+2024-09-26 20:31:00,0.0428772167,307.21883695,26.0,29.7228455167
+2024-09-26 20:32:00,0.0417206167,299.97116,26.0,29.7233796333
+2024-09-26 20:33:00,0.0409130667,295.1906405833,26.0,29.7249815667
+2024-09-26 20:34:00,-0.0121473729,129.1013849153,37.2203389831,29.7291721695
+2024-09-26 20:35:00,-0.1222077833,0.1409804,45.0,29.7265835833
+2024-09-26 20:36:00,-0.0120538667,148.9362384333,33.55,29.7292535667
+2024-09-26 20:37:00,0.0421691833,312.9061168333,26.0,29.7313896167
+2024-09-26 20:38:00,0.0430824,317.7475138667,26.0,29.7233796
+2024-09-26 20:39:00,0.0436911333,321.0189048,26.0,29.7255156
+2024-09-26 20:40:00,0.0435693667,320.8234551167,26.0,29.72444755
+2024-09-26 20:41:00,0.0437488333,320.0897147667,26.0,29.7249816
+2024-09-26 20:42:00,0.04370405,319.3784038333,26.0,29.72017545
+2024-09-26 20:43:00,0.0439730667,320.2883658333,26.0,29.72391365
+2024-09-26 20:44:00,0.0438544333,320.4165316,26.0,29.7287197333
+2024-09-26 20:45:00,0.0439666167,320.57673515,26.0,29.7271175833
+2024-09-26 20:46:00,0.0439314,319.9903881833,26.0,29.7313897167
+2024-09-26 20:47:00,0.0439955,320.0961245,26.0,29.7265836333
+2024-09-26 20:48:00,0.0441172667,319.9903866333,26.0,29.7223115833
+2024-09-26 20:49:00,0.04359505,317.1996150167,26.0,29.7281855833
+2024-09-26 20:50:00,0.0429381,312.8868983333,26.0,29.7228455167
+2024-09-26 20:51:00,0.0419385,306.5203440833,26.0,29.7223114833
+2024-09-26 20:52:00,0.0410701667,300.9676361167,26.0,29.7292536167
+2024-09-26 20:53:00,0.0402756,296.2095443,26.0,29.73085575
+2024-09-26 20:54:00,0.0402178167,295.7097091,26.0,29.73299155
+2024-09-26 20:55:00,0.0402562667,295.2002593,26.0,29.7233796333
+2024-09-26 20:56:00,0.04029795,295.4149300833,26.0,29.72711755
+2024-09-26 20:57:00,0.0403428167,295.6007684333,26.0,29.7271174667
+2024-09-26 20:58:00,0.0403555833,295.5591129167,26.0,29.7292536167
+2024-09-26 20:59:00,0.0405671333,296.3088734667,26.0,29.7260496333
+2024-09-26 21:00:00,0.0442069667,316.145464,26.0,29.7239136167
+2024-09-26 21:01:00,0.0531271667,372.6754232833,26.0,29.7276516667
+2024-09-26 21:02:00,0.05412355,377.7731511333,26.0,29.7340596333
+2024-09-26 21:03:00,0.0430118333,311.6501115,26.0,29.7276516667
+2024-09-26 21:04:00,0.0433001333,312.0153772833,26.0,29.7297876167
+2024-09-26 21:05:00,0.0441364667,316.8087151667,26.0,29.7239135833
+2024-09-26 21:06:00,0.0440115167,317.0394087667,26.0,29.7212435167
+2024-09-26 21:07:00,0.0442583,317.6097442167,26.0,29.7244474833
+2024-09-26 21:08:00,0.0442937667,317.3566172167,26.0,29.7244475
+2024-09-26 21:09:00,0.04417185,317.3630258,26.0,29.7271175833
+2024-09-26 21:10:00,0.0491734667,342.8260155333,26.0,29.7223116333
+2024-09-26 21:11:00,0.0609452167,413.4956765333,26.0,29.71964155
+2024-09-26 21:12:00,0.05928565,405.40211835,26.0,29.7244475
+2024-09-26 21:13:00,0.04414935,315.2226871667,26.0,29.7281857667
+2024-09-26 21:14:00,0.0442198833,315.3027876833,26.0,29.7340597167
+2024-09-26 21:15:00,0.0441717167,314.7292536,26.0,29.7297876667
+2024-09-26 21:16:00,0.0440532333,314.18135425,26.0,29.7228456333
+2024-09-26 21:17:00,0.04400185,314.2710678167,26.0,29.7239134667
+2024-09-26 21:18:00,0.0460749833,323.8449222333,26.0,29.7191074333
+2024-09-26 21:19:00,0.0605128167,410.5094488667,26.0,29.7169714167
+2024-09-26 21:20:00,0.0602019833,409.6699773833,26.0,29.72231155
+2024-09-26 21:21:00,0.0442390833,316.5171417167,26.0,29.7233796167
+2024-09-26 21:22:00,0.0439923,312.4511364,26.0,29.7372637833
+2024-09-26 21:23:00,0.04421025,313.55014595,26.0,29.7239136
+2024-09-26 21:24:00,0.0441782,313.8096782667,26.0,29.7217775167
+2024-09-26 21:25:00,0.0441398,312.7843634833,26.0,29.7191074333
+2024-09-26 21:26:00,0.0442615667,313.6558799833,26.0,29.7260496167
+2024-09-26 21:27:00,0.0442806833,312.8420376167,26.0,29.7249815333
+2024-09-26 21:28:00,0.0443192333,312.7170793167,26.0,29.7335258
+2024-09-26 21:29:00,0.0444953833,313.4636379167,26.0,29.7276517167
+2024-09-26 21:30:00,0.0443351833,313.4251888333,26.0,29.7287196333
+2024-09-26 21:31:00,0.0445819333,313.8417216167,26.0,29.7292537
+2024-09-26 21:32:00,0.0443255167,312.99583285,26.0,29.72498165
+2024-09-26 21:33:00,0.0443896833,312.931752,26.0,29.7255156667
+2024-09-26 21:34:00,0.0446661864,314.042114339,26.0,29.7172248983
+2024-09-26 21:35:00,0.0445850833,313.4476165833,26.0,29.72337955
+2024-09-26 21:36:00,0.0446362667,313.7840430167,26.0,29.7244475333
+2024-09-26 21:37:00,0.0445883333,313.7840460667,26.0,29.7271175167
+2024-09-26 21:38:00,0.0446810833,313.9923142667,26.0,29.7233795833
+2024-09-26 21:39:00,0.0448356949,313.5957104237,26.0,29.7237416102
+2024-09-26 21:40:00,0.0447676,313.05350965,26.0,29.7228455
+2024-09-26 21:41:00,0.0447676333,313.1688599667,26.0,29.7292536
+2024-09-26 21:42:00,0.0448062,312.84524355,26.0,29.7276515833
+2024-09-26 21:43:00,0.04477105,313.7199651667,26.0,29.7297876833
+2024-09-26 21:44:00,0.0448830167,313.4636318,26.0,29.7255155833
+2024-09-26 21:45:00,0.0441365667,309.4424863833,26.0,29.7191075167
+2024-09-26 21:46:00,-0.0322045167,85.5430939,40.1666666667,29.7233795667
+2024-09-26 21:47:00,-0.1095516333,0.1217558,45.0166666667,29.7217774167
+2024-09-26 21:48:00,0.0170169667,204.0595956,29.9833333333,29.7233795333
+2024-09-26 21:49:00,0.0441717833,319.4841417,26.0,29.72818555
+2024-09-26 21:50:00,0.0443896333,319.8462032833,26.0,29.7175053833
+2024-09-26 21:51:00,0.0443000333,319.18936475,26.0,29.72177755
+2024-09-26 21:52:00,0.04431605,318.9458537833,26.0,29.7239136333
+2024-09-26 21:53:00,0.04441535,319.4873445,26.0,29.7244476667
+2024-09-26 21:54:00,0.0444602,318.4972783667,26.0,29.7260494667
+2024-09-26 21:55:00,0.0445146167,318.5677689,26.0,29.7244475833
+2024-09-26 21:56:00,0.0446266333,319.3848140833,26.0,29.7217775833
+2024-09-26 21:57:00,0.0446907667,318.7984640167,26.0,29.7207095167
+2024-09-26 21:58:00,0.0444762,317.9910318833,26.0,29.7276516167
+2024-09-26 21:59:00,0.0439955833,314.4985545667,26.0,29.72124345
+2024-09-26 22:00:00,0.0429861,308.1095795333,26.0,29.7239136
+2024-09-26 22:01:00,0.0422397667,303.0983657333,26.0,29.7287197
+2024-09-26 22:02:00,0.0413105,297.4911876,26.0,29.7217775667
+2024-09-26 22:03:00,0.0408938167,294.9150912667,26.0,29.7244476167
+2024-09-26 22:04:00,0.0409578667,294.09804815,26.0,29.7287195833
+2024-09-26 22:05:00,0.04082975,294.0628028833,26.0,29.7271176167
+2024-09-26 22:06:00,0.0409611833,294.0531899333,26.0,29.7287195833
+2024-09-26 22:07:00,0.0408553333,294.5273959167,26.0,29.7281854667
+2024-09-26 22:08:00,0.0411054333,294.8926644333,26.0,29.7260495667
+2024-09-26 22:09:00,0.0410298136,294.0257096271,26.0,29.7221125085
+2024-09-26 22:10:00,0.04095785,293.70714905,26.0,29.7281856
+2024-09-26 22:11:00,0.0411951167,294.2710711667,26.0,29.7175054167
+2024-09-26 22:12:00,0.0433226667,305.4117303333,26.0,29.7212434667
+2024-09-26 22:13:00,0.0540146833,371.2015356167,26.0,29.7207095167
+2024-09-26 22:14:00,0.0543864167,373.8769613,26.0,29.7255156
+2024-09-26 22:15:00,0.0445978833,315.7353423833,26.0,29.7265837167
+2024-09-26 22:16:00,0.04344125,307.3822458667,26.0,29.72871975
+2024-09-26 22:17:00,0.0441589333,311.5860301,26.0,29.7239136333
+2024-09-26 22:18:00,0.0446652333,314.3319467667,26.0,29.7292536333
+2024-09-26 22:19:00,0.04497615,316.36654795,26.0,29.7276516167
+2024-09-26 22:20:00,0.0448830833,316.1006128667,26.0,29.7255155667
+2024-09-26 22:21:00,0.0448542333,314.8029474667,26.0,29.7233795333
+2024-09-26 22:22:00,0.0507627,346.4979140833,26.0,29.7185735167
+2024-09-26 22:23:00,0.0618806167,411.6244801667,26.0,29.7212435833
+2024-09-26 22:24:00,0.05774755,389.6763844167,26.0,29.7228455667
+2024-09-26 22:25:00,0.04488625,313.95386605,26.0,29.7292536833
+2024-09-26 22:26:00,0.0448830833,313.8032722167,26.0,29.7249815
+2024-09-26 22:27:00,0.0448862833,313.6270458167,26.0,29.7201754667
+2024-09-26 22:28:00,0.0450144333,314.6683762167,26.0,29.7233796167
+2024-09-26 22:29:00,0.0451458667,313.8224950833,26.0,29.7201755667
+2024-09-26 22:30:00,0.0491990667,335.6135891667,26.0,29.71750535
+2024-09-26 22:31:00,0.0610638,406.9016315667,26.0,29.7260495
+2024-09-26 22:32:00,0.0594938,399.50336625,26.0,29.7217775667
+2024-09-26 22:33:00,0.04506255,312.5921210333,26.0,29.72284555
+2024-09-26 22:34:00,0.0448291695,312.1424560508,26.0,29.7226555085
+2024-09-26 22:35:00,0.0450913667,312.4415233,26.0,29.7180395
+2024-09-26 22:36:00,0.04492155,312.2108272333,26.0,29.7116312667
+2024-09-26 22:37:00,0.0449375667,311.646904,26.0,29.7239136
+2024-09-26 22:38:00,0.0451202333,312.3870529333,26.0,29.7228455333
+2024-09-26 22:39:00,0.0451331167,312.2204412667,26.0,29.7223115833
+2024-09-26 22:40:00,0.0450658,312.1948116833,26.0,29.7191074833
+2024-09-26 22:41:00,0.0450945833,312.0890742833,26.0,29.7207095333
+2024-09-26 22:42:00,0.0452548,312.1467474667,26.0,29.7233796333
+2024-09-26 22:43:00,0.0452450833,312.30374405,26.0,29.7249816167
+2024-09-26 22:44:00,0.0451972333,312.3486086,26.0,29.7271176333
+2024-09-26 22:45:00,0.0453862167,312.54405735,26.0,29.7212434833
+2024-09-26 22:46:00,0.0451811333,312.0089686833,26.0,29.7228454833
+2024-09-26 22:47:00,0.0452388167,311.8007029833,26.0,29.7260496
+2024-09-26 22:48:00,0.0453541,312.45754505,26.0,29.7260495667
+2024-09-26 22:49:00,0.04503075,310.7689823667,26.0,29.72711765
+2024-09-26 22:50:00,0.04528035,311.9160481833,26.0,29.7255155833
+2024-09-26 22:51:00,0.0455335167,312.7715496,26.0,29.7265835667
+2024-09-26 22:52:00,0.0454597667,312.1948065167,26.0,29.7313897
+2024-09-26 22:53:00,0.0454438167,311.9833364667,26.0,29.72444755
+2024-09-26 22:54:00,0.0449696167,308.6895221833,26.0,29.7121652667
+2024-09-26 22:55:00,0.0439795,302.4639546333,26.0,29.7207095333
+2024-09-26 22:56:00,0.0427652,296.05895225,26.0,29.7196414667
+2024-09-26 22:57:00,0.0418197833,290.6824752667,26.5,29.7265836
+2024-09-26 22:58:00,-0.0564915,32.7202818167,42.8,29.7239135167
+2024-09-26 22:59:00,-0.1008217288,0.1270778644,45.2033898305,29.7264569492
+2024-09-26 23:00:00,0.0347068167,248.13841755,26.6166666667,29.7244476167
+2024-09-26 23:01:00,0.0431400333,307.6610061833,26.0,29.7159033
+2024-09-26 23:02:00,0.0438449667,311.0285166333,26.0,29.7212435667
+2024-09-26 23:03:00,0.04438,313.89939475,26.0,29.7223115167
+2024-09-26 23:04:00,0.0448510167,316.24479255,26.0,29.7249815667
+2024-09-26 23:05:00,0.0450817167,317.5680902333,26.0,29.7223115833
+2024-09-26 23:06:00,0.0451681017,317.5546739661,26.0,29.7313443898
+2024-09-26 23:07:00,0.045242,317.355546037,26.0,29.7287195741
+2024-09-26 23:08:00,0.0451017241,317.2491180517,26.0,29.7208015345
+2024-09-26 23:09:00,0.0450850333,316.2800382833,26.0,29.7212435
+2024-09-26 23:10:00,0.0445338,312.77475395,26.0,29.72978765
+2024-09-26 23:11:00,0.0441269,309.9487343833,26.0,29.71750545
+2024-09-26 23:12:00,0.0401505333,287.1996174833,26.0,29.7201755333
+2024-09-26 23:13:00,0.0328998167,240.8907402333,26.0,29.7265836333
+2024-09-26 23:14:00,0.0321467167,235.70970655,26.0,29.7303215833
+2024-09-26 23:15:00,0.0315443833,232.1948119667,26.0,29.7303216167
+2024-09-26 23:16:00,0.0314673833,231.4226232333,26.0,29.7335258333
+2024-09-26 23:17:00,0.03145465,231.1246427167,26.0,29.7356617333
+2024-09-26 23:18:00,0.03156685,231.3232978,26.0,29.7308557667
+2024-09-26 23:19:00,0.0315090833,231.5059308667,26.0,29.7377978333
+2024-09-26 23:20:00,0.0315667,231.2912559,26.0,29.7292537167
+2024-09-26 23:21:00,0.0316430526,231.5148676842,26.0,29.7357460526
+2024-09-26 23:22:00,0.0315122833,230.84909015,26.0,29.7377976667
+2024-09-26 23:23:00,0.0315891833,231.4194219,26.0,29.7388658
+2024-09-26 23:24:00,0.0321692,233.5341233,26.0,29.7303216
+2024-09-26 23:25:00,0.04040365,286.2640125167,26.0,29.73352575
+2024-09-26 23:26:00,0.0414034833,291.1726993333,26.0,29.7276516
+2024-09-26 23:27:00,0.0366741833,263.3771232667,26.0,29.7303217333
+2024-09-26 23:28:00,0.0362897,260.7785969667,26.0,29.7303216333
+2024-09-26 23:29:00,0.03720265,265.8122379167,26.0,29.7249815667
+2024-09-26 23:30:00,0.0375808,267.6481899333,26.0,29.7292536333
+2024-09-26 23:31:00,0.0374655,267.3117616,26.0,29.7319237333
+2024-09-26 23:32:00,0.0375936333,267.4943945167,26.0,29.7329917333
+2024-09-26 23:33:00,0.0390547167,275.0977254667,26.0,29.7297876833
+2024-09-26 23:34:00,0.0546075,364.0179401,26.0,29.7239136
+2024-09-26 23:35:00,0.06297345,412.69144875,26.0,29.7249815833
+2024-09-26 23:36:00,0.0571065333,381.9096450167,26.0,29.7239136167
+2024-09-26 23:37:00,0.0455654833,314.3671896,26.0,29.7239136
+2024-09-26 23:38:00,0.0453733167,313.56616585,26.0,29.72444755
+2024-09-26 23:39:00,0.0395578333,278.8016653,26.0,29.7201755
+2024-09-26 23:40:00,0.0361325833,258.09996585,26.0,29.7265836333
+2024-09-26 23:41:00,0.03620965,258.4492171833,26.0,29.7287197
+2024-09-26 23:42:00,0.04074335,284.5818654333,26.0,29.7260496167
+2024-09-26 23:43:00,0.0474433,326.1134226333,26.0,29.7233796167
+2024-09-26 23:44:00,0.045303,314.9343161333,26.0,29.7255155
+2024-09-26 23:45:00,0.0359660833,256.07497855,26.0,29.7217776167
+2024-09-26 23:46:00,0.0361166167,256.1358521333,26.0,29.7276516333
+2024-09-26 23:47:00,0.0361070333,256.1550768,26.0,29.7244476
+2024-09-26 23:48:00,0.0360749333,256.3889762667,26.0,29.7249815833
+2024-09-26 23:49:00,0.0361006,255.9019537,26.0,29.7276515833
+2024-09-26 23:50:00,0.0361095932,256.1271664407,26.0,29.7280860678
+2024-09-26 23:51:00,0.03609095,256.6933656667,26.0,29.7244475667
+2024-09-26 23:52:00,0.0361934333,256.2063424,26.0,29.7260495667
+2024-09-26 23:53:00,0.0362929333,256.61646895,26.0,29.7335257833
+2024-09-26 23:54:00,0.03625115,256.07497675,26.0,29.72978775
+2024-09-26 23:55:00,0.03624795,256.2608136167,26.0,29.7319237167
+2024-09-26 23:56:00,0.0363345,256.0429359,26.0,29.7265836167
+2024-09-26 23:57:00,0.03635055,256.5491801167,26.0,29.72498155
+2024-09-26 23:58:00,0.0362895833,256.4562624333,26.0,29.7271176
+2024-09-26 23:59:00,0.0363602667,256.5716085167,26.0,29.7361958833
+2024-09-27 00:00:00,0.0363730667,256.65491735,26.0,29.7335257333
+2024-09-27 00:01:00,0.03631515,256.10381405,26.0,29.7345938
+2024-09-27 00:02:00,0.0363409333,255.9147705833,26.0,29.7281856167
+2024-09-27 00:03:00,0.03635365,256.1582821833,26.0,29.72604965
+2024-09-27 00:04:00,0.0363376833,256.10701525,26.0,29.7313896833
+2024-09-27 00:05:00,0.0363538333,256.7254112167,26.0,29.7340598667
+2024-09-27 00:06:00,0.0355271167,250.5511064667,26.0,29.72391355
+2024-09-27 00:07:00,0.0346364667,245.0464606,26.0,29.7255154667
+2024-09-27 00:08:00,0.0337585167,239.3623832167,26.0,29.7265836667
+2024-09-27 00:09:00,0.0281736,206.8920196,28.7333333333,29.7292536833
+2024-09-27 00:10:00,-0.0864531333,0.1217558,44.9666666667,29.7329917167
+2024-09-27 00:11:00,-0.0750529333,16.7862862167,41.35,29.7308556833
+2024-09-27 00:12:00,0.0428325167,303.1239979333,26.0,29.7239135667
+2024-09-27 00:13:00,0.043643,308.4492106,26.0,29.7217775833
+2024-09-27 00:14:00,0.0442391167,311.4194178833,26.0,29.7292536
+2024-09-27 00:15:00,0.0447293,313.6462678167,26.0,29.7303215833
+2024-09-27 00:16:00,0.04513625,315.2771545,26.0,29.7164373833
+2024-09-27 00:17:00,0.0454342,316.7286146667,26.0,29.72070955
+2024-09-27 00:18:00,0.0454917167,317.4815784667,26.0,29.7260496667
+2024-09-27 00:19:00,0.0456649167,317.2220427,26.0,29.7228456
+2024-09-27 00:20:00,0.0455175,317.00416455,26.0,29.7329918
+2024-09-27 00:21:00,0.0452516833,314.4088480167,26.0,29.7212435667
+2024-09-27 00:22:00,0.0447997,311.4963114333,26.0,29.73192385
+2024-09-27 00:23:00,0.0443255667,309.0900378333,26.0,29.7265836333
+2024-09-27 00:24:00,0.0436014667,304.7068274667,26.0,29.7244475667
+2024-09-27 00:25:00,0.0429572667,300.0416515167,26.0,29.7228455667
+2024-09-27 00:26:00,0.0424447,296.5235468333,26.0,29.72765175
+2024-09-27 00:27:00,0.0418359333,292.93495875,26.0,29.7180394
+2024-09-27 00:28:00,0.0419226,292.5728959167,26.0,29.7292537
+2024-09-27 00:29:00,0.0419064833,291.6821524333,26.0,29.7303216833
+2024-09-27 00:30:00,0.0419672833,292.8003864833,26.0,29.7271176167
+2024-09-27 00:31:00,0.0420121833,292.4863814333,26.0,29.7228456
+2024-09-27 00:32:00,0.0419834667,292.2364599333,26.0,29.7292537
+2024-09-27 00:33:00,0.0419064167,292.27170525,26.0,29.7255156333
+2024-09-27 00:34:00,0.0419480833,292.4991988667,26.0,29.72551565
+2024-09-27 00:35:00,0.0420698667,292.4030762333,26.0,29.72925365
+2024-09-27 00:36:00,0.0425312833,293.8417171,26.0,29.7233795167
+2024-09-27 00:37:00,0.0549567667,365.7065077667,26.0,29.7228455333
+2024-09-27 00:38:00,0.0559851667,371.68535645,26.0,29.71910745
+2024-09-27 00:39:00,0.0482505833,329.21820025,26.0,29.7287196333
+2024-09-27 00:40:00,0.0448542667,307.8148020333,26.0,29.7255157
+2024-09-27 00:41:00,0.0457770333,312.9798147,26.0,29.72284555
+2024-09-27 00:42:00,0.0459692667,313.6110254667,26.0,29.7308556333
+2024-09-27 00:43:00,0.0460526,314.0115358667,26.0,29.7207095833
+2024-09-27 00:44:00,0.04608785,313.7744350167,26.0,29.7319238
+2024-09-27 00:45:00,0.0461551167,314.5786601333,26.0,29.7287196333
+2024-09-27 00:46:00,0.05685035,371.6885606333,26.0,29.7281858167
+2024-09-27 00:47:00,0.0635244333,410.1762254667,26.0,29.7249816167
+2024-09-27 00:48:00,0.0542774,360.403711,26.0,29.7249815833
+2024-09-27 00:49:00,0.0458219167,311.37455595,26.0,29.7244476667
+2024-09-27 00:50:00,0.0457770167,310.8939416333,26.0,29.7265836
+2024-09-27 00:51:00,0.0460845333,312.90291565,26.0,29.7287197333
+2024-09-27 00:52:00,0.0460879333,312.0346018833,26.0,29.72658365
+2024-09-27 00:53:00,0.0460685833,312.4959942333,26.0,29.72498155
+2024-09-27 00:54:00,0.05491825,359.3944229333,26.0,29.7212435333
+2024-09-27 00:55:00,0.0632168333,407.3502069833,26.0,29.7292537
+2024-09-27 00:56:00,0.0560429833,371.0958012,26.0,29.72871975
+2024-09-27 00:57:00,0.0458348,309.9487369667,26.0,29.7201755667
+2024-09-27 00:58:00,0.0458796333,309.9871845167,26.0,29.7191075333
+2024-09-27 00:59:00,0.0460269,310.53187855,26.0,29.7324577333
+2024-09-27 01:00:00,0.0460141833,310.310797,26.0,29.7255156333
+2024-09-27 01:01:00,0.0460366,310.6279987167,26.0,29.7244475667
+2024-09-27 01:02:00,0.0461134667,310.1698149,26.0,29.74100185
+2024-09-27 01:03:00,0.04599815,310.5254657167,26.0,29.7239135833
+2024-09-27 01:04:00,0.0460013167,309.9006743,26.0,29.7249815167
+2024-09-27 01:05:00,0.0461135333,310.2114668,26.0,29.7292536333
+2024-09-27 01:06:00,0.0461038833,310.1570006,26.0,29.73138975
+2024-09-27 01:07:00,0.0461550833,310.40692285,26.0,29.7239136333
+2024-09-27 01:08:00,0.0463794167,310.5863488833,26.0,29.7255156333
+2024-09-27 01:09:00,0.0463890667,310.6792652167,26.0,29.72871965
+2024-09-27 01:10:00,0.0463921667,310.8747197167,26.0,29.7404678167
+2024-09-27 01:11:00,0.0464113833,310.5863463,26.0,29.7377978167
+2024-09-27 01:12:00,0.0463441333,310.4581836667,26.0,29.73566185
+2024-09-27 01:13:00,0.0464274167,310.3652632,26.0,29.7313896833
+2024-09-27 01:14:00,0.0463057,309.7244479667,26.0,29.7308556833
+2024-09-27 01:15:00,0.0464371167,310.38128495,26.0,29.7239135333
+2024-09-27 01:16:00,0.0464466333,309.8430001333,26.0,29.7271176667
+2024-09-27 01:17:00,0.0464917,310.2659392,26.0,29.72925365
+2024-09-27 01:18:00,0.0466133833,309.8782417833,26.0,29.7319237
+2024-09-27 01:19:00,0.0465909333,310.0576735,26.0,29.7319236667
+2024-09-27 01:20:00,0.0464243667,308.9138091,26.0,29.7324577
+2024-09-27 01:21:00,0.0299039,212.4575456833,32.1666666667,29.7372638333
+2024-09-27 01:22:00,-0.1044666,0.1441845,45.0,29.7367298
+2024-09-27 01:23:00,-0.0469721167,68.59980845,38.2666666667,29.7308557333
+2024-09-27 01:24:00,0.0456776167,315.7481561,26.0,29.7239136333
+2024-09-27 01:25:00,0.0459309167,316.7542459,26.0,29.7361958833
+2024-09-27 01:26:00,0.0461134833,316.7125914667,26.0,29.73459385
+2024-09-27 01:27:00,0.0461904333,317.0490232833,26.0,29.7351277333
+2024-09-27 01:28:00,0.04627365,316.25119665,26.0,29.7335258
+2024-09-27 01:29:00,0.0461871,315.5591139833,26.0,29.7324577
+2024-09-27 01:30:00,0.0463281,316.0621566167,26.0,29.7287196333
+2024-09-27 01:31:00,0.04633785,315.8635055,26.0,29.7329916167
+2024-09-27 01:32:00,0.0463089667,315.7481546667,26.0,29.7329917333
+2024-09-27 01:33:00,0.0463217667,316.0140960333,26.0,29.7329918833
+2024-09-27 01:34:00,0.0455527333,310.8490802667,26.0,29.7345937333
+2024-09-27 01:35:00,0.0448748136,306.1372122542,26.0,29.7297152542
+2024-09-27 01:36:00,0.0439025333,300.34284305,26.0,29.72925365
+2024-09-27 01:37:00,0.0431143167,295.5527057667,26.0,29.7351277833
+2024-09-27 01:38:00,0.0424863333,291.5571901,26.0,29.73566175
+2024-09-27 01:39:00,0.04255055,291.73021105,26.0,29.74367185
+2024-09-27 01:40:00,0.04252485,291.51874195,26.0,29.7329917667
+2024-09-27 01:41:00,0.0426017167,291.8679891833,26.0,29.7324577333
+2024-09-27 01:42:00,0.0424863167,290.7177141,26.0,29.7361958667
+2024-09-27 01:43:00,0.0425632333,291.0669623667,26.0,29.7393998333
+2024-09-27 01:44:00,0.0426049333,292.0281968,26.0,29.7329916833
+2024-09-27 01:45:00,0.0427780667,291.2816383667,26.0,29.7399338833
+2024-09-27 01:46:00,0.0426434333,291.1726965833,26.0,29.7345937333
+2024-09-27 01:47:00,0.0427395167,291.1951270833,26.0,29.7415357
+2024-09-27 01:48:00,0.0427204,291.4963147333,26.0,29.7372638167
+2024-09-27 01:49:00,0.0548126167,358.2153167,26.0,29.7329918
+2024-09-27 01:50:00,0.0566677333,368.9682803833,26.0,29.73512785
+2024-09-27 01:51:00,0.04993595,333.2745884833,26.0,29.7367298
+2024-09-27 01:52:00,0.0448029833,302.4511377333,26.0,29.7383318
+2024-09-27 01:53:00,0.0455591667,306.8023072167,26.0,29.7351279
+2024-09-27 01:54:00,0.0461135333,310.2114678333,26.0,29.7345939
+2024-09-27 01:55:00,0.0466485833,312.31336175,26.0,29.7313897333
+2024-09-27 01:56:00,0.0468024,313.2810036167,26.0,29.7297877
+2024-09-27 01:57:00,0.0469049333,313.4412069833,26.0,29.7329918167
+2024-09-27 01:58:00,0.06042935,384.0788223,26.0,29.73939995
+2024-09-27 01:59:00,0.06398595,406.3505267667,26.0,29.7356618
+2024-09-27 02:00:00,0.052307,344.1973708667,26.0,29.7420699
+2024-09-27 02:01:00,0.0465941,310.9868549167,26.0,29.7490119667
+2024-09-27 02:02:00,0.04675115,311.7270075833,26.0,29.7340596833
+2024-09-27 02:03:00,0.0467672,310.8971486167,26.0,29.7388658333
+2024-09-27 02:04:00,0.0466998833,310.6728596167,26.0,29.7399338833
+2024-09-27 02:05:00,0.0468472,311.49951785,26.0,29.7361958167
+2024-09-27 02:06:00,0.0583402833,372.5857069833,26.0,29.7393998
+2024-09-27 02:07:00,0.0641267,405.5495004,26.0,29.73833195
+2024-09-27 02:08:00,0.0542198667,354.5081719333,26.0,29.73459375
+2024-09-27 02:09:00,0.0465941667,308.6863223333,26.0,29.73779785
+2024-09-27 02:10:00,0.0466711,308.8785667667,26.0,29.73619585
+2024-09-27 02:11:00,0.0466902833,308.69914075,26.0,29.7361957667
+2024-09-27 02:12:00,0.0467446333,308.5325228667,26.0,29.7351277333
+2024-09-27 02:13:00,0.0467062833,308.75681135,26.0,29.73619575
+2024-09-27 02:14:00,0.0468505333,309.1797539333,26.0,29.73352575
+2024-09-27 02:15:00,0.0467960333,308.6062167833,26.0,29.7399338333
+2024-09-27 02:16:00,0.0468536,308.5837872833,26.0,29.7351278333
+2024-09-27 02:17:00,0.0470329167,309.1925677167,26.0,29.73672995
+2024-09-27 02:18:00,0.0469786333,309.0003171333,26.0,29.7319237
+2024-09-27 02:19:00,0.0469496333,308.8657508833,26.0,29.7426039
+2024-09-27 02:20:00,0.047017,309.3207314167,26.0,29.73192365
+2024-09-27 02:21:00,0.0469465167,308.2121127,26.0,29.7442059
+2024-09-27 02:22:00,0.0471612333,309.2918963667,26.0,29.73833185
+2024-09-27 02:23:00,0.0472511333,309.6123081167,26.0,29.7319237667
+2024-09-27 02:24:00,0.0473214667,309.5546303333,26.0,29.7388659167
+2024-09-27 02:25:00,0.04712605,308.5517494333,26.0,29.7383319
+2024-09-27 02:26:00,0.04732805,309.2694685,26.0,29.7410019167
+2024-09-27 02:27:00,0.0473439333,309.6892016667,26.0,29.7442059167
+2024-09-27 02:28:00,0.047376,308.7824445,26.0,29.73993395
+2024-09-27 02:29:00,0.0474016667,309.3399534,26.0,29.74100195
+2024-09-27 02:30:00,0.0474272667,308.6959343667,26.0,29.7431379167
+2024-09-27 02:31:00,0.0473086333,308.02948095,26.0,29.7388658
+2024-09-27 02:32:00,0.04620315,302.7651388667,26.0,29.7335257167
+2024-09-27 02:33:00,0.0047548833,157.34379845,35.2166666667,29.7431379
+2024-09-27 02:34:00,-0.12441525,0.1473886,45.0,29.7361958833
+2024-09-27 02:35:00,-0.0405639,94.98558095,36.6833333333,29.7377978667
+2024-09-27 02:36:00,0.0462896167,315.0112146667,26.0,29.7372637333
+2024-09-27 02:37:00,0.0466326,315.9532184,26.0,29.7377977667
+2024-09-27 02:38:00,0.04639215,312.9349576333,26.0,29.7404679333
+2024-09-27 02:39:00,0.0468985333,315.23870485,26.0,29.7399338833
+2024-09-27 02:40:00,0.0468216,315.27394905,26.0,29.7447399667
+2024-09-27 02:41:00,0.0469785833,315.7673800833,26.0,29.7313898667
+2024-09-27 02:42:00,0.0469337333,315.0144189833,26.0,29.74046795
+2024-09-27 02:43:00,0.0469048,314.7933335,26.0,29.74046795
+2024-09-27 02:44:00,0.0470715167,315.4918274167,26.0,29.7431379
+2024-09-27 02:45:00,0.0470362333,315.00480835,26.0,29.7335257333
+2024-09-27 02:46:00,0.0472029833,315.1842376,26.0,29.7399339333
+2024-09-27 02:47:00,0.0473087167,315.13617745,26.0,29.745808
+2024-09-27 02:48:00,0.0471932333,315.1457870167,26.0,29.743138
+2024-09-27 02:49:00,0.04660695,310.8458816833,26.0,29.7431379667
+2024-09-27 02:50:00,0.0456648833,305.3316269333,26.0,29.740468
+2024-09-27 02:51:00,0.0445178,298.1992976833,26.0,29.73833185
+2024-09-27 02:52:00,0.0434860667,292.6946497167,26.0,29.7426039667
+2024-09-27 02:53:00,0.0432296,290.97724785,26.0,29.749546
+2024-09-27 02:54:00,0.0432040667,290.7721854667,26.0,29.7436719167
+2024-09-27 02:55:00,0.0432970667,290.4549791,26.0,29.7329916667
+2024-09-27 02:56:00,0.0433194167,290.52867915,26.0,29.7399339333
+2024-09-27 02:57:00,0.04322965,290.5607143333,26.0,29.7420699167
+2024-09-27 02:58:00,0.04344125,290.6055721833,26.0,29.746876
+2024-09-27 02:59:00,0.0435021667,290.71771565,26.0,29.7367299
+2024-09-27 03:00:00,0.0435214667,290.5927553,26.0,29.744206
+2024-09-27 03:01:00,0.0540019333,347.1066941667,26.0,29.73993385
+2024-09-27 03:02:00,0.0576418333,368.2633756333,26.0,29.74473995
+2024-09-27 03:03:00,0.0527202333,343.3803277833,26.0,29.7426038667
+2024-09-27 03:04:00,0.04592115,303.2105067167,26.0,29.7415358667
+2024-09-27 03:05:00,0.0466999167,307.5744929167,26.0,29.7393998833
+2024-09-27 03:06:00,0.0472317833,310.9836588333,26.0,29.7345938
+2024-09-27 03:07:00,0.0475649833,312.1563579333,26.0,29.7420698
+2024-09-27 03:08:00,0.04734395,311.2495992167,26.0,29.738866
+2024-09-27 03:09:00,0.0474431833,310.86510615,26.0,29.7404678667
+2024-09-27 03:10:00,0.0638448833,396.2640202833,26.0,29.7388658667
+2024-09-27 03:11:00,0.065524,406.7061848667,26.0,29.73779795
+2024-09-27 03:12:00,0.0504935333,329.34956745,26.0,29.7442059167
+2024-09-27 03:13:00,0.0473983333,309.9711643167,26.0,29.74260395
+2024-09-27 03:14:00,0.0424447167,283.4828565333,26.0,29.741536
+2024-09-27 03:15:00,0.0377953833,255.2899694167,26.0,29.74313795
+2024-09-27 03:16:00,0.03785305,255.4533759667,26.0,29.745808
+2024-09-27 03:17:00,0.0378563667,255.5366854333,26.0,29.7468760333
+2024-09-27 03:18:00,0.0472829333,308.4972789,26.0,29.751148
+2024-09-27 03:19:00,0.0498941833,323.61743145,26.0,29.73833175
+2024-09-27 03:20:00,0.0424894833,284.3319457167,26.0,29.7404678833
+2024-09-27 03:21:00,0.0375679833,253.3546925667,26.0,29.7436719167
+2024-09-27 03:22:00,0.0375423167,252.7555274333,26.0,29.74741
+2024-09-27 03:23:00,0.0375904,253.3803253333,26.0,29.743672
+2024-09-27 03:24:00,0.0376640833,253.1336101667,26.0,29.74367195
+2024-09-27 03:25:00,0.0375615667,253.05030385,26.0,29.747944
+2024-09-27 03:26:00,0.0375744333,252.8612624833,26.0,29.7420699667
+2024-09-27 03:27:00,0.0376705,253.28099845,26.0,29.7506140833
+2024-09-27 03:28:00,0.0377152333,253.8545308333,26.0,29.7356619
+2024-09-27 03:29:00,0.0387117333,259.1605227167,26.0,29.74206995
+2024-09-27 03:30:00,0.03922455,262.1916027,26.0,29.745808
+2024-09-27 03:31:00,0.03910915,261.3553326833,26.0,29.7420699667
+2024-09-27 03:32:00,0.0390067,260.8907381,26.0,29.7458079667
+2024-09-27 03:33:00,0.0389554333,260.5575125667,26.0,29.7399339167
+2024-09-27 03:34:00,0.0389555167,260.7016974667,26.0,29.7436719
+2024-09-27 03:35:00,0.0441877833,288.4235809167,26.0,29.749012
+2024-09-27 03:36:00,0.0479494833,310.3844887833,26.0,29.74207
+2024-09-27 03:37:00,0.0478885167,309.2566500667,26.0,29.745274
+2024-09-27 03:38:00,0.0479270167,309.31753065,26.0,29.73886595
+2024-09-27 03:39:00,0.0478468667,308.3627033167,26.0,29.7377979667
+2024-09-27 03:40:00,0.0470171667,304.4793320167,26.0,29.7377979667
+2024-09-27 03:41:00,0.0459371833,297.4303115667,26.0,29.7393998167
+2024-09-27 03:42:00,0.0448575667,291.4162125833,26.0,29.7420699167
+2024-09-27 03:43:00,0.0440210833,285.9916690833,26.0,29.746342
+2024-09-27 03:44:00,0.04395075,285.8506890833,26.0,29.751682
+2024-09-27 03:45:00,-0.0110573667,121.90003245,37.4,29.7372639
+2024-09-27 03:46:00,-0.1126690167,0.0897148,45.0,29.74634195
+2024-09-27 03:47:00,-0.00449845,152.3934623,32.5,29.74794405
+2024-09-27 03:48:00,0.0453445667,305.1297660167,26.0,29.745274
+2024-09-27 03:49:00,0.04602705,307.9750076167,26.0,29.73993395
+2024-09-27 03:50:00,0.0467735,310.778598,26.0,29.7356617667
+2024-09-27 03:51:00,0.0472958667,313.89939125,26.0,29.7410018833
+2024-09-27 03:52:00,0.0475073167,314.2422342667,26.0,29.74741
+2024-09-27 03:53:00,0.0475232667,314.5434197667,26.0,29.7404678167
+2024-09-27 03:54:00,0.0474978,313.4091639333,26.0,29.7415358333
+2024-09-27 03:55:00,0.04764825,313.8128855167,26.0,29.745808
+2024-09-27 03:56:00,0.04776025,314.5338052667,26.0,29.7415359167
+2024-09-27 03:57:00,0.0475169167,313.4380020667,26.0,29.7474099667
+2024-09-27 03:58:00,0.046828,310.2979806333,26.0,29.7410019333
+2024-09-27 03:59:00,0.0464787167,306.3280996167,26.0,29.745274
+2024-09-27 04:00:00,0.0457673667,302.0378071667,26.0,29.7415358833
+2024-09-27 04:01:00,0.0450176333,298.250559,26.0,29.7356617
+2024-09-27 04:02:00,0.0442775167,293.6654930667,26.0,29.7410019167
+2024-09-27 04:03:00,0.0438288333,291.2431877167,26.0,29.7420699167
+2024-09-27 04:04:00,0.0439089667,291.2688204167,26.0,29.744206
+2024-09-27 04:05:00,0.0440627833,291.3072699667,26.0,29.746342
+2024-09-27 04:06:00,0.0439410667,290.2723482,26.0,29.7420699167
+2024-09-27 04:07:00,0.0439602,290.781798,26.0,29.7484778833
+2024-09-27 04:08:00,0.0440884833,291.3937781833,26.0,29.7458078667
+2024-09-27 04:09:00,0.0440755667,290.5799404833,26.0,29.745274
+2024-09-27 04:10:00,0.0440724167,290.4133291833,26.0,29.746876
+2024-09-27 04:11:00,0.04411735,290.2018576667,26.0,29.7431379667
+2024-09-27 04:12:00,0.0441428333,290.5190619667,26.0,29.742604
+2024-09-27 04:13:00,0.0538096333,341.0990077333,26.0,29.74473995
+2024-09-27 04:14:00,0.0583533,367.2701100333,26.0,29.7420699667
+2024-09-27 04:15:00,0.0548991167,349.8269781,26.0,29.7393998667
+2024-09-27 04:16:00,0.0465332833,302.8452408333,26.0,29.744206
+2024-09-27 04:17:00,0.0474208167,308.31784315,26.0,29.74420595
+2024-09-27 04:18:00,0.0477187667,309.5994865833,26.0,29.748478
+2024-09-27 04:19:00,0.0481897667,311.9384817667,26.0,29.741002
+2024-09-27 04:20:00,0.0481514167,311.5187382667,26.0,29.74420595
+2024-09-27 04:21:00,0.05002905,318.7215601,26.0,29.7426039167
+2024-09-27 04:22:00,0.0663730167,406.49151255,26.0,29.7335257333
+2024-09-27 04:23:00,0.06611665,406.5652054,26.0,29.74046785
+2024-09-27 04:24:00,0.0483308167,312.1948093,26.0,29.73993375
+2024-09-27 04:25:00,0.0479527167,309.6475487333,26.0,29.74046775
+2024-09-27 04:26:00,0.0480585,310.0897165,26.0,29.7442059667
+2024-09-27 04:27:00,0.0481802333,309.8494062833,26.0,29.7383319
+2024-09-27 04:28:00,0.0481705167,309.8429980667,26.0,29.7356617667
+2024-09-27 04:29:00,0.0481257667,309.8782428167,26.0,29.7404679167
+2024-09-27 04:30:00,0.0637616,390.9163731333,26.0,29.7351278
+2024-09-27 04:31:00,0.0658217667,403.1784707333,26.0,29.74046795
+2024-09-27 04:32:00,0.0539700333,342.47036085,26.0,29.7399338
+2024-09-27 04:33:00,0.04790135,307.0970830667,26.0,29.74100185
+2024-09-27 04:34:00,0.04801355,308.13521475,26.0,29.7410018
+2024-09-27 04:35:00,0.0480168333,307.65780135,26.0,29.7452739167
+2024-09-27 04:36:00,0.0480809,307.5841043833,26.0,29.7431379667
+2024-09-27 04:37:00,0.0481994167,308.53892895,26.0,29.7367299
+2024-09-27 04:38:00,0.0481867167,308.0134581667,26.0,29.73779795
+2024-09-27 04:39:00,0.0481096333,307.3373918333,26.0,29.7410019
+2024-09-27 04:40:00,0.04822835,308.1832768167,26.0,29.7404679333
+2024-09-27 04:41:00,0.0484781333,308.32745855,26.0,29.7404679
+2024-09-27 04:42:00,0.0483500167,308.138418,26.0,29.7447399667
+2024-09-27 04:43:00,0.0483596667,307.98461755,26.0,29.7458079
+2024-09-27 04:44:00,0.0483949333,307.7987823,26.0,29.7452739667
+2024-09-27 04:45:00,0.0484460167,308.01345475,26.0,29.74153585
+2024-09-27 04:46:00,0.0485197167,308.035888,26.0,29.74153595
+2024-09-27 04:47:00,0.04826035,306.46267115,26.0,29.7404678833
+2024-09-27 04:48:00,0.0485934833,307.7891738167,26.0,29.7340596833
+2024-09-27 04:49:00,0.04870885,308.3082355167,26.0,29.7393998667
+2024-09-27 04:50:00,0.0485742,307.3405925167,26.0,29.7436718833
+2024-09-27 04:51:00,0.0486447167,307.4687555667,26.0,29.74473995
+2024-09-27 04:52:00,0.04870565,307.72829135,26.0,29.74100185
+2024-09-27 04:53:00,0.0486672,307.6385752333,26.0,29.7468759167
+2024-09-27 04:54:00,0.04888515,308.5197056,26.0,29.7410019167
+2024-09-27 04:55:00,0.04891075,307.8820957167,26.0,29.74206995
+2024-09-27 04:56:00,0.04824745,304.6747818333,26.0,29.746342
+2024-09-27 04:57:00,-0.0346524333,80.61198425,40.2,29.74260395
+2024-09-27 04:58:00,-0.1127138,0.1249599,45.0333333333,29.74260395
+2024-09-27 04:59:00,0.0219545167,209.7596945167,29.6333333333,29.739934
+2024-09-27 05:00:00,0.0481770333,315.1297638,26.0,29.7436719667
+2024-09-27 05:01:00,0.0481995333,314.8958663333,26.0,29.74313795
+2024-09-27 05:02:00,0.0483275833,314.7196425333,26.0,29.74100185
+2024-09-27 05:03:00,0.0483819667,314.8958649,26.0,29.7329918833
+2024-09-27 05:04:00,0.0484141167,314.370396,26.0,29.743672
+2024-09-27 05:05:00,0.0483949333,313.5661688,26.0,29.7393999667
+2024-09-27 05:06:00,0.0485646833,314.7260488,26.0,29.7436719667
+2024-09-27 05:07:00,0.04865115,314.4312743833,26.0,29.7410018833
+2024-09-27 05:08:00,0.0485485333,314.107659,26.0,29.743672
+2024-09-27 05:09:00,0.0484973167,313.5052862333,26.0,29.74420595
+2024-09-27 05:10:00,0.0475007833,308.0006398333,26.0,29.7452739667
+2024-09-27 05:11:00,0.0466774333,303.04709575,26.0,29.74473985
+2024-09-27 05:12:00,0.0458507,297.6257609,26.0,29.743672
+2024-09-27 05:13:00,0.0449439333,292.4030777167,26.0,29.743138
+2024-09-27 05:14:00,0.0445210833,289.8942628,26.0,29.7404678
+2024-09-27 05:15:00,0.0446073667,290.0128170333,26.0,29.74473995
+2024-09-27 05:16:00,0.0446394833,290.3075955333,26.0,29.745808
+2024-09-27 05:17:00,0.0446651667,290.0768988667,26.0,29.7393998
+2024-09-27 05:18:00,0.0446427167,289.8077556833,26.0,29.7361957833
+2024-09-27 05:19:00,0.0446042833,289.7853283333,26.0,29.7426039667
+2024-09-27 05:20:00,0.0447163833,289.4200624,26.0,29.745808
+2024-09-27 05:21:00,0.0446362333,289.1893647,26.0,29.745808
+2024-09-27 05:22:00,0.04471945,289.1124675333,26.0,29.748478
+2024-09-27 05:23:00,0.0447741167,290.0032039833,26.0,29.742604
+2024-09-27 05:24:00,0.0448381667,289.1381002667,26.0,29.74367195
+2024-09-27 05:25:00,0.0524832167,327.9493765833,26.0,29.7484779167
+2024-09-27 05:26:00,0.0590390333,364.9471279833,26.0,29.7468759667
+2024-09-27 05:27:00,0.0570554667,356.5395701333,26.0,29.744206
+2024-09-27 05:28:00,0.0468696333,299.8974676167,26.0,29.7447399667
+2024-09-27 05:29:00,0.0477506667,304.8349864833,26.0,29.7436719167
+2024-09-27 05:30:00,0.04860635,309.4649128333,26.0,29.7420699667
+2024-09-27 05:31:00,0.0488306167,310.22108295,26.0,29.7442059333
+2024-09-27 05:32:00,0.0489780833,310.5318780333,26.0,29.74741
+2024-09-27 05:33:00,0.0528933,329.1893584333,26.0,29.7410019167
+2024-09-27 05:34:00,0.0673469833,405.3668688833,26.0,29.73833195
+2024-09-27 05:35:00,0.0665043333,401.9673185667,26.0,29.7319238167
+2024-09-27 05:36:00,0.0485774333,308.2537609167,26.0,29.7394
+2024-09-27 05:37:00,0.0485615333,307.1932043167,26.0,29.7367298
+2024-09-27 05:38:00,0.0486159,307.6994571667,26.0,29.74313795
+2024-09-27 05:39:00,0.0486446833,307.69305115,26.0,29.7431379167
+2024-09-27 05:40:00,0.0487761,308.2281308667,26.0,29.7511480667
+2024-09-27 05:41:00,0.0488017333,307.6257603667,26.0,29.7345938667
+2024-09-27 05:42:00,0.06471325,388.76000865,26.0,29.7324577
+2024-09-27 05:43:00,0.06660035,400.5478989,26.0,29.74100195
+2024-09-27 05:44:00,0.0524832667,329.4681199167,26.0,29.7393997167
+2024-09-27 05:45:00,0.0485550167,305.71931625,26.0,29.7393999167
+2024-09-27 05:46:00,0.0485998,305.5655224833,26.0,29.7308557833
+2024-09-27 05:47:00,0.04867045,305.7705853333,26.0,29.7404678833
+2024-09-27 05:48:00,0.0486704333,305.3572593667,26.0,29.73726385
+2024-09-27 05:49:00,0.0487922333,305.5463005167,26.0,29.74313785
+2024-09-27 05:50:00,0.0488884333,305.8186464833,26.0,29.7393997833
+2024-09-27 05:51:00,0.0485197333,304.1621303333,26.0,29.7410018667
+2024-09-27 05:52:00,0.0487666167,305.4726056333,26.0,29.7404679
+2024-09-27 05:53:00,0.04887565,306.1678906,26.0,29.747944
+2024-09-27 05:54:00,0.0488916833,305.2098665,26.0,29.7474099667
+2024-09-27 05:55:00,0.0489171,305.2771526833,26.0,29.7399339333
+2024-09-27 05:56:00,0.0490068167,305.37327595,26.0,29.7383319
+2024-09-27 05:57:00,0.0491413667,305.9532168,26.0,29.7458079333
+2024-09-27 05:58:00,0.0490069333,305.546301,26.0,29.7410018833
+2024-09-27 05:59:00,0.0491862,305.04966605,26.0,29.7393997833
+2024-09-27 06:00:00,0.04915745,305.4373572667,26.0,29.7388658
+2024-09-27 06:01:00,0.0491414,304.9887890833,26.0,29.7436719167
+2024-09-27 06:02:00,0.0493334833,305.4469687667,26.0,29.7388658833
+2024-09-27 06:03:00,0.04920865,305.7994198333,26.0,29.7361958167
+2024-09-27 06:04:00,0.0491605667,305.5783373,26.0,29.7420699167
+2024-09-27 06:05:00,0.04927585,305.9051556333,26.0,29.7356617833
+2024-09-27 06:06:00,0.0493207333,305.5014391,26.0,29.7399338333
+2024-09-27 06:07:00,0.0495129667,306.1486655667,26.0,29.74420595
+2024-09-27 06:08:00,0.04932715,305.6840725833,26.4166666667,29.747944
+2024-09-27 06:09:00,-0.0597372,34.0916375667,42.9333333333,29.7415358833
+2024-09-27 06:10:00,-0.1055814833,0.1441845,45.2333333333,29.7394
+2024-09-27 06:11:00,0.0409615085,260.5977031017,24.593220339,29.7373182034
+2024-09-27 06:12:00,0.0486128,313.3771246833,26.0,29.745808
+2024-09-27 06:13:00,0.0487183833,312.6049374667,26.0,29.7436719667
+2024-09-27 06:14:00,0.0486800333,311.9865377167,26.0,29.7399338167
+2024-09-27 06:15:00,0.0488466,312.1467479833,26.0,29.7367298333
+2024-09-27 06:16:00,0.0489396333,312.14354105,26.0,29.7335257
+2024-09-27 06:17:00,0.0488947667,312.3261802167,26.0,29.7287196333
+2024-09-27 06:18:00,0.0428388167,279.4168517667,26.0,29.7420699667
+2024-09-27 06:19:00,0.039074,256.5844256667,26.0,29.7426038833
+2024-09-27 06:20:00,0.0390739667,256.6997719,26.0,29.741536
+2024-09-27 06:21:00,0.0384587833,252.7363024167,26.0,29.745274
+2024-09-27 06:22:00,0.0375550667,247.6321688333,26.0,29.745808
+2024-09-27 06:23:00,0.0365522833,241.7654592333,26.0,29.745808
+2024-09-27 06:24:00,0.0355720167,235.6071771333,26.0,29.75008
+2024-09-27 06:25:00,0.0345628333,229.7308566333,26.0,29.742604
+2024-09-27 06:26:00,0.0337872667,225.1842313833,26.0,29.746342
+2024-09-27 06:27:00,0.0337519333,225.0208251333,26.0,29.7538181
+2024-09-27 06:28:00,0.0338897167,225.1714162,26.0,29.75755605
+2024-09-27 06:29:00,0.0338063667,225.2066626667,26.0,29.74474
+2024-09-27 06:30:00,0.0340052333,226.1935325167,26.0,29.7564881833
+2024-09-27 06:31:00,0.0339122333,225.2034590667,26.0,29.7447399667
+2024-09-27 06:32:00,0.0339187167,225.5847474667,26.0,29.749546
+2024-09-27 06:33:00,0.0353893333,233.6494735167,26.0,29.747944
+2024-09-27 06:34:00,0.03567155,235.5559111667,26.0,29.75008005
+2024-09-27 06:35:00,0.0356169833,234.8670274,26.0,29.7479439833
+2024-09-27 06:36:00,0.0355144667,233.915409,26.0,29.751682
+2024-09-27 06:37:00,0.0401890833,259.7084268833,26.0,29.749546
+2024-09-27 06:38:00,0.0463953667,295.0368457167,26.0,29.743672
+2024-09-27 06:39:00,0.0538033833,333.8160822333,26.0,29.746342
+2024-09-27 06:40:00,0.0471804333,297.6449855833,26.0,29.742604
+2024-09-27 06:41:00,0.0477156167,301.4834995833,26.0,29.74474
+2024-09-27 06:42:00,0.04839165,304.7516832,26.0,29.7458079667
+2024-09-27 06:43:00,0.0487923167,306.7991018333,26.0,29.7388658667
+2024-09-27 06:44:00,0.0492118167,308.7055438333,26.0,29.7388658667
+2024-09-27 06:45:00,0.0554533667,337.33098585,26.0,29.7351277833
+2024-09-27 06:46:00,0.0683594833,405.82185205,26.0,29.7372638333
+2024-09-27 06:47:00,0.0640949,386.5555906833,26.0,29.7372637833
+2024-09-27 06:48:00,0.0493528333,308.2089044333,26.0,29.7367298333
+2024-09-27 06:49:00,0.0493302,308.3595030167,26.0,29.7313897667
+2024-09-27 06:50:00,0.0493622333,308.0903568167,26.0,29.7447398833
+2024-09-27 06:51:00,0.0494263833,308.6606860333,26.0,29.7308557667
+2024-09-27 06:52:00,0.0494199667,308.0038441333,26.0,29.74100195
+2024-09-27 06:53:00,0.0508617667,313.4155705,26.0,29.7399339667
+2024-09-27 06:54:00,0.0672604167,399.5514256333,26.0,29.73886585
+2024-09-27 06:55:00,0.0674560667,400.8426777333,26.0,29.7415359
+2024-09-27 06:56:00,0.0499552667,313.1400183167,26.0,29.73779795
+2024-09-27 06:57:00,0.0493111333,305.8474835333,26.0,29.7410019667
+2024-09-27 06:58:00,0.0493495167,306.1070157,26.0,29.74046795
+2024-09-27 06:59:00,0.0493143333,306.13905445,26.0,29.744206
+2024-09-27 07:00:00,0.04940075,305.9307889333,26.0,29.7377978667
+2024-09-27 07:01:00,0.0494136,305.6264004833,26.0,29.7287196833
+2024-09-27 07:02:00,0.0495097,305.95642215,26.0,29.7452739667
+2024-09-27 07:03:00,0.0494840667,306.0717678667,26.0,29.746342
+2024-09-27 07:04:00,0.0495512333,305.8410733333,26.0,29.743138
+2024-09-27 07:05:00,0.0497468833,306.6100526667,26.0,29.745808
+2024-09-27 07:06:00,0.0497339167,306.4498522167,26.0,29.73726385
+2024-09-27 07:07:00,0.0495897833,306.1102144,26.0,29.7436719167
+2024-09-27 07:08:00,0.0497340167,306.32168905,26.0,29.7372638833
+2024-09-27 07:09:00,0.0496378833,305.7193173333,26.0,29.7410018833
+2024-09-27 07:10:00,0.0498321053,305.3677080526,26.0,29.7357462105
+2024-09-27 07:12:00,0.0497555625,305.6832755,26.0,29.7480775
+2024-09-27 07:13:00,0.0498462333,305.6392184,26.0,29.7399339
+2024-09-27 07:14:00,0.0499903,305.64882825,26.0,29.74794405
+2024-09-27 07:15:00,0.0499230333,305.4117235,26.0,29.73833195
+2024-09-27 07:16:00,0.0499871167,305.7833986167,26.0,29.7313897333
+2024-09-27 07:17:00,0.05007685,306.0621585,26.0,29.7415359
+2024-09-27 07:18:00,0.05003835,305.5687268333,26.0,29.745808
+2024-09-27 07:19:00,0.04989405,304.37039555,26.0,29.7474099667
+2024-09-27 07:20:00,0.04167565,263.0791421,28.8,29.7404679
+2024-09-27 07:21:00,-0.0872285333,0.22108295,44.9833333333,29.7506140833
+2024-09-27 07:22:00,-0.0724094,17.5488623,41.8166666667,29.74527395
+2024-09-27 07:23:00,0.0483499833,307.8019850667,26.0,29.7468759667
+2024-09-27 07:24:00,0.0492119167,312.44152535,26.0,29.7442059167
+2024-09-27 07:25:00,0.0495481,312.74271255,26.0,29.74100185
+2024-09-27 07:26:00,0.0494680333,312.32617815,26.0,29.7426038833
+2024-09-27 07:27:00,0.0495801167,312.6273632333,26.0,29.74847795
+2024-09-27 07:28:00,0.0496635833,312.5344479,26.0,29.7393999
+2024-09-27 07:29:00,0.0496218167,312.4190984333,26.0,29.7490120333
+2024-09-27 07:30:00,0.04958975,311.60204775,26.0,29.7410020333
+2024-09-27 07:31:00,0.0496538167,312.0185847833,26.0,29.74634195
+2024-09-27 07:32:00,0.0494168333,309.5962808167,26.0,29.74527395
+2024-09-27 07:33:00,0.04845895,305.0592784333,26.0,29.7442059833
+2024-09-27 07:34:00,0.04750405,299.5065678667,26.0,29.7511479833
+2024-09-27 07:35:00,0.0465620167,294.3543756167,26.0,29.749012
+2024-09-27 07:36:00,0.0458667667,289.9423208167,26.0,29.74580795
+2024-09-27 07:37:00,0.0455462833,287.3213703,26.0,29.7463419667
+2024-09-27 07:38:00,0.0455430333,288.1960911167,26.0,29.746876
+2024-09-27 07:39:00,0.04552065,287.7891676,26.0,29.7484779667
+2024-09-27 07:40:00,0.0455623167,287.8949059833,26.0,29.74420595
+2024-09-27 07:41:00,0.0455687167,287.3918623833,26.0,29.74367195
+2024-09-27 07:42:00,0.04567135,287.3534142333,26.0,29.7479440333
+2024-09-27 07:43:00,0.0455527333,286.3953799833,26.0,29.7458079667
+2024-09-27 07:44:00,0.0457514667,287.1547570167,26.0,29.74741
+2024-09-27 07:45:00,0.0458893167,287.1387348167,26.0,29.7500801
+2024-09-27 07:46:00,0.0457162,286.6741433333,26.0,29.753818
+2024-09-27 07:47:00,0.0458667667,287.6033360167,26.0,29.752216
+2024-09-27 07:48:00,0.0458571833,287.6481927667,26.0,29.746876
+2024-09-27 07:49:00,0.0508780333,311.2880493,26.0,29.74741
+2024-09-27 07:50:00,0.0605222833,363.5917974667,26.0,29.75061405
+2024-09-27 07:51:00,0.0609323667,366.1935277833,26.0,29.751148
+2024-09-27 07:52:00,0.0479590333,298.2249257,26.0,29.75114805
+2024-09-27 07:53:00,0.0487120333,301.4931111,26.0,29.74473995
+2024-09-27 07:54:00,0.0494263833,305.3860923167,26.0,29.75275005
+2024-09-27 07:55:00,0.0499998667,307.4879847333,26.0,29.745808
+2024-09-27 07:56:00,0.05012815,308.2249298167,26.0,29.7554201
+2024-09-27 07:57:00,0.0589138167,349.3976282333,26.0,29.745808
+2024-09-27 07:58:00,0.0693721333,404.0628040167,26.0,29.74901205
+2024-09-27 07:59:00,0.0621339167,370.2435119667,26.0,29.748478
+2024-09-27 08:00:00,0.0494936667,304.52418985,26.0,29.75008
+2024-09-27 08:01:00,0.0498942167,306.0717674167,26.0,29.74741
+2024-09-27 08:02:00,0.049909,306.2349570678,26.0,29.748179322
+2024-09-27 08:03:00,0.0499583667,306.6004457167,26.0,29.7495460333
+2024-09-27 08:04:00,0.0500608333,306.3953823833,26.0,29.75221605
+2024-09-27 08:05:00,0.0536975333,322.7683431833,26.0,29.748478
+2024-09-27 08:06:00,0.0681768667,398.8881753667,26.0,29.74741
+2024-09-27 08:07:00,0.0680359167,398.5485416167,26.0,29.75114805
+2024-09-27 08:08:00,0.0501601333,307.9782125167,26.0,29.7538180833
+2024-09-27 08:09:00,0.0496539167,304.0083339333,26.0,29.7580901667
+2024-09-27 08:10:00,0.0496442,303.2777974167,26.0,29.7490120333
+2024-09-27 08:11:00,0.0499102833,304.0243510333,26.0,29.7490120333
+2024-09-27 08:12:00,0.04988145,303.6590840667,26.0,29.7463419667
+2024-09-27 08:13:00,0.0499167667,303.9891073167,26.0,29.750614
+2024-09-27 08:14:00,0.04977895,303.6142283333,26.0,29.746876
+2024-09-27 08:15:00,0.0499935667,303.8993963667,26.0,29.745808
+2024-09-27 08:16:00,0.05016645,303.9987214,26.0,29.74741
+2024-09-27 08:17:00,0.0500425424,303.6477899322,26.0,29.7530669153
+2024-09-27 08:18:00,0.05017935,303.8737605333,26.0,29.748478
+2024-09-27 08:19:00,0.0501120833,303.47645575,26.0,29.749546
+2024-09-27 08:20:00,0.0503235333,304.2422344333,26.0,29.753284
+2024-09-27 08:21:00,0.0502818333,303.79686285,26.0,29.74741
+2024-09-27 08:22:00,0.05042275,304.1397024667,26.0,29.74794405
+2024-09-27 08:23:00,0.0502881167,303.3546987167,26.0,29.7602261333
+2024-09-27 08:24:00,0.0504260167,304.00192575,26.0,29.7538181
+2024-09-27 08:25:00,0.0505638167,303.7488012167,26.0,29.7564881667
+2024-09-27 08:26:00,0.0504966167,303.4892716,26.0,29.753284
+2024-09-27 08:27:00,0.05047405,303.5693695,26.0,29.7532841333
+2024-09-27 08:28:00,0.0503394333,303.5501454667,26.0,29.7559541167
+2024-09-27 08:29:00,0.0506311833,303.5853897,26.0,29.7543520333
+2024-09-27 08:30:00,0.0506471667,303.7103526333,26.0,29.7580900833
+2024-09-27 08:31:00,0.0507786,304.50496685,26.0,29.7479440167
+2024-09-27 08:32:00,0.03595,221.8487676167,31.65,29.7484780167
+2024-09-27 08:33:00,-0.10431915,0.1057353,45.0,29.7580900333
+2024-09-27 08:34:00,-0.0514836,58.7600131333,38.9333333333,29.749546
+2024-09-27 08:35:00,0.0496955333,312.3582164667,26.0,29.754352
+2024-09-27 08:36:00,0.0499198167,311.2111475333,26.0,29.753818
+2024-09-27 08:37:00,0.0500319667,311.3681482833,26.0,29.7564881833
+2024-09-27 08:38:00,0.04992,310.2723468667,26.0,29.7564881167
+2024-09-27 08:39:00,0.0499936,310.3652668167,26.0,29.7580901667
+2024-09-27 08:40:00,0.0500191333,310.44536575,26.0,29.7623621333
+2024-09-27 08:41:00,0.0500223667,310.1441863,26.0,29.7575561333
+2024-09-27 08:42:00,0.0500577833,309.9231001,26.0,29.7564880333
+2024-09-27 08:43:00,0.0501408,309.2470385667,26.0,29.7532841
+2024-09-27 08:44:00,0.05022085,309.8141625333,26.0,29.75542
+2024-09-27 08:45:00,0.0501727833,309.1893654333,26.0,29.7570221167
+2024-09-27 08:46:00,0.0502786,309.4969577167,26.0,29.7602261833
+2024-09-27 08:47:00,0.0503491667,309.1156731333,26.0,29.7682363
+2024-09-27 08:48:00,0.0494681167,304.8510101667,26.0,29.7554201833
+2024-09-27 08:49:00,0.0482795667,298.5164978667,26.0,29.7661002
+2024-09-27 08:50:00,0.0472414,291.92246205,26.0,29.7591581333
+2024-09-27 08:51:00,0.0462353,286.8952259833,26.0,29.7591581
+2024-09-27 08:52:00,0.0461390833,286.2479938833,26.0,29.7612942
+2024-09-27 08:53:00,0.0462159833,286.65171145,26.0,29.7650322667
+2024-09-27 08:54:00,0.0463056667,286.0493432167,26.0,29.7618281333
+2024-09-27 08:55:00,0.0462767667,285.9788512167,26.0,29.7602262167
+2024-09-27 08:56:00,0.0463153,285.9275858167,26.0,29.7714404333
+2024-09-27 08:57:00,0.0463602,285.9692391667,26.0,29.7596921333
+2024-09-27 08:58:00,0.0464434667,286.0044824333,26.0,29.7586241667
+2024-09-27 08:59:00,0.04632505,285.9436039333,26.0,29.76236225
+2024-09-27 09:00:00,0.0464275167,285.8763188333,26.0,29.7730424667
+2024-09-27 09:01:00,0.0496764833,300.2851632667,26.0,29.7564881167
+2024-09-27 09:02:00,0.0610798,361.0893944833,26.0,29.7682362667
+2024-09-27 09:03:00,0.0617526667,363.74559215,26.0,29.7591580833
+2024-09-27 09:04:00,0.0494008833,303.3546904667,26.0,29.7682363167
+2024-09-27 09:05:00,0.0486703167,297.01057325,26.0,29.7735764167
+2024-09-27 09:06:00,0.04933995,300.34604295,26.0,29.7570221833
+2024-09-27 09:07:00,0.0501088333,303.8769628167,26.0,29.7703724
+2024-09-27 09:08:00,0.0505444833,305.4886237333,26.0,29.7714403333
+2024-09-27 09:09:00,0.0621917333,361.2463944167,26.0,29.7602261167
+2024-09-27 09:10:00,0.0701667167,402.7202851,26.0,29.7661002667
+2024-09-27 09:11:00,0.0618519667,363.80006815,26.0,29.7666341833
+2024-09-27 09:12:00,0.0504453333,304.4985628,26.0,29.7677022
+2024-09-27 09:13:00,0.0505093833,304.9887844333,26.0,29.7628962333
+2024-09-27 09:14:00,0.05056705,304.8221737333,26.0,29.77197435
+2024-09-27 09:15:00,0.05062145,304.8381918333,26.0,29.77464435
+2024-09-27 09:16:00,0.0505766833,304.9311087167,26.0,29.7623623167
+2024-09-27 09:17:00,0.05486705,323.82249005,26.0,29.7661002333
+2024-09-27 09:18:00,0.0689972167,397.5232299667,26.0,29.7661002833
+2024-09-27 09:19:00,0.0671720169,390.0542534576,26.0,29.767186339
+2024-09-27 09:20:00,0.0502336333,302.73630055,26.0,29.7703723167
+2024-09-27 09:21:00,0.0502016167,302.5825072,26.0,29.7650322
+2024-09-27 09:22:00,0.0503074333,302.29092995,26.0,29.7580901
+2024-09-27 09:23:00,0.0503170667,302.65940385,26.0,29.7741103167
+2024-09-27 09:24:00,0.0505863667,302.7234887833,26.0,29.7762464167
+2024-09-27 09:25:00,0.0504806,302.7907723833,26.0,29.7666343
+2024-09-27 09:26:00,0.0505318333,302.4158893167,26.0,29.7730423167
+2024-09-27 09:27:00,0.0506151,302.76193535,26.0,29.76716815
+2024-09-27 09:28:00,0.05069855,303.0342869667,26.0,29.7644982
+2024-09-27 09:29:00,0.0507722,302.8196099833,26.0,29.7725082333
+2024-09-27 09:30:00,0.05083935,303.3803278333,26.0,29.7687703667
+2024-09-27 09:31:00,0.0508714,302.1499504833,26.0,29.7735763333
+2024-09-27 09:32:00,0.0506599167,302.3518115667,26.0,29.7661002333
+2024-09-27 09:33:00,0.0509418833,302.92855065,26.0,29.7719743667
+2024-09-27 09:34:00,0.0509322,302.7683445167,26.0,29.7687702667
+2024-09-27 09:35:00,0.0510027667,302.6049341167,26.0,29.76610015
+2024-09-27 09:36:00,0.0510827667,302.40948315,26.0,29.7687703333
+2024-09-27 09:37:00,0.0510298814,302.7517254068,26.0,29.7688154746
+2024-09-27 09:38:00,0.0511021333,302.3229698833,26.0,29.77250825
+2024-09-27 09:39:00,0.0510635333,302.5120167167,26.0,29.7666341833
+2024-09-27 09:40:00,0.0511084833,302.5024051667,26.0,29.7741104333
+2024-09-27 09:41:00,0.05134565,303.0150629333,26.0,29.7693042833
+2024-09-27 09:42:00,0.05128165,302.111505,26.0,29.7767803667
+2024-09-27 09:43:00,0.05130725,302.2941363667,26.0,29.7634302167
+2024-09-27 09:44:00,0.01636655,178.4652379333,34.3333333333,29.7703722833
+2024-09-27 09:45:00,-0.1269946167,0.1025312,45.0,29.7794504667
+2024-09-27 09:46:00,-0.05292535,71.8295421,38.3333333333,29.7746443667
+2024-09-27 09:47:00,0.0502049167,311.6853536,26.0,29.7714402
+2024-09-27 09:48:00,0.0503587833,311.28484035,26.0,29.7810524667
+2024-09-27 09:49:00,0.05033625,311.1566766833,26.0,29.77571235
+2024-09-27 09:50:00,0.0504581167,310.9708409167,26.0,29.7741103667
+2024-09-27 09:51:00,0.0505125167,310.2755507,26.0,29.7693044
+2024-09-27 09:52:00,0.0506216333,310.1954455667,26.0,29.7671681833
+2024-09-27 09:53:00,0.0506215833,310.4902287167,26.0,29.7719743
+2024-09-27 09:54:00,0.0505990833,310.03844895,26.0,29.7735763667
+2024-09-27 09:55:00,0.0505158333,309.6667727667,26.0,29.7661002333
+2024-09-27 09:56:00,0.0507626,310.0929172167,26.0,29.7725083333
+2024-09-27 09:57:00,0.0508553167,310.512654,26.0,29.7714402667
+2024-09-27 09:58:00,0.0508458,310.25632925,26.0,29.7783823833
+2024-09-27 09:59:00,0.0508330167,309.7436725167,26.0,29.7687702833
+2024-09-27 10:00:00,0.0506502667,308.8753590833,26.0,29.7767804
+2024-09-27 10:01:00,0.04945855,302.2396724167,26.0,29.7757123
+2024-09-27 10:02:00,0.0484076333,295.79301705,26.0,29.7741103667
+2024-09-27 10:03:00,0.04720605,289.9647533333,26.0,29.7799844333
+2024-09-27 10:04:00,0.0465332667,286.1903218,26.0,29.7874607667
+2024-09-27 10:05:00,0.0464371167,285.11694855,26.0,29.7826546333
+2024-09-27 10:06:00,0.04666145,286.3312992,26.0,29.7789164
+2024-09-27 10:07:00,0.0467128,286.4979134833,26.0,29.7815864833
+2024-09-27 10:08:00,0.0466806333,285.9436050333,26.0,29.7746443667
+2024-09-27 10:09:00,0.0467415667,286.1454638833,26.0,29.7799844333
+2024-09-27 10:10:00,0.0467383,286.2640154667,26.0,29.7794504167
+2024-09-27 10:11:00,0.04687605,285.7898076333,26.0,29.7815864833
+2024-09-27 10:12:00,0.04686325,286.1550749833,26.0,29.77731435
+2024-09-27 10:13:00,0.0479878833,290.6504316333,26.0,29.7789164
+2024-09-27 10:14:00,0.06154425,360.4197334333,26.0,29.7783824333
+2024-09-27 10:15:00,0.0620088167,361.8904174667,26.0,29.7810524667
+2024-09-27 10:16:00,0.0517878667,312.96699825,26.0,29.7901308
+2024-09-27 10:17:00,0.04897795,296.9561015333,26.0,29.7810524833
+2024-09-27 10:18:00,0.0496763,299.9038796333,26.0,29.7773143833
+2024-09-27 10:19:00,0.0502722333,302.4959969833,26.0,29.7789164667
+2024-09-27 10:20:00,0.05067595,305.32200945,26.0,29.7778483667
+2024-09-27 10:21:00,0.0635180833,365.0624787667,26.0,29.78051845
+2024-09-27 10:22:00,0.0706857333,401.78788805,26.0,29.77411025
+2024-09-27 10:23:00,0.0596314333,350.8651113167,26.0,29.7778484167
+2024-09-27 10:24:00,0.0508553667,305.4053153167,26.0,29.7789164
+2024-09-27 10:25:00,0.0507016167,304.6074972667,26.0,29.77731435
+2024-09-27 10:26:00,0.0510412,305.3925005,26.0,29.7773144
+2024-09-27 10:27:00,0.0509258,304.7420717,26.0,29.7794504167
+2024-09-27 10:28:00,0.0510700667,305.1746211833,26.0,29.7783823833
+2024-09-27 10:29:00,0.0580037833,337.17718715,26.0,29.7826545167
+2024-09-27 10:30:00,0.0700000333,398.55495185,26.0,29.7783824333
+2024-09-27 10:31:00,0.0651682333,376.7959003333,26.0,29.78746075
+2024-09-27 10:32:00,0.0509643167,302.9317524167,26.0,29.7847906333
+2024-09-27 10:33:00,0.0507464576,302.2075684237,26.0,29.7867366271
+2024-09-27 10:34:00,0.0508714,303.0406946833,26.0,29.7895968333
+2024-09-27 10:35:00,0.0510507667,303.1944894833,26.0,29.7901308833
+2024-09-27 10:36:00,0.0509899667,303.2489629167,26.0,29.7815865333
+2024-09-27 10:37:00,0.0510571167,302.9029194667,26.0,29.7906649167
+2024-09-27 10:38:00,0.0512559167,303.4091706,26.0,29.7858587333
+2024-09-27 10:39:00,0.0510604,302.3966693667,26.0,29.7842566167
+2024-09-27 10:40:00,0.0511533667,302.8676736833,26.0,29.7842565667
+2024-09-27 10:41:00,0.0511886,302.9541834333,26.0,29.7821205333
+2024-09-27 10:42:00,0.0512815333,302.8132023167,26.0,29.7858587
+2024-09-27 10:43:00,0.0513296833,302.7234898167,26.0,29.7863926333
+2024-09-27 10:44:00,0.05126545,302.4991997833,26.0,29.7863927333
+2024-09-27 10:45:00,0.0514066167,303.2617772167,26.0,29.78479065
+2024-09-27 10:46:00,0.0513681833,302.6529972167,26.0,29.7911989333
+2024-09-27 10:47:00,0.0513586,302.5664855833,26.0,29.7890627833
+2024-09-27 10:48:00,0.0516308,303.16565705,26.0,29.7960051833
+2024-09-27 10:49:00,0.0515219333,303.19449,26.0,29.7885288
+2024-09-27 10:50:00,0.0512303,301.3553380167,26.0,29.7901309
+2024-09-27 10:51:00,0.0436782167,263.0983675167,26.0,29.79386905
+2024-09-27 10:52:00,0.0414898,250.5639224833,26.0,29.7901309833
+2024-09-27 10:53:00,0.0409131833,248.1896809833,26.0,29.7949371
+2024-09-27 10:54:00,0.0400416167,243.1592430333,26.0,29.8029474833
+2024-09-27 10:55:00,0.0389361167,237.22845095,26.0,29.8077536
+2024-09-27 10:56:00,0.0022813,125.2579300333,35.2166666667,29.7965391667
+2024-09-27 10:57:00,-0.1292695167,0.1153476,45.0,29.79600515
+2024-09-27 10:58:00,-0.0330983833,95.1586039667,35.4666666667,29.7933349833
+2024-09-27 10:59:00,0.0391477,249.5257922167,26.0,29.7992092833
+2024-09-27 11:00:00,0.03998085,253.2777955167,26.0,29.7949370833
+2024-09-27 11:01:00,0.04071125,256.5716084667,26.0,29.7981411833
+2024-09-27 11:02:00,0.0410637833,258.5421366333,26.0,29.7960051333
+2024-09-27 11:03:00,0.0411567,258.7792414333,26.0,29.8029474333
+2024-09-27 11:04:00,0.0411727,258.2505625167,26.0,29.8056175667
+2024-09-27 11:05:00,0.04112785,258.0519063,26.0,29.7960051833
+2024-09-27 11:06:00,0.0421723667,263.5565529667,26.0,29.794403
+2024-09-27 11:07:00,0.04196085,262.2492812833,26.0,29.7944030833
+2024-09-27 11:08:00,0.0412175167,257.9653934667,26.0,29.7992093
+2024-09-27 11:09:00,0.0404356167,253.2297331833,26.0,29.8034815
+2024-09-27 11:10:00,0.0393943,247.53284275,26.0,29.8002773333
+2024-09-27 11:11:00,0.03835945,241.9577049667,26.0,29.7992092833
+2024-09-27 11:12:00,0.04391855,270.2915700333,26.0,29.8066855833
+2024-09-27 11:13:00,0.0468665667,287.7731511,26.0,29.80561755
+2024-09-27 11:14:00,0.04681845,287.20602455,26.0,29.7986754167
+2024-09-27 11:15:00,0.0469337333,286.7894911167,26.0,29.8002772833
+2024-09-27 11:16:00,0.04706515,287.5040027167,26.0,29.7960052333
+2024-09-27 11:17:00,0.0470522167,287.3502083333,26.0,29.7970731
+2024-09-27 11:18:00,0.0471804333,287.542458,26.0,29.8056175667
+2024-09-27 11:19:00,0.0472477167,287.6610045667,26.0,29.80508365
+2024-09-27 11:20:00,0.0471677,287.0201821333,26.0,29.79814125
+2024-09-27 11:21:00,0.0472766167,287.4271066167,26.0,29.8029473
+2024-09-27 11:22:00,0.0474175667,287.7763533333,26.0,29.8034814833
+2024-09-27 11:23:00,0.0474305333,287.1707772333,26.0,29.8029474833
+2024-09-27 11:24:00,0.0473184,287.5873132333,26.0,29.7992093167
+2024-09-27 11:25:00,0.0476707167,288.16084585,26.0,29.80615165
+2024-09-27 11:26:00,0.0613424167,356.78628785,26.0,29.8024133833
+2024-09-27 11:27:00,0.0620761667,361.8327474,26.0,29.7960051167
+2024-09-27 11:28:00,0.0442102833,273.3963437,26.0,29.8082877167
+2024-09-27 11:29:00,0.0380998167,238.1512334667,26.0,29.8098897667
+2024-09-27 11:30:00,0.0388689,242.72989455,26.0,29.8066855833
+2024-09-27 11:31:00,0.0496314833,296.7414269333,26.0,29.8072196
+2024-09-27 11:32:00,0.0537391167,319.8526125,26.0,29.80454955
+2024-09-27 11:33:00,0.0541491833,321.4130061833,26.0,29.80081135
+2024-09-27 11:34:00,0.0542742333,321.0733727167,26.0,29.7997433333
+2024-09-27 11:35:00,0.0543287,321.0990017333,26.0,29.7976072667
+2024-09-27 11:36:00,0.05433195,321.0861838667,26.0,29.8077537
+2024-09-27 11:37:00,0.0544153,320.5543113333,26.0,29.7938690667
+2024-09-27 11:38:00,0.05429675,319.9166927833,26.0,29.8002772333
+2024-09-27 11:39:00,0.0533481333,315.1073405333,26.0,29.8088217167
+2024-09-27 11:40:00,0.0523998333,310.6952881,26.0,29.8093557333
+2024-09-27 11:41:00,0.0511118,304.1429047667,26.0,29.8120258667
+2024-09-27 11:42:00,0.0501185,298.1255998,26.0,29.8114918333
+2024-09-27 11:43:00,0.0499421833,297.571289,26.0,29.8082876333
+2024-09-27 11:44:00,0.0492053,293.322656,26.0,29.8179002333
+2024-09-27 11:45:00,0.04921815,292.8676741667,26.0,29.8200363667
+2024-09-27 11:46:00,0.0485262,289.4585042167,26.0,29.8173662
+2024-09-27 11:47:00,0.04666795,280.3876944167,26.0,29.8162981333
+2024-09-27 11:48:00,0.0393016167,239.0483828,26.0,29.814162
+2024-09-27 11:49:00,0.0497052,296.8151209833,26.0,29.8152300667
+2024-09-27 11:50:00,0.0515347333,305.3828882333,26.0,29.8077536
+2024-09-27 11:51:00,0.0400447667,246.6164699667,26.0,29.8018793667
+2024-09-27 11:52:00,0.0408264833,248.0711297833,26.0,29.8173662
+2024-09-27 11:53:00,0.0409161167,248.3210496833,26.0,29.8152300667
+2024-09-27 11:54:00,0.0408938167,248.5389278667,26.0,29.8152300667
+2024-09-27 11:55:00,0.0408137833,248.1416208333,26.0,29.8189683
+2024-09-27 11:56:00,0.0410411833,248.7760330833,26.0,29.8173662
+2024-09-27 11:57:00,0.0534796667,311.5924372167,26.0,29.8146960333
+2024-09-27 11:58:00,0.044739,269.7372602833,26.0,29.8189683
+2024-09-27 11:59:00,0.0426370167,257.4943936667,26.0,29.8162981333
+2024-09-27 12:00:00,0.04284855,258.0102507,26.0,29.8120258667
+2024-09-27 12:01:00,0.0426850667,257.0682414333,26.0,29.8109578
+2024-09-27 12:02:00,0.04266595,256.6100587667,26.0,29.8125599
+2024-09-27 12:03:00,0.0433836167,259.8718301167,26.0,29.814162
+2024-09-27 12:04:00,0.0513072333,300.8490871167,26.0,29.8157641
+2024-09-27 12:05:00,0.0520217167,305.1009279,26.0,29.8184342667
+2024-09-27 12:06:00,0.0518903667,304.05639195,26.0,29.8152300667
+2024-09-27 12:07:00,0.0520313333,303.7295802833,26.0,29.8162981333
+2024-09-27 12:08:00,-0.0369862034,77.6804495254,40.4745762712,29.8171489661
+2024-09-27 12:09:00,-0.11487025,0.1121435,45.05,29.8098897333
+2024-09-27 12:10:00,0.0315410667,238.1960925833,29.3333333333,29.8221725
+2024-09-27 12:11:00,0.0509738667,311.51233215,26.0,29.8114918333
+2024-09-27 12:12:00,0.0512111,311.1727025667,26.0,29.8136279667
+2024-09-27 12:13:00,0.05108605,310.30118395,26.0,29.8157641
+2024-09-27 12:14:00,0.0512304,310.4005115667,26.0,29.8125599
+2024-09-27 12:15:00,0.0512784,310.2114668,26.0,29.8189683
+2024-09-27 12:16:00,0.0512880333,309.7789157167,26.0,29.8130939333
+2024-09-27 12:17:00,0.0512109833,309.8237740333,26.0,29.8152300667
+2024-09-27 12:18:00,0.0513200167,309.2085894667,26.0,29.8088216667
+2024-09-27 12:19:00,0.0514321833,309.9839801667,26.0,29.8157641
+2024-09-27 12:20:00,0.0514899,309.5610374833,26.0,29.8216384667
+2024-09-27 12:21:00,0.0516083833,309.7981444,26.0,29.8211044333
+2024-09-27 12:22:00,0.05155385,309.64114365,26.0,29.8162981333
+2024-09-27 12:23:00,0.0517076,309.4873421167,26.0,29.8136279667
+2024-09-27 12:24:00,0.0513617333,307.95578205,26.0,29.8157641
+2024-09-27 12:25:00,0.0516628333,309.5738559167,26.0,29.8195023333
+2024-09-27 12:26:00,0.0518550333,310.0064064833,26.0,29.8173662
+2024-09-27 12:27:00,0.0518454167,309.7468789333,26.0,29.8146960333
+2024-09-27 12:28:00,0.0517814167,309.30471175,26.0,29.8162981333
+2024-09-27 12:29:00,0.0509035333,304.8990703333,26.0,29.8195023333
+2024-09-27 12:30:00,0.0495673833,297.8564544667,26.0,29.8221725
+2024-09-27 12:31:00,0.04852615,292.28452505,26.0,29.8211044333
+2024-09-27 12:32:00,0.0476676,287.2572879333,26.0,29.8237746
+2024-09-27 12:33:00,0.0474625167,286.0877928,26.0,29.8227065333
+2024-09-27 12:34:00,0.047549,286.7157926333,26.0,29.8291149333
+2024-09-27 12:35:00,0.0475392667,286.0877908167,26.0,29.8232405667
+2024-09-27 12:36:00,0.0476994833,286.4850987167,26.0,29.8243086333
+2024-09-27 12:37:00,0.047802,287.1098930167,26.0,29.8227065333
+2024-09-27 12:38:00,0.054066,316.9753289333,26.0,29.8243086333
+2024-09-27 12:39:00,0.0627169667,361.4194152333,26.0,29.8355233333
+2024-09-27 12:40:00,0.0619449167,358.9907099333,26.0,29.8323191333
+2024-09-27 12:41:00,0.04946175,294.5209832167,26.0,29.8264447667
+2024-09-27 12:42:00,0.0502626167,298.1480291167,26.0,29.8328531667
+2024-09-27 12:43:00,0.0510189333,302.72348575,26.0,29.8333872
+2024-09-27 12:44:00,0.0516692167,305.02723095,26.0,29.8296489667
+2024-09-27 12:45:00,0.0519191167,306.97532565,26.0,29.8243086333
+2024-09-27 12:46:00,0.0520185667,306.8311404167,26.0,29.8397956
+2024-09-27 12:47:00,0.0520698333,306.7606508667,26.0,29.8285809
+2024-09-27 12:48:00,0.0520504833,306.4017914833,26.0,29.8371254333
+2024-09-27 12:49:00,0.0521530833,306.1646867667,26.0,29.8323191333
+2024-09-27 12:50:00,0.0522493167,307.0265925333,26.0,29.8307170333
+2024-09-27 12:51:00,0.0516595667,303.4508204333,26.0,29.8360573667
+2024-09-27 12:52:00,0.0512398833,300.9644332667,26.0,29.8397956
+2024-09-27 12:53:00,0.0502338167,295.9980777667,26.0,29.8408636667
+2024-09-27 12:54:00,0.0493078833,291.73982855,26.0,29.8381935
+2024-09-27 12:55:00,0.0486831333,287.6353759,26.0,29.8360573667
+2024-09-27 12:56:00,0.0481097333,284.7901331333,26.0,29.8371254333
+2024-09-27 12:57:00,0.0481002167,284.4889479833,26.0,29.8408636667
+2024-09-27 12:58:00,0.0480745,284.5241944167,26.0,29.8360573667
+2024-09-27 12:59:00,0.0481834667,284.7901336167,26.0,29.8381935
+2024-09-27 13:00:00,0.0518520333,300.5799408167,26.0,29.8413977
+2024-09-27 13:01:00,0.0658218,373.1207963333,26.0,29.8381935
+2024-09-27 13:02:00,0.0657513167,374.1941676,26.0,29.8429998
+2024-09-27 13:03:00,0.05528345,319.2310171,26.0,29.846204
+2024-09-27 13:04:00,0.0516147833,300.9516193667,26.0,29.8376594667
+2024-09-27 13:05:00,0.0523229167,304.6235190167,26.0,29.8413977
+2024-09-27 13:06:00,0.0524767167,305.15540185,26.0,29.8413977
+2024-09-27 13:07:00,0.0551329167,315.23870865,26.0,29.8429998
+2024-09-27 13:08:00,0.072573,400.52226205,26.0,29.8408636667
+2024-09-27 13:09:00,0.0718263167,398.5773757667,26.0,29.8429998
+2024-09-27 13:10:00,0.0572988833,328.3146386667,26.0,29.8446019
+2024-09-27 13:11:00,0.0521884,302.4575469333,26.0,29.8504760167
+2024-09-27 13:12:00,0.05213705,302.6465901167,26.0,29.8467380333
+2024-09-27 13:13:00,0.05225895,302.8612655,26.0,29.8467380333
+2024-09-27 13:14:00,0.0522045,302.7971821167,26.0,29.85421425
+2024-09-27 13:15:00,0.0523133667,302.5760979833,26.0,29.8520781167
+2024-09-27 13:16:00,0.0524477833,303.0535135833,26.0,29.8515439833
+2024-09-27 13:17:00,0.0524447167,303.3354741667,26.0,29.8494078
+2024-09-27 13:18:00,0.0525278833,303.0791458,26.0,29.849408
+2024-09-27 13:19:00,0.0520954,302.3774475,26.6666666667,29.8515443333
+2024-09-27 13:20:00,-0.064662,24.9182950333,43.2166666667,29.8499422333
+2024-09-27 13:21:00,-0.10329065,0.1217558,45.3666666667,29.85902025
+2024-09-27 13:22:00,0.0497758333,290.00640985,25.9166666667,29.8558166
+2024-09-27 13:23:00,0.0515636333,310.7850025667,26.0,29.8611565333
+2024-09-27 13:24:00,0.0517813833,310.8715138167,26.0,29.8568842667
+2024-09-27 13:25:00,0.0518295167,309.8846515167,26.0,29.85101
+2024-09-27 13:26:00,0.0519191,310.59595935,26.0,29.86222455
+2024-09-27 13:27:00,0.0519800667,310.31399825,26.0,29.8542141
+2024-09-27 13:28:00,0.05202165,309.9359185333,26.0,29.8552822667
+2024-09-27 13:29:00,0.0520280833,309.8429991,26.0,29.8600884667
+2024-09-27 13:30:00,0.0522300667,310.5478982333,26.0,29.85901985
+2024-09-27 13:31:00,0.0522653333,310.0000004167,26.0,29.86863255
+2024-09-27 13:32:00,0.0521884333,310.2915729667,26.0,29.8664965167
+2024-09-27 13:33:00,0.0518326833,308.0871524667,26.0,29.8659623833
+2024-09-27 13:34:00,0.0521916667,309.2021803,26.0,29.8606221
+2024-09-27 13:35:00,0.0523678,309.724449,26.0,29.8600880667
+2024-09-27 13:36:00,0.05240945,309.4424863833,26.0,29.8600885167
+2024-09-27 13:37:00,0.0524189,309.1957695167,26.0,29.8675642333
+2024-09-27 13:38:00,0.0525855333,309.58026255,26.0,29.8648945167
+2024-09-27 13:39:00,0.05257595,309.52579325,26.0,29.8622245
+2024-09-27 13:40:00,0.0525407333,309.4264640833,26.0,29.8686325
+2024-09-27 13:41:00,0.0527939667,309.5642418333,26.0,29.8697003167
+2024-09-27 13:42:00,0.0526625167,309.69881575,26.0,29.8595542333
+2024-09-27 13:43:00,0.0526722667,309.3719973667,26.0,29.8654285
+2024-09-27 13:44:00,0.0524094,307.4174927,26.0,29.8697005667
+2024-09-27 13:45:00,0.05141285,302.2012138167,26.0,29.8787783833
+2024-09-27 13:46:00,0.05004145,295.1874383667,26.0,29.8739726333
+2024-09-27 13:47:00,0.04873765,288.8657485333,26.0,29.8723706833
+2024-09-27 13:48:00,0.04835,286.12944425,26.0,29.86542875
+2024-09-27 13:49:00,0.0483051667,286.0781792333,26.0,29.87023445
+2024-09-27 13:50:00,0.048462,286.16148665,26.0,29.8718367
+2024-09-27 13:51:00,0.0483532167,285.8763208167,26.0,29.8691666833
+2024-09-27 13:52:00,0.05937505,338.3530939667,26.0,29.8718366
+2024-09-27 13:53:00,0.0637711,360.794613,26.0,29.8777105167
+2024-09-27 13:54:00,0.05896825,339.0676066667,26.0,29.8771765333
+2024-09-27 13:55:00,0.0505670167,295.5014404333,26.0,29.8761085667
+2024-09-27 13:56:00,0.0514257667,299.9935923167,26.0,29.87504045
+2024-09-27 13:57:00,0.0521819833,303.8353124667,26.0,29.8713026167
+2024-09-27 13:58:00,0.0526240667,305.6328076333,26.0,29.87984645
+2024-09-27 13:59:00,0.0529189,306.8023032167,26.0,29.8830509
+2024-09-27 14:00:00,0.05303755,307.2028183167,26.0,29.8771766833
+2024-09-27 14:01:00,0.0530535333,306.8471604833,26.0,29.8793126167
+2024-09-27 14:02:00,0.0530567333,306.4242200333,26.0,29.8771765333
+2024-09-27 14:03:00,0.05310155,306.7157899333,26.0,29.8782445
+2024-09-27 14:04:00,0.0529958,306.0749742667,26.0,29.8835849333
+2024-09-27 14:05:00,0.0523742333,302.7683409,26.0,29.8846526
+2024-09-27 14:06:00,0.0518583167,300.2371064333,26.0,29.88305045
+2024-09-27 14:07:00,0.0509324167,295.1265597667,26.0,29.8809144167
+2024-09-27 14:08:00,0.050032,290.7657801833,26.0,29.8787784833
+2024-09-27 14:09:00,0.0493462667,286.9272679667,26.0,29.8889243167
+2024-09-27 14:10:00,0.0487663,283.3066383167,26.0,29.8835844333
+2024-09-27 14:11:00,0.0487023,283.8449255167,26.0,29.8958668
+2024-09-27 14:12:00,0.05454655,309.3143255667,26.0,29.8899925833
+2024-09-27 14:13:00,0.06679275,372.6337748833,26.0,29.8862547
+2024-09-27 14:14:00,0.0652804167,365.9019578,26.0,29.8969348167
+2024-09-27 14:15:00,0.0557321,317.2540878333,26.0,29.8937304667
+2024-09-27 14:16:00,0.0524286,300.4421665,26.0,29.89426445
+2024-09-27 14:17:00,0.05301185,303.8289058333,26.0,29.8921289167
+2024-09-27 14:18:00,0.0529158,304.0179469833,26.0,29.8857209667
+2024-09-27 14:19:00,0.0581961167,325.5046488833,26.0,29.8964004833
+2024-09-27 14:20:00,0.07336435,398.6254418333,26.0,29.8910606
+2024-09-27 14:21:00,0.0704708667,386.1422610833,26.0,29.8958669
+2024-09-27 14:22:00,0.0575841,324.1941725667,26.0,29.8867887833
+2024-09-27 14:23:00,0.0527233833,301.3361072667,26.0,29.8947987333
+2024-09-27 14:24:00,0.0529253333,301.90963715,26.0,29.907081
+2024-09-27 14:25:00,0.0528709167,301.5379657833,26.0,29.9012068333
+2024-09-27 14:26:00,0.0526176667,299.8750401,26.0,29.8937307167
+2024-09-27 14:27:00,0.0528484167,301.0028808333,26.0,29.89746915
+2024-09-27 14:28:00,0.0529959167,301.4130064667,26.0,29.8931965833
+2024-09-27 14:29:00,0.0530663833,301.8071114333,26.0,29.9028085333
+2024-09-27 14:30:00,0.05318155,302.0538278167,26.0,29.9028086833
+2024-09-27 14:31:00,0.04692725,276.9785301,28.2166666667,29.9012066833
+2024-09-27 14:32:00,-0.0836815833,1.05414925,44.9333333333,29.9097509667
+2024-09-27 14:33:00,-0.0800161,8.4844597667,42.45,29.9070808
+2024-09-27 14:34:00,0.0563600667,330.5350841667,26.0,29.9022745
+2024-09-27 14:35:00,0.052371,310.1537941833,26.0,29.9028089333
+2024-09-27 14:36:00,0.0523454,309.5706489833,26.0,29.89586625
+2024-09-27 14:37:00,0.05242225,309.3335482667,26.0,29.9097509667
+2024-09-27 14:38:00,0.0525119333,308.9907102,26.0,29.9038767
+2024-09-27 14:39:00,0.0525503,309.3944267833,26.0,29.9113530667
+2024-09-27 14:40:00,0.0525438333,309.0291598167,26.0,29.910285
+2024-09-27 14:41:00,0.05269455,308.8945875,26.0,29.9060127333
+2024-09-27 14:42:00,0.0526593667,308.4331993833,26.0,29.9145571667
+2024-09-27 14:43:00,0.05257605,307.1835917833,26.0,29.9108190333
+2024-09-27 14:44:00,0.05297345,309.5706505333,26.0,29.9113530667
+2024-09-27 14:45:00,0.0528868167,308.8657483,26.0,29.9108190333
+2024-09-27 14:46:00,0.0529093167,308.3723188667,26.0,29.91669335
+2024-09-27 14:47:00,0.05306965,308.68952455,26.0,29.9161593667
+2024-09-27 14:48:00,0.0530728333,308.4972802167,26.0,29.9086828667
+2024-09-27 14:49:00,0.0532200333,308.7023425167,26.0,29.9140232333
+2024-09-27 14:50:00,0.0531848333,308.49407685,26.0,29.9124210833
+2024-09-27 14:51:00,0.0533322167,309.28548565,26.0,29.9129551167
+2024-09-27 14:52:00,0.0532745,308.8561357667,26.0,29.910819
+2024-09-27 14:53:00,0.0533033167,308.2858050833,26.0,29.91348915
+2024-09-27 14:54:00,0.0533322333,308.37552015,26.0,29.9193634167
+2024-09-27 14:55:00,0.05337055,307.7475193,26.0,29.9193635333
+2024-09-27 14:56:00,0.0528419667,305.99487465,26.0,29.9198975667
+2024-09-27 14:57:00,0.0517653,299.891064,26.0,29.9284418
+2024-09-27 14:58:00,0.05041325,292.95097855,26.0,29.9279079
+2024-09-27 14:59:00,0.04945205,287.8692738333,26.0,29.9236357167
+2024-09-27 15:00:00,0.0490484,285.5879520667,26.0,29.9407244167
+2024-09-27 15:01:00,0.0490419667,285.7129124,26.0,29.9263058
+2024-09-27 15:02:00,0.0490739833,285.2899689667,26.0,29.9273738167
+2024-09-27 15:03:00,0.0491028,285.0881148167,26.0,29.9279078
+2024-09-27 15:04:00,0.0490259667,285.0945191667,26.0,29.9247037333
+2024-09-27 15:05:00,0.0497885167,287.00096325,26.0,29.9295099167
+2024-09-27 15:06:00,0.0641524,357.1771855667,26.0,29.9327140833
+2024-09-27 15:07:00,0.0648670333,360.3844914333,26.0,29.9385882833
+2024-09-27 15:08:00,0.0553860667,316.8247390833,26.0,29.9353842333
+2024-09-27 15:09:00,0.04811925,279.96796185,26.0,29.9380543167
+2024-09-27 15:10:00,0.0470072833,271.64690575,26.0,29.93858835
+2024-09-27 15:11:00,0.05329385,304.3127167333,26.0,29.93805435
+2024-09-27 15:12:00,0.05410445,307.5937220333,26.0,29.93698625
+2024-09-27 15:13:00,0.05443765,309.6539579833,26.0,29.9364522
+2024-09-27 15:14:00,0.0545723,310.13457015,26.0,29.92630585
+2024-09-27 15:15:00,0.0547580333,309.7532855667,26.0,29.9364522167
+2024-09-27 15:16:00,0.05462355,309.5802672,26.0,29.9343161667
+2024-09-27 15:17:00,0.0543927833,308.3947438,26.0,29.93645225
+2024-09-27 15:18:00,0.0536269333,303.9794989167,26.0,29.9375202833
+2024-09-27 15:19:00,0.05297005,300.8138418,26.0,29.9375203167
+2024-09-27 15:20:00,0.051868,294.9919875167,26.0,29.9396563833
+2024-09-27 15:21:00,0.0507399333,289.22460485,26.0,29.9391224
+2024-09-27 15:22:00,0.049734,284.0115347667,26.0,29.9417924333
+2024-09-27 15:23:00,0.0487409,279.49054845,26.0,29.9460645667
+2024-09-27 15:24:00,0.0481191167,274.7004171,26.0,29.94499655
+2024-09-27 15:25:00,0.0537840167,304.0852300667,26.0,29.9524726833
+2024-09-27 15:26:00,0.053486,303.9666769333,26.0,29.9514045333
+2024-09-27 15:27:00,0.0414867167,241.5123356,26.0,29.94873455
+2024-09-27 15:28:00,0.04259205,246.4883045,26.0,29.9508707333
+2024-09-27 15:29:00,0.0436014833,251.9961540167,26.0,29.9498025667
+2024-09-27 15:30:00,0.0462127167,263.4379994167,26.0,29.9503366667
+2024-09-27 15:31:00,0.0632105167,343.4347966333,26.0,29.95033665
+2024-09-27 15:32:00,0.07438625,398.5068901833,26.0,29.9482006833
+2024-09-27 15:33:00,0.0697692333,377.7314951,26.0,29.9433945333
+2024-09-27 15:34:00,0.0565395167,317.6321695833,26.0,29.9546087833
+2024-09-27 15:35:00,0.0532841833,300.8490814333,26.0,29.95033665
+2024-09-27 15:36:00,0.0533609333,300.5222690833,26.0,29.9465985
+2024-09-27 15:37:00,0.0532968667,300.3396336333,26.0,29.9487346
+2024-09-27 15:38:00,0.0534057833,300.0384500667,26.0,29.9514047
+2024-09-27 15:39:00,0.0534090667,300.4069198167,26.0,29.9524727167
+2024-09-27 15:40:00,0.0535372833,300.7016942833,26.0,29.9465985667
+2024-09-27 15:41:00,0.0534795333,300.1986545667,26.0,29.9465985167
+2024-09-27 15:42:00,0.05358865,300.5671247667,26.0,29.9626188167
+2024-09-27 15:43:00,0.03252485,200.7882059,32.6166666667,29.9540748
+2024-09-27 15:44:00,-0.10959935,0.1153476,45.0,29.9631529167
+2024-09-27 15:45:00,-0.0413905,79.7725100333,36.4,29.9615507833
+2024-09-27 15:46:00,0.0560749167,329.3079166833,26.0,29.9535406333
+2024-09-27 15:47:00,0.0525438,309.3816109333,26.0,29.9540746667
+2024-09-27 15:48:00,0.05260795,308.6190356,26.0,29.9556767
+2024-09-27 15:49:00,0.05272665,308.1832766667,26.0,29.96208485
+2024-09-27 15:50:00,0.0527842833,308.3498935333,26.0,29.96582285
+2024-09-27 15:51:00,0.0528163333,307.6770258333,26.0,29.96582295
+2024-09-27 15:52:00,0.0526048667,306.5716087,26.0,29.9636869667
+2024-09-27 15:53:00,0.0528932833,307.7282907833,26.0,29.9652889167
+2024-09-27 15:54:00,0.0530566667,308.5100936,26.0,29.971697
+2024-09-27 15:55:00,0.0530311167,308.7407880833,26.0,29.971163
+2024-09-27 15:56:00,0.0530728,308.2569710667,26.0,29.97009495
+2024-09-27 15:57:00,0.0532072833,308.2954217667,26.0,29.96902695
+2024-09-27 15:58:00,0.053313,308.2761976667,26.0,29.9658228167
+2024-09-27 15:59:00,0.0534508,309.0964455333,26.0,29.975969
+2024-09-27 16:00:00,0.05337385,308.0134549667,26.0,29.972231
+2024-09-27 16:01:00,0.0533097333,307.5680857833,26.0,29.9754350667
+2024-09-27 16:02:00,0.0533257333,308.0038496833,26.0,29.9716969667
+2024-09-27 16:03:00,0.0533322833,307.4014720333,26.0,29.972765
+2024-09-27 16:04:00,0.0533835,307.4559392667,26.0,29.970629
+2024-09-27 16:05:00,0.0535789333,307.9045200333,26.0,29.9781051
+2024-09-27 16:06:00,0.0535020333,307.39185935,26.0,29.973833
+2024-09-27 16:07:00,0.05359185,308.1448260833,26.0,29.9759691
+2024-09-27 16:08:00,0.0535917667,307.8116006667,26.0,29.9759691
+2024-09-27 16:09:00,0.05222365,301.4162150333,26.0,29.9823771667
+2024-09-27 16:10:00,0.0448637333,262.3261789333,26.0,29.9807752333
+2024-09-27 16:11:00,0.0505030667,291.0637609167,26.0,29.9807753667
+2024-09-27 16:12:00,0.0489491167,282.8228124,26.0,29.974367
+2024-09-27 16:13:00,0.0479365833,277.1355323333,26.0,29.9845132333
+2024-09-27 16:14:00,0.0478660667,277.0265916833,26.0,29.9834451
+2024-09-27 16:15:00,0.0479655,276.6421007,26.0,29.9909213667
+2024-09-27 16:16:00,0.04790465,276.7574495333,26.0,29.9829111
+2024-09-27 16:17:00,0.0479397833,276.8439577667,26.0,29.9797071667
+2024-09-27 16:18:00,0.0479398,277.0778555833,26.0,29.9882513
+2024-09-27 16:19:00,0.0481482667,276.67414165,26.0,29.9866493333
+2024-09-27 16:20:00,0.0481290167,276.5716103167,26.0,29.9967956667
+2024-09-27 16:21:00,0.0481778621,276.7680508621,26.0,29.993701931
+2024-09-27 16:22:00,0.0482698833,276.9849382333,26.0,29.9839791667
+2024-09-27 16:23:00,0.0481706333,276.8055050833,26.0,29.9887852667
+2024-09-27 16:24:00,0.0485966833,278.6831122,26.0,29.9882513333
+2024-09-27 16:25:00,0.0397788667,233.7744326667,26.0,29.9941254667
+2024-09-27 16:26:00,0.0392885,230.7850059167,26.0,30.0058739667
+2024-09-27 16:27:00,0.03917645,230.29157135,26.0,30.0032038
+2024-09-27 16:28:00,0.0391188167,230.2050594833,26.0,30.0112142
+2024-09-27 16:29:00,0.0391636,230.2178789667,26.0,30.0032037667
+2024-09-27 16:30:00,0.0403908833,235.70650205,26.0,30.0048059
+2024-09-27 16:31:00,0.0494841333,282.1691773167,26.0,30.0042719
+2024-09-27 16:32:00,0.0498557667,284.18456075,26.0,30.0090780833
+2024-09-27 16:33:00,0.0499584,284.3864176667,26.0,30.0053399167
+2024-09-27 16:34:00,0.0499775167,284.3351506833,26.0,30.0010676667
+2024-09-27 16:35:00,0.04984945,283.1047756667,26.0,30.0042718667
+2024-09-27 16:36:00,0.05864145,322.9637945667,26.0,30.0016017667
+2024-09-27 16:37:00,0.0687792,374.9247058167,26.0,30.0026698667
+2024-09-27 16:38:00,0.0648767,358.1159906,26.0,30.0069419833
+2024-09-27 16:39:00,0.0515411667,291.3265005667,26.0,30.0048059
+2024-09-27 16:40:00,0.0523261167,295.4790155333,26.0,30.0048059
+2024-09-27 16:41:00,0.0528708333,298.0519088,26.0,30.0074760167
+2024-09-27 16:42:00,0.0534539833,300.42294005,26.0,30.0069420333
+2024-09-27 16:43:00,0.06782115,364.5882700333,26.0,30.0026697667
+2024-09-27 16:44:00,0.0753443667,400.5062433833,26.0,30.0106801667
+2024-09-27 16:45:00,0.0691797333,374.43127245,26.0,30.0192246167
+2024-09-27 16:46:00,0.0540243167,302.1339271333,26.0,30.0138843667
+2024-09-27 16:47:00,0.05469075,304.1813527,26.0,30.0058739333
+2024-09-27 16:48:00,0.07070835,376.97532565,26.0,30.0074760333
+2024-09-27 16:49:00,0.07108935,380.4261451833,26.0,30.0154864333
+2024-09-27 16:50:00,0.0611727833,337.1643717167,26.0,30.0144184167
+2024-09-27 16:51:00,0.0536526167,299.6507547667,26.0,30.0101462
+2024-09-27 16:52:00,0.0536654333,299.7949352667,26.0,30.0202925833
+2024-09-27 16:53:00,0.0537968167,299.83979445,26.0,30.0176225333
+2024-09-27 16:54:00,0.0537872333,300.1185491333,26.0,30.0192245333
+2024-09-27 16:55:00,-0.01105095,115.8699130167,38.2166666667,30.02349685
+2024-09-27 16:56:00,-0.1092054667,0.128164,45.0,30.0170885
+2024-09-27 16:57:00,0.0113264833,189.7661006167,31.4833333333,30.0213606667
+2024-09-27 16:58:00,0.0529253333,308.1063784833,26.0,30.0208266833
+2024-09-27 16:59:00,0.0529638167,307.7635360333,26.0,30.03043915
+2024-09-27 17:00:00,0.0532425,307.8083952833,26.0,30.0218947833
+2024-09-27 17:01:00,0.0530437,306.9432887667,26.0,30.0186906
+2024-09-27 17:02:00,0.05311115,306.7670610333,26.0,30.0245648
+2024-09-27 17:03:00,0.0533449667,307.8404370833,26.0,30.0186906667
+2024-09-27 17:04:00,0.0534441833,307.3341863667,26.0,30.0224287667
+2024-09-27 17:05:00,0.0535628667,307.85325515,26.0,30.0283030833
+2024-09-27 17:06:00,0.0534186333,306.9817313667,26.0,30.0309731833
+2024-09-27 17:07:00,0.0535821,307.0874686167,26.0,30.0299051333
+2024-09-27 17:08:00,0.05363025,307.2765147333,26.0,30.02776905
+2024-09-27 17:09:00,0.053679,307.3916966949,26.0,30.0332903559
+2024-09-27 17:10:00,0.0538161167,307.0297973833,26.0,30.0357794167
+2024-09-27 17:11:00,0.0537968667,307.6545979833,26.0,30.0373814833
+2024-09-27 17:12:00,0.0539026333,308.17045785,26.0,30.03524535
+2024-09-27 17:13:00,0.053909,307.3309804833,26.0,30.0336432833
+2024-09-27 17:14:00,0.0537231667,306.7318132167,26.0,30.0363133833
+2024-09-27 17:15:00,0.0540788,306.94649005,26.0,30.0320413
+2024-09-27 17:16:00,0.0540498333,307.34379575,26.0,30.0352454
+2024-09-27 17:17:00,0.05410435,307.1131057833,26.0,30.0389835167
+2024-09-27 17:18:00,0.0540659333,306.74783695,26.0,30.0448578
+2024-09-27 17:19:00,0.0541523667,307.0426108,26.0,30.0405856333
+2024-09-27 17:20:00,0.0544152667,307.3149632333,26.0,30.0427216667
+2024-09-27 17:21:00,0.0543704167,307.4463282833,26.0,30.0416536
+2024-09-27 17:22:00,0.0543447667,306.8952261833,26.0,30.0571404333
+2024-09-27 17:23:00,0.0544055833,306.6645256,26.0,30.0566064167
+2024-09-27 17:24:00,0.05447935,307.4174916833,26.0,30.0475278833
+2024-09-27 17:25:00,0.0544537,306.9496935,26.0,30.0624806833
+2024-09-27 17:26:00,0.0545114333,306.7093849,26.0,30.0475279
+2024-09-27 17:27:00,0.0547388,307.2124289333,26.0,30.0443237333
+2024-09-27 17:28:00,0.0547004333,306.9304659,26.0,30.0491300167
+2024-09-27 17:29:00,0.0546843667,307.4591465833,26.0,30.0507320333
+2024-09-27 17:30:00,0.0547933333,307.1771878,26.0,30.0491299833
+2024-09-27 17:31:00,0.0547964333,306.94008545,26.0,30.0443238
+2024-09-27 17:32:00,0.0548927167,307.28612725,26.0,30.05500415
+2024-09-27 17:33:00,0.0539601333,302.4158945333,26.0,30.0630147167
+2024-09-27 17:34:00,0.05268495,296.4178156,26.0,30.0598104167
+2024-09-27 17:35:00,0.05157945,290.5382909833,26.0,30.0662188333
+2024-09-27 17:36:00,0.0505092833,285.46619855,26.0,30.0614124833
+2024-09-27 17:37:00,0.0504516667,284.4985587167,26.0,30.0587423833
+2024-09-27 17:38:00,0.0504934,284.47613275,26.0,30.0678208333
+2024-09-27 17:39:00,0.0504324333,284.3159272333,26.0,30.0614124833
+2024-09-27 17:40:00,0.0504293,284.2486394667,26.0,30.0624805
+2024-09-27 17:41:00,0.05071445,285.0432576167,26.0,30.0763650667
+2024-09-27 17:42:00,0.0506984833,284.7837234833,26.0,30.0651506667
+2024-09-27 17:43:00,0.0507177333,284.9919902,26.0,30.0747628667
+2024-09-27 17:44:00,0.0508778667,284.9695617333,26.0,30.0763652167
+2024-09-27 17:45:00,0.0507946,284.7644994667,26.0,30.0731611667
+2024-09-27 17:46:00,0.0509002333,285.5014418667,26.0,30.0678208333
+2024-09-27 17:47:00,0.0509067,284.70682385,26.0,30.0790348333
+2024-09-27 17:48:00,0.0509227,284.2742731833,26.0,30.0747631667
+2024-09-27 17:49:00,0.0610573,330.4870229333,26.0,30.0720931
+2024-09-27 17:50:00,0.0702755667,376.0589533,26.0,30.0731611667
+2024-09-27 17:51:00,0.06438005,351.6084585333,26.0,30.08490915
+2024-09-27 17:52:00,0.0523838333,291.6757446833,26.0,30.0811711667
+2024-09-27 17:53:00,0.0533289167,295.5879510333,26.0,30.072093
+2024-09-27 17:54:00,0.0538096,298.0390878833,26.0,30.07689915
+2024-09-27 17:55:00,0.07124315,375.5911591167,26.0,30.0758312333
+2024-09-27 17:56:00,0.0764561667,400.9163727667,26.0,30.0774331333
+2024-09-27 17:57:00,0.0611950833,334.2037774833,26.0,30.0790351833
+2024-09-27 17:58:00,0.0547932667,302.6081430333,26.0,30.0795692667
+2024-09-27 17:59:00,0.0559627833,306.51714525,26.0,30.0801029
+2024-09-27 18:00:00,0.0718264333,379.11246205,26.0,30.0859772667
+2024-09-27 18:01:00,0.0721307333,379.8814460333,26.0,30.0865111
+2024-09-27 18:02:00,0.0611791333,333.5533484167,26.0,30.08010315
+2024-09-27 18:03:00,0.0543864,299.3752011,26.0,30.0811712667
+2024-09-27 18:04:00,0.0545018833,300.1345723833,26.0,30.0854432333
+2024-09-27 18:05:00,0.05462675,300.7433502167,26.0,30.08651145
+2024-09-27 18:06:00,0.0546362667,300.3172067667,26.25,30.0902492833
+2024-09-27 18:07:00,-0.0612080167,42.1243184833,42.3833333333,30.08971535
+2024-09-27 18:08:00,-0.1249214667,0.1249599,45.0333333333,30.0907833667
+2024-09-27 18:09:00,0.0329124833,231.0349211167,28.9833333333,30.08811315
+2024-09-27 18:10:00,0.05362375,309.6795917167,26.0,30.08971505
+2024-09-27 18:11:00,0.0534122833,307.5136148833,26.0,30.09131745
+2024-09-27 18:12:00,0.0539923,309.6411400333,26.0,30.09452145
+2024-09-27 18:13:00,0.0540050667,309.6699771167,26.0,30.1052015667
+2024-09-27 18:14:00,0.05399865,309.5001594833,26.0,30.1003956667
+2024-09-27 18:15:00,0.0542005833,309.3175285833,26.0,30.0918513833
+2024-09-27 18:16:00,0.0542548833,309.47773265,26.0,30.0950553833
+2024-09-27 18:17:00,0.0542647,309.4008349667,26.0,30.0982594833
+2024-09-27 18:18:00,0.0543415,309.298303,26.0,30.1014635333
+2024-09-27 18:19:00,0.0545050333,309.1477104667,26.0,30.1019973667
+2024-09-27 18:20:00,0.0543287167,308.7311780333,26.0,30.0961237
+2024-09-27 18:21:00,0.05443445,308.79205755,26.0,30.1030654833
+2024-09-27 18:22:00,0.0545114,308.8689547167,26.0,30.0993276
+2024-09-27 18:23:00,0.0545818667,308.67350235,26.0,30.0993276
+2024-09-27 18:24:00,0.0547132667,309.0804227333,26.0,30.0993275
+2024-09-27 18:25:00,0.0547164833,308.9650734667,26.0,30.1019975167
+2024-09-27 18:26:00,0.0547291833,308.8176866667,26.0,30.0993275
+2024-09-27 18:27:00,0.0549182,308.9522616167,26.0,30.0987933167
+2024-09-27 18:28:00,0.0547900833,308.5453391667,26.0,30.0998615833
+2024-09-27 18:29:00,0.0550239667,308.7888506667,26.0,30.1057354
+2024-09-27 18:30:00,0.0549246333,308.3050321667,26.0,30.10092955
+2024-09-27 18:31:00,0.0550240333,308.78884965,26.0,30.0987935167
+2024-09-27 18:32:00,0.0550943833,308.8753619167,26.0,30.0998614333
+2024-09-27 18:33:00,0.05510735,308.21211385,26.0,30.1030654833
+2024-09-27 18:34:00,0.0553764833,308.9650744167,26.0,30.1105414
+2024-09-27 18:35:00,0.05537325,309.0836312667,26.0,30.1110753833
+2024-09-27 18:36:00,0.0553828833,308.2345372,26.0,30.1052015167
+2024-09-27 18:37:00,0.0553733,308.6542804,26.0,30.1019975667
+2024-09-27 18:38:00,0.0553893833,308.8881797833,26.0,30.10733735
+2024-09-27 18:39:00,0.0554983,308.62223845,26.0,30.1062693833
+2024-09-27 18:40:00,0.0555558833,309.0676105,26.0,30.1046675833
+2024-09-27 18:41:00,0.055604,308.7568107833,26.0,30.1062695333
+2024-09-27 18:42:00,0.055604,308.42999195,26.0,30.1041336
+2024-09-27 18:43:00,0.0555398667,308.06152035,26.0,30.1025316
+2024-09-27 18:44:00,0.0553476167,306.7446264833,26.0,30.1100072667
+2024-09-27 18:45:00,0.0557673167,308.7792396667,26.0,30.1132115667
+2024-09-27 18:46:00,0.0558987167,308.6863197333,26.0,30.1084056167
+2024-09-27 18:47:00,0.0557834,308.0999709,26.0,30.10733745
+2024-09-27 18:48:00,0.0552066167,305.3316229667,26.0,30.1116095167
+2024-09-27 18:49:00,0.0539313833,299.1156677333,26.0,30.1030656333
+2024-09-27 18:50:00,0.05278745,292.6017318833,26.0,30.1126775833
+2024-09-27 18:51:00,0.0517203833,287.5264332333,26.0,30.1174833833
+2024-09-27 18:52:00,0.05147065,286.356933,26.0,30.1201537
+2024-09-27 18:53:00,0.0516052,286.7510385333,26.0,30.1206873833
+2024-09-27 18:54:00,0.0515827,286.24799735,26.0,30.1094735333
+2024-09-27 18:55:00,0.0516276,286.2768295667,26.0,30.10733775
+2024-09-27 18:56:00,0.0516789333,286.2095438,26.0,30.1270956833
+2024-09-27 18:57:00,0.0517653,286.3985889167,26.0,30.1254936833
+2024-09-27 18:58:00,0.0516403,286.2960554833,26.0,30.1222896333
+2024-09-27 18:59:00,0.05180375,286.6549157167,26.0,30.11534755
+2024-09-27 19:00:00,0.05203135,286.7926907833,26.0,30.1148133167
+2024-09-27 19:01:00,0.0574207333,309.3912169833,26.0,30.1148135667
+2024-09-27 19:02:00,0.07107985,376.4370412,26.0,30.1201534
+2024-09-27 19:03:00,0.07069855,376.2287719,26.0,30.1292319667
+2024-09-27 19:04:00,0.0533130167,292.9253474167,26.0,30.1260276667
+2024-09-27 19:05:00,0.0541877,297.2060242,26.0,30.1169496
+2024-09-27 19:06:00,0.0549470667,301.0765755667,26.0,30.1153476
+2024-09-27 19:07:00,0.0692342,362.35501565,26.0,30.1217557
+2024-09-27 19:08:00,0.0775392667,401.6917658667,26.0,30.1313678
+2024-09-27 19:09:00,0.0698878333,371.34251815,26.0,30.1238916333
+2024-09-27 19:10:00,0.0552387,301.52514995,26.0,30.1190858333
+2024-09-27 19:11:00,0.0570489,306.99135075,26.0,30.1212213167
+2024-09-27 19:12:00,0.0730151333,380.1185497833,26.0,30.1238916833
+2024-09-27 19:13:00,0.0732266833,381.6405003167,26.0,30.11855165
+2024-09-27 19:14:00,0.0622397167,335.12976435,26.0,30.1228237667
+2024-09-27 19:15:00,0.05544385,300.6600428667,26.0,30.1169494
+2024-09-27 19:16:00,0.0555976,300.9323897,26.0,30.1180175667
+2024-09-27 19:17:00,0.0555783667,300.89714555,26.0,30.1222896833
+2024-09-27 19:18:00,0.0546459,299.08041995,26.75,30.1196192667
+2024-09-27 19:19:00,-0.0747933667,19.0483816667,43.85,30.12335755
+2024-09-27 19:20:00,-0.1100545,0.1505927,44.65,30.1244256667
+2024-09-27 19:21:00,0.0521435833,289.1413011,25.9166666667,30.1335041333
+2024-09-27 19:22:00,0.05470685,311.0829838667,26.0,30.1222897833
+2024-09-27 19:23:00,0.0548060333,310.6279976833,26.0,30.12656165
+2024-09-27 19:24:00,0.0548924667,311.2367787167,26.0,30.1222897833
+2024-09-27 19:25:00,0.0547868833,310.0256362,26.0,30.1233574
+2024-09-27 19:26:00,0.05500165,310.75937035,26.0,30.1302997333
+2024-09-27 19:27:00,0.0550913167,310.5382877667,26.0,30.1270959333
+2024-09-27 19:28:00,0.0552163167,310.4293507167,26.0,30.1286976333
+2024-09-27 19:29:00,0.05524195,310.4421645,26.0,30.12816375
+2024-09-27 19:30:00,0.0552997333,310.6504271,26.0,30.1313679
+2024-09-27 19:31:00,0.0552996833,310.2242873,26.0,30.12495985
+2024-09-27 19:32:00,0.0553829167,309.9647556167,26.0,30.1302998333
+2024-09-27 19:33:00,0.0554694667,310.3332253833,26.0,30.1276296667
+2024-09-27 19:34:00,0.0554725333,309.7789193333,26.0,30.1249597
+2024-09-27 19:35:00,0.05540535,309.2182010167,26.0,30.1270955333
+2024-09-27 19:36:00,0.0556007167,309.8814482,26.0,30.1297657
+2024-09-27 19:37:00,0.0556263167,309.9230995833,26.0,30.1302998333
+2024-09-27 19:38:00,0.0557481667,310.2403044,26.0,30.1313679
+2024-09-27 19:39:00,0.05586025,309.9615486833,26.0,30.1244256167
+2024-09-27 19:40:00,0.0558667333,310.2595305,26.0,30.1265616
+2024-09-27 19:41:00,0.05593405,310.33322745,26.0,30.1340379667
+2024-09-27 19:42:00,0.0559627833,310.5222655,26.0,30.13083375
+2024-09-27 19:43:00,0.0560686167,310.3716739667,26.0,30.1319019167
+2024-09-27 19:44:00,0.0561648,309.9647561333,26.0,30.1324358667
+2024-09-27 19:45:00,0.0560590167,309.4841392833,26.0,30.1302997333
+2024-09-27 19:46:00,0.0562736833,310.3140029,26.0,30.1286978333
+2024-09-27 19:47:00,0.05611995,309.51938765,26.0,30.1228237667
+2024-09-27 19:48:00,0.0560334333,308.561362,26.0,30.1302997333
+2024-09-27 19:49:00,0.05627035,310.0448581667,26.0,30.1270956833
+2024-09-27 19:50:00,0.0563409,309.4456912167,26.0,30.1324358667
+2024-09-27 19:51:00,0.056469,309.987184,26.0,30.1313678
+2024-09-27 19:52:00,0.0565074167,309.5225883833,26.0,30.1324361167
+2024-09-27 19:53:00,0.0565747833,310.2467151667,26.0,30.1297657
+2024-09-27 19:54:00,0.0565459333,309.743672,26.0,30.1276295667
+2024-09-27 19:55:00,0.0567798667,309.9102847667,26.0,30.1335039333
+2024-09-27 19:56:00,0.0567607333,309.8333855333,26.0,30.1324358667
+2024-09-27 19:57:00,0.0569145167,309.8910607333,26.0,30.1302998833
+2024-09-27 19:58:00,0.05678955,309.4168573167,26.0,30.13296995
+2024-09-27 19:59:00,0.0560108833,305.5430938833,26.0,30.1324359667
+2024-09-27 20:00:00,0.054594678,298.7438850339,26.0,30.1315851356
+2024-09-27 20:01:00,0.0533835833,292.0602381833,26.0,30.1313678
+2024-09-27 20:02:00,0.0525023167,288.4940724167,26.0,30.1313679167
+2024-09-27 20:03:00,0.05244145,287.65139295,26.0,30.1367081333
+2024-09-27 20:04:00,0.0523485,287.3982680333,26.0,30.1335039333
+2024-09-27 20:05:00,0.0525182333,287.8724770833,26.0,30.1329699
+2024-09-27 20:06:00,0.0525407,287.3758406,26.0,30.1351061333
+2024-09-27 20:07:00,0.05259515,287.8308257,26.0,30.1340379667
+2024-09-27 20:08:00,0.0525791667,287.8340295333,26.0,30.1399122833
+2024-09-27 20:09:00,0.0527682333,287.4206974,26.0,30.1302997333
+2024-09-27 20:10:00,0.05270095,287.8756809167,26.0,30.1335039333
+2024-09-27 20:11:00,0.0527778,287.5776954833,26.0,30.135106
+2024-09-27 20:12:00,0.0529093667,287.7603367167,26.0,30.13617405
+2024-09-27 20:13:00,0.0528675667,287.5905165333,26.0,30.1308338667
+2024-09-27 20:14:00,0.0646525333,339.9423294333,26.0,30.1308337667
+2024-09-27 20:15:00,0.0724224,378.30823525,26.0,30.1329699
+2024-09-27 20:16:00,0.0654246,348.0999679333,26.0,30.1356400167
+2024-09-27 20:17:00,0.0549632,297.08747365,26.0,30.1335039333
+2024-09-27 20:18:00,0.0559660167,302.0634363,26.0,30.1260274667
+2024-09-27 20:19:00,0.0659437167,344.0756200667,26.0,30.1276298167
+2024-09-27 20:20:00,0.0787472667,402.6081386333,26.0,30.1340379667
+2024-09-27 20:21:00,0.0754565667,387.96219075,26.0,30.1260274667
+2024-09-27 20:22:00,0.0587857167,316.0397344833,26.0,30.1281637
+2024-09-27 20:23:00,0.0575584167,306.9625159167,26.0,30.1319018333
+2024-09-27 20:24:00,0.0738802167,379.0131359167,26.0,30.1281636
+2024-09-27 20:25:00,0.0742294333,381.4963150667,26.0,30.1254938833
+2024-09-27 20:26:00,0.0647933833,342.0538294833,26.0,30.12495955
+2024-09-27 20:27:00,0.0560236833,300.2787532,26.0,30.12335745
+2024-09-27 20:28:00,0.05639215,301.6917621833,26.0,30.1351060333
+2024-09-27 20:29:00,0.0563024833,301.3649432,26.0,30.12175535
+2024-09-27 20:30:00,0.0485101,267.3790405167,28.7166666667,30.1244256167
+2024-09-27 20:31:00,-0.0893206667,0.4101249,44.9666666667,30.12495955
+2024-09-27 20:32:00,-0.0771772333,15.7577698333,41.3333333333,30.1297657
+2024-09-27 20:33:00,0.058808,326.10701295,26.0,30.1228236667
+2024-09-27 20:34:00,0.0555110333,311.1726953333,26.0,30.1313678
+2024-09-27 20:35:00,0.0555142,310.7433491167,26.0,30.12495965
+2024-09-27 20:36:00,0.0557033,310.6344089667,26.0,30.1335039333
+2024-09-27 20:37:00,0.0557257167,310.1473865167,26.0,30.1324358667
+2024-09-27 20:38:00,0.05581865,310.2306970333,26.0,30.1238913333
+2024-09-27 20:39:00,0.0559405,310.0352477,26.0,30.1372421167
+2024-09-27 20:40:00,0.0559788667,310.3364307667,26.0,30.12976595
+2024-09-27 20:41:00,0.0560527,310.2467151667,26.0,30.1260277667
+2024-09-27 20:42:00,0.0560847667,309.82056865,26.0,30.1292316667
+2024-09-27 20:43:00,0.0562320333,310.2338977667,26.0,30.1260277167
+2024-09-27 20:44:00,0.0561167167,309.7372607167,26.0,30.1254935833
+2024-09-27 20:45:00,0.0562,309.8397973333,26.0,30.1254935833
+2024-09-27 20:46:00,0.0563121,310.2306934167,26.0,30.1254934333
+2024-09-27 20:47:00,0.0564625833,309.8750400167,26.0,30.1244256667
+2024-09-27 20:48:00,0.0564819,310.5543048667,26.0,30.134572
+2024-09-27 20:49:00,0.05646905,309.9743660833,26.0,30.1319019833
+2024-09-27 20:50:00,0.05670305,310.3748772833,26.0,30.1244254667
+2024-09-27 20:51:00,0.05667745,310.1602034,26.0,30.1260277667
+2024-09-27 20:52:00,0.0566005,309.1284849667,26.0,30.1260276667
+2024-09-27 20:53:00,0.0565461,309.0836272333,26.0,30.1164157167
+2024-09-27 20:54:00,0.0568313167,309.4713229167,26.0,30.1286976333
+2024-09-27 20:55:00,0.0568984667,310.1345717,26.0,30.1302999833
+2024-09-27 20:56:00,0.0569465167,310.1345711833,26.0,30.12656165
+2024-09-27 20:57:00,0.0568312167,309.6090991167,26.0,30.1302998333
+2024-09-27 20:58:00,0.0570553333,309.5610416167,26.0,30.1270956333
+2024-09-27 20:59:00,0.0570234167,309.7596958167,26.0,30.1297658
+2024-09-27 21:00:00,0.0592053667,317.3149631,26.0,30.1142799333
+2024-09-27 21:01:00,0.0652067,347.9782146,26.0,30.1270958333
+2024-09-27 21:02:00,0.05780525,312.64018015,26.0,30.1292317667
+2024-09-27 21:03:00,0.0580359167,312.9798184833,26.0,30.1212214667
+2024-09-27 21:04:00,0.0580616667,312.71707935,26.0,30.1217555
+2024-09-27 21:05:00,0.05816725,313.2681850833,26.0,30.1217553
+2024-09-27 21:06:00,0.05820255,312.71067165,26.0,30.1217556
+2024-09-27 21:07:00,0.0581609333,313.05671445,26.0,30.1222895333
+2024-09-27 21:08:00,0.0582251,312.6017331333,26.0,30.1297658
+2024-09-27 21:09:00,0.0582859333,313.0791403333,26.0,30.1190853833
+2024-09-27 21:10:00,0.05835315,312.6241573833,26.0,30.1190857333
+2024-09-27 21:11:00,0.05832445,312.43511675,26.0,30.11855145
+2024-09-27 21:12:00,0.0585294,312.8260225667,26.0,30.12175565
+2024-09-27 21:13:00,0.0585614,313.1912849,26.0,30.1238917833
+2024-09-27 21:14:00,0.0585965833,312.7715501833,26.0,30.1206874833
+2024-09-27 21:15:00,0.0586863667,312.7619370833,26.0,30.1254940333
+2024-09-27 21:16:00,0.0587728667,312.50560775,26.0,30.1185517
+2024-09-27 21:17:00,0.0588529833,313.0374920333,26.0,30.1228235167
+2024-09-27 21:18:00,0.0588402333,312.7170798667,26.0,30.1164156167
+2024-09-27 21:19:00,0.05885615,312.9157345,26.0,30.1196194167
+2024-09-27 21:20:00,0.05898125,312.9958366,26.0,30.10893935
+2024-09-27 21:21:00,0.0590582833,312.8484515167,26.0,30.11534775
+2024-09-27 21:22:00,0.0592184,313.6718990167,26.0,30.1201535
+2024-09-27 21:23:00,0.0588273833,311.4931101833,26.0,30.1185514
+2024-09-27 21:24:00,0.0501473,271.65011115,26.0,30.12175565
+2024-09-27 21:25:00,0.0446523167,244.6875978167,26.0,30.1281641
+2024-09-27 21:26:00,0.0499326333,268.6094167,26.0,30.1292318667
+2024-09-27 21:27:00,0.0587824833,312.1723801833,26.0,30.1169492
+2024-09-27 21:28:00,0.0564820333,302.2685031667,26.0,30.1185516
+2024-09-27 21:29:00,0.04404995,241.4995237667,26.0,30.12175555
+2024-09-27 21:30:00,0.05091,273.1464284167,26.0,30.1238914333
+2024-09-27 21:31:00,0.0537233,288.0871540667,26.0,30.12015355
+2024-09-27 21:32:00,0.069843,355.82185155,26.0,30.1132115667
+2024-09-27 21:33:00,0.07273005,370.9932673,26.0,30.1228239167
+2024-09-27 21:34:00,0.0615796333,323.9859014167,26.0,30.1094736833
+2024-09-27 21:35:00,0.0534313833,285.22909195,26.0,30.11214335
+2024-09-27 21:36:00,0.0532776,284.4056447833,26.0,30.1068033667
+2024-09-27 21:37:00,0.0532872833,284.7388662167,26.0,30.1068033667
+2024-09-27 21:38:00,0.05336425,284.4697239667,26.0,30.1121432
+2024-09-27 21:39:00,0.0532489667,283.2489564167,26.0,30.1110755833
+2024-09-27 21:40:00,0.0535052167,284.3735988167,26.0,30.1035995667
+2024-09-27 21:41:00,0.0536206667,285.0560715333,26.0,30.1073376
+2024-09-27 21:42:00,0.03040045,189.0515868333,33.1166666667,30.1121436
+2024-09-27 21:43:00,-0.12824735,0.1345722,45.0,30.1105416
+2024-09-27 21:44:00,-0.0453379667,87.1066977833,37.3333333333,30.1094734333
+2024-09-27 21:45:00,0.0533643667,301.3553379,26.0,30.10573555
+2024-09-27 21:46:00,0.0524958833,293.85133025,26.0,30.11214365
+2024-09-27 21:47:00,0.0526208667,293.7359850167,26.0,30.1132112667
+2024-09-27 21:48:00,0.0525791667,293.8000694333,26.0,30.1094732833
+2024-09-27 21:49:00,0.0526622667,293.5405337833,26.0,30.1089393
+2024-09-27 21:50:00,0.0527105,293.0022434333,26.0,30.1030654833
+2024-09-27 21:51:00,0.05279705,293.5116996333,26.0,30.11054135
+2024-09-27 21:52:00,0.0529606,293.7455955167,26.0,30.1068033667
+2024-09-27 21:53:00,0.0528805333,292.8708764333,26.0,30.1052014167
+2024-09-27 21:54:00,0.0529766,293.0406964333,26.0,30.10733745
+2024-09-27 21:55:00,0.0531079,293.3194517,26.0,30.10413345
+2024-09-27 21:56:00,0.0530503333,292.5184229167,26.0,30.1100074667
+2024-09-27 21:57:00,0.0526689333,291.5700085,26.0,30.1116099167
+2024-09-27 21:58:00,0.0529829333,291.9545005667,26.0,30.10733735
+2024-09-27 21:59:00,0.05897145,315.8378723667,26.0,30.1062694833
+2024-09-27 22:00:00,0.0748862667,390.7497546833,26.0,30.1089394
+2024-09-27 22:01:00,0.0750590833,391.0445384167,26.0,30.10413345
+2024-09-27 22:02:00,0.0606344333,327.3309819333,26.0,30.1052014167
+2024-09-27 22:03:00,0.05596605,306.6260782333,26.0,30.1100075667
+2024-09-27 22:04:00,0.05337375,293.4892698333,26.0,30.1019976167
+2024-09-27 22:05:00,0.0531431833,292.6914489333,26.0,30.0998615833
+2024-09-27 22:06:00,0.0533834667,292.6850396833,26.0,30.10092955
+2024-09-27 22:07:00,0.05346025,293.1111845333,26.0,30.10413345
+2024-09-27 22:08:00,0.05338985,292.3710343167,26.0,30.0982596333
+2024-09-27 22:09:00,0.0536495333,293.78724955,26.0,30.0966574833
+2024-09-27 22:10:00,0.0534732,293.0054518167,26.0,30.0977256
+2024-09-27 22:11:00,0.0537136333,292.9958377333,26.0,30.0971914167
+2024-09-27 22:12:00,0.0537424667,292.5825088167,26.0,30.0987936167
+2024-09-27 22:13:00,0.05367835,292.65620425,26.0,30.0971916667
+2024-09-27 22:14:00,0.0540050667,293.9570692167,26.0,30.0987936167
+2024-09-27 22:15:00,0.0539378,293.2970243833,26.0,30.10092945
+2024-09-27 22:16:00,0.0540082833,293.4091638333,26.0,30.0971915167
+2024-09-27 22:17:00,0.0541558,293.3707177,26.0,30.1014635333
+2024-09-27 22:18:00,0.0541974833,293.7071466333,26.0,30.0923858167
+2024-09-27 22:19:00,0.0541204333,293.0535078333,26.0,30.0971914167
+2024-09-27 22:20:00,0.0540786833,293.1464288167,26.0,30.0987935167
+2024-09-27 22:21:00,0.0540916167,293.4636381667,26.0,30.0987936167
+2024-09-27 22:22:00,0.0543030167,293.3162509667,26.0,30.0923855667
+2024-09-27 22:23:00,0.05411395,293.0791411833,26.0,30.0929196
+2024-09-27 22:24:00,0.0545979667,294.5498253667,26.0,30.0891815667
+2024-09-27 22:25:00,0.0544729667,293.3066357833,26.0,30.09291935
+2024-09-27 22:26:00,0.0545658667,294.2454379167,26.0,30.0886475833
+2024-09-27 22:27:00,0.05452095,293.0086522,26.0,30.09131755
+2024-09-27 22:28:00,0.05438005,292.8164076833,26.0,30.09131735
+2024-09-27 22:29:00,0.0544280333,293.0855512167,26.0,30.0881135
+2024-09-27 22:30:00,0.0545851333,293.2361428667,26.0,30.0918515333
+2024-09-27 22:31:00,0.05496635,294.2646595333,26.0,30.0939875167
+2024-09-27 22:32:00,0.0546973,293.1752714667,26.0,30.08971545
+2024-09-27 22:33:00,0.0546522667,292.6433837333,26.0,30.09932735
+2024-09-27 22:34:00,0.0547036,293.3418805333,26.0,30.0918513333
+2024-09-27 22:35:00,0.05481895,293.3162503833,26.0,30.0918512833
+2024-09-27 22:36:00,0.0549247333,293.5853915,26.0,30.0961236
+2024-09-27 22:37:00,0.0549022333,293.3354674667,26.0,30.0843751167
+2024-09-27 22:38:00,0.0546716,292.62416175,26.0,30.0811715667
+2024-09-27 22:39:00,0.05491825,293.09837125,26.0,30.0875794167
+2024-09-27 22:40:00,0.0550784,293.5950035167,26.0,30.0897154
+2024-09-27 22:41:00,0.0550785,293.0054492833,26.0,30.0955894167
+2024-09-27 22:42:00,0.0549502667,293.1880810667,26.0,30.0907829167
+2024-09-27 22:43:00,0.0550656167,292.9349588667,26.0,30.0971913667
+2024-09-27 22:44:00,0.05506895,292.74591865,26.0,30.0849093
+2024-09-27 22:45:00,0.05509445,292.6786350333,26.0,30.0891815167
+2024-09-27 22:46:00,0.05515235,293.0054467667,26.0,30.07689915
+2024-09-27 22:47:00,0.0553093667,292.8196114833,26.0,30.081705
+2024-09-27 22:48:00,0.0550208667,292.4319127167,26.0,30.0795688667
+2024-09-27 22:49:00,0.05500165,291.7622554667,26.0,30.0870454833
+2024-09-27 22:50:00,0.0554981833,293.2810016167,26.0,30.0790353333
+2024-09-27 22:51:00,0.0508041,272.0025624333,26.0,30.0790348833
+2024-09-27 22:52:00,0.0511245333,272.50560345,26.0,30.0822393333
+2024-09-27 22:53:00,0.0512431833,272.3133569333,26.0,30.0801033
+2024-09-27 22:54:00,0.00038765,123.6014089167,36.5833333333,30.0870452333
+2024-09-27 22:55:00,-0.1121082333,0.1089394,45.0,30.0795691167
+2024-09-27 22:56:00,-0.0336815167,0.1441845,18.3666666667,30.0817055
+2024-09-27 22:57:00,0.0006887,0.128164,0.0,30.0785012
+2024-09-27 22:58:00,0.0010602333,0.1313681,0.0,30.0817052
+2024-09-27 22:59:00,0.0009898667,0.1313681,0.0,30.0822390833
+2024-09-27 23:00:00,0.00081055,0.1249599,0.0,30.0833071
+2024-09-27 23:01:00,0.0008522,0.1345722,0.0,30.08010305
+2024-09-27 23:02:00,0.00098985,0.1441845,0.0,30.07689915
+2024-09-27 23:03:00,0.00093225,0.1217558,0.0,30.0774330333
+2024-09-27 23:04:00,0.000849,0.1441845,0.0,30.0843751167
+2024-09-27 23:05:00,0.0072764167,32.3229734,10.8666666667,30.0699569667
+2024-09-27 23:06:00,0.0406920333,236.4017961667,26.0,30.0833074
+2024-09-27 23:07:00,0.0406599667,235.02402935,26.0,30.0865111
+2024-09-27 23:08:00,0.0405830333,234.1300835333,26.0,30.0806371333
+2024-09-27 23:09:00,0.04067925,233.0567127833,26.0,30.0833073
+2024-09-27 23:10:00,0.0407497167,233.4636321333,26.0,30.0817053
+2024-09-27 23:11:00,0.0491508,269.3880143,26.0,30.0758308333
+2024-09-27 23:12:00,0.0698623,369.6859985,26.0,30.0672868
+2024-09-27 23:13:00,0.06842055,363.56296365,26.0,30.0795692167
+2024-09-27 23:14:00,0.0527619667,290.6600434667,26.0,30.0774326333
+2024-09-27 23:15:00,0.0498975,277.12591965,26.0,30.0763651167
+2024-09-27 23:16:00,0.0499583167,276.4049975,26.0,30.08651135
+2024-09-27 23:17:00,0.0499935,277.0265936667,26.0,30.0715590167
+2024-09-27 23:18:00,0.0498941667,276.5427736667,26.0,30.0694228333
+2024-09-27 23:19:00,0.0499871333,276.1807106167,26.0,30.0683548167
+2024-09-27 23:20:00,0.0500672,276.1166267167,26.0,30.0699569667
+2024-09-27 23:21:00,0.05015685,275.9179756167,26.0,30.0731610667
+2024-09-27 23:22:00,0.0502722167,276.4915107333,26.0,30.0694228333
+2024-09-27 23:23:00,0.0503011,276.3889767833,26.0,30.0736952
+2024-09-27 23:24:00,0.0501920833,276.3184863,26.0,30.073695
+2024-09-27 23:25:00,0.0503267,276.1038119,26.0,30.070491
+2024-09-27 23:26:00,0.0502242,275.4469733167,26.0,30.0694229333
+2024-09-27 23:27:00,0.05090025,277.5168158333,26.0,30.0688889
+2024-09-27 23:28:00,0.070468,366.4786920167,26.0,30.06087855
+2024-09-27 23:29:00,0.0711887667,371.5379659167,26.0,30.0646166333
+2024-09-27 23:30:00,0.0496731167,275.1874379,26.0,30.0640827167
+2024-09-27 23:31:00,0.0481544167,262.99583355,26.0,30.0640827
+2024-09-27 23:32:00,0.0507081167,276.4850989833,26.0,30.06301465
+2024-09-27 23:33:00,0.0507561167,276.5043210333,26.0,30.0576742833
+2024-09-27 23:34:00,0.0506472,276.0813809333,26.0,30.0624806333
+2024-09-27 23:35:00,0.0508778667,276.7766731333,26.0,30.0635486833
+2024-09-27 23:36:00,0.0509771833,276.4690776833,26.0,30.0608785667
+2024-09-27 23:37:00,0.0509484167,276.7862815833,26.0,30.0710250667
+2024-09-27 23:38:00,0.0510123833,276.8375485167,26.0,30.0630145333
+2024-09-27 23:39:00,0.0510379833,276.84716005,26.0,30.06034445
+2024-09-27 23:40:00,0.0511116833,276.5331590833,26.0,30.0571403167
+2024-09-27 23:41:00,0.0510380667,276.1006054833,26.0,30.0598104667
+2024-09-27 23:42:00,0.0512709153,276.384684678,26.0,30.0555563898
+2024-09-27 23:43:00,0.0512847833,277.1867987,26.0,30.0539362833
+2024-09-27 23:44:00,0.0512816167,276.6453009833,26.0,30.0571403833
+2024-09-27 23:45:00,0.0514129667,276.5748151333,26.0,30.05233405
+2024-09-27 23:46:00,0.0513874,276.4754865333,26.0,30.048596
+2024-09-27 23:47:00,0.05147385,276.5363680167,26.0,30.0534021667
+2024-09-27 23:48:00,0.0514353167,276.5780188667,26.0,30.05874245
+2024-09-27 23:49:00,0.0515635667,276.7606481833,26.0,30.0582083
+2024-09-27 23:50:00,0.0515027333,276.1710980333,26.0,30.04912995
+2024-09-27 23:51:00,0.0509067333,273.66869715,26.0,30.04966405
+2024-09-27 23:52:00,0.0502369833,270.45498305,26.0,30.0416536333
+2024-09-27 23:53:00,0.0492629667,265.3636688,26.0,30.0464598667
+2024-09-27 23:54:00,0.0482315,260.3043901,26.0,30.0512660833
+2024-09-27 23:55:00,0.0470875667,254.57545535,26.0,30.0528681833
+2024-09-27 23:56:00,0.04595005,249.65716195,26.0,30.0507321
+2024-09-27 23:57:00,0.0454436,247.4335165167,26.0,30.0485960333
+2024-09-27 23:58:00,0.04546295,246.8856169,26.0,30.0550042833
+2024-09-27 23:59:00,0.0456231667,247.0233928167,26.0,30.0485959833
+2024-09-28 00:00:00,0.0456969167,247.4911902333,26.0,30.0550041833
+2024-09-28 00:01:00,0.0451074333,244.7677049,26.0,30.0544702833
+2024-09-28 00:02:00,0.0352451167,196.02050805,26.0,30.0534022
+2024-09-28 00:03:00,0.0470074333,252.7939764167,26.0,30.0587424167
+2024-09-28 00:04:00,0.0470233167,253.18487565,26.0,30.04218765
+2024-09-28 00:05:00,0.0470522167,252.6305679333,26.0,30.0480618667
+2024-09-28 00:06:00,0.04696255,252.2588932,26.0,30.0400515833
+2024-09-28 00:07:00,0.04719655,253.2137124333,26.0,30.0416536667
+2024-09-28 00:08:00,0.0471901667,253.2105084667,26.0,30.0464598167
+2024-09-28 00:09:00,0.0472157167,252.8644665167,26.0,30.0395175333
+2024-09-28 00:10:00,0.04723495,253.0823447,26.0,30.0368474
+2024-09-28 00:11:00,0.0473150833,253.46683575,26.0,30.0384495667
+2024-09-28 00:12:00,0.0473599333,253.0246710833,26.0,30.0395175667
+2024-09-28 00:13:00,0.0472638167,253.2297329333,26.0,30.0315072333
+2024-09-28 00:14:00,0.0474209,253.5853869833,26.0,30.0405855667
+2024-09-28 00:15:00,0.0199488667,147.3726368167,33.75,30.0347113333
+2024-09-28 00:16:00,-0.1182121667,0.160205,45.0,30.03684745
+2024-09-28 00:17:00,-0.05457225,0.128164,25.0166666667,30.0432557333
+2024-09-28 00:18:00,0.0010091833,0.1345722,0.0,30.0437897667
+2024-09-28 00:19:00,0.0009994833,0.1345722,0.0,30.0331093
+2024-09-28 00:20:00,0.0009546833,0.128164,0.0,30.0315071833
+2024-09-28 00:21:00,0.00100265,0.1570009,0.0,30.0331093
+2024-09-28 00:22:00,0.0009418167,0.1121435,0.0,30.0315071833
+2024-09-28 00:23:00,0.00094185,0.1473886,0.0,30.0368475
+2024-09-28 00:24:00,0.0010379,0.1473886,0.0,30.0293711167
+2024-09-28 00:25:00,0.001041,0.1377763,0.0,30.0331092167
+2024-09-28 00:26:00,0.0009418,0.1345722,0.0,30.0331093333
+2024-09-28 00:27:00,0.0009194,0.1377763,0.0,30.0250989167
+2024-09-28 00:28:00,0.00093865,0.160205,0.0,30.0325752
+2024-09-28 00:29:00,0.0009194833,0.1666132,0.0,30.0352454
+2024-09-28 00:30:00,0.00095145,0.1409804,0.0,30.0331093
+2024-09-28 00:31:00,0.00101225,0.1537968,0.0,30.0250989
+2024-09-28 00:32:00,0.0009802333,0.1185517,0.0,30.0160204667
+2024-09-28 00:33:00,0.0009578333,0.1698173,0.0,30.0379154833
+2024-09-28 00:34:00,0.0009674,0.1409804,0.0,30.0325753
+2024-09-28 00:35:00,0.0009546333,0.192246,0.0,30.0373814667
+2024-09-28 00:36:00,0.0008906,0.160205,0.0,30.0363134333
+2024-09-28 00:37:00,0.0010667333,0.1473886,0.0,30.0363134333
+2024-09-28 00:38:00,0.0009834,0.1570009,0.0,30.02456485
+2024-09-28 00:39:00,0.00101225,0.1570009,0.0,30.03204125
+2024-09-28 00:40:00,0.0305574333,162.7363025,22.5,30.027235
+2024-09-28 00:41:00,0.0581416167,319.3880174,26.0,30.0283031
+2024-09-28 00:42:00,0.0583949,318.89779215,26.0,30.02403085
+2024-09-28 00:43:00,0.0585806333,318.51330115,26.0,30.0229628333
+2024-09-28 00:44:00,0.0586574333,318.5100968,26.0,30.02776905
+2024-09-28 00:45:00,0.0587248,318.5004847833,26.0,30.0229628667
+2024-09-28 00:46:00,0.0588401667,318.4876668667,26.0,30.02136075
+2024-09-28 00:47:00,0.0589043167,318.3402781333,26.0,30.0299051833
+2024-09-28 00:48:00,0.0589395667,317.84364315,26.0,30.0256329167
+2024-09-28 00:49:00,0.0590645167,318.3210561667,26.0,30.0272349833
+2024-09-28 00:50:00,0.0590805167,317.6065419167,26.0,30.02189475
+2024-09-28 00:51:00,0.05917355,317.7411086,26.0,30.0240308833
+2024-09-28 00:52:00,0.05927915,317.8340290667,26.0,30.0106802167
+2024-09-28 00:53:00,0.0592343,317.8276239833,26.0,30.0208267167
+2024-09-28 00:54:00,0.0594072333,317.66100915,26.0,30.0101462
+2024-09-28 00:55:00,0.05944575,317.5200296333,26.0,30.0229629
+2024-09-28 00:56:00,0.0595001667,317.5264336833,26.0,30.0250989333
+2024-09-28 00:57:00,0.0595450167,317.7379068333,26.0,30.0256328
+2024-09-28 00:58:00,0.05971495,317.64178305,26.0,30.02136075
+2024-09-28 00:59:00,0.05963465,317.4879923333,26.0,30.0213608667
+2024-09-28 01:00:00,0.0597983167,317.2861275833,26.0,30.0160205667
+2024-09-28 01:01:00,0.059731,317.2444731,26.0,30.0272350667
+2024-09-28 01:02:00,0.0599809167,317.4527351833,26.0,30.0085441
+2024-09-28 01:03:00,0.0494487667,270.0833104833,26.0,30.0213607
+2024-09-28 01:04:00,0.0406631167,226.1582836833,26.0,30.03524535
+2024-09-28 01:05:00,0.04100275,228.57417495,26.0,30.03150725
+2024-09-28 01:06:00,0.04087135,227.2989450667,26.0,30.03364335
+2024-09-28 01:07:00,0.0405415,226.3056748333,26.0,30.0379154833
+2024-09-28 01:08:00,0.0406888333,226.7190004667,26.0,30.0315072
+2024-09-28 01:09:00,0.0455237667,248.1928878,26.0,30.0299051667
+2024-09-28 01:10:00,0.05107325,275.3412401333,26.0,30.0240308
+2024-09-28 01:11:00,0.0512784333,275.98205435,26.0,30.0154864333
+2024-09-28 01:12:00,0.0511052167,274.97917805,26.0,30.0170884667
+2024-09-28 01:13:00,0.0511821,275.4629915,26.0,30.01869055
+2024-09-28 01:14:00,0.0513809833,275.5943594833,26.0,30.02616695
+2024-09-28 01:15:00,0.0514513667,275.69689045,26.0,30.0170885333
+2024-09-28 01:16:00,0.0513649,275.4373603167,26.0,30.0208267
+2024-09-28 01:17:00,0.05133615,275.0048071667,26.0,30.0213606833
+2024-09-28 01:18:00,0.0515506667,275.4405657,26.0,30.0176225167
+2024-09-28 01:19:00,0.0516019833,275.49503345,26.0,30.0133503167
+2024-09-28 01:20:00,0.0515893,275.6712572,26.0,30.0192247167
+2024-09-28 01:21:00,0.0517429833,275.5366874167,26.0,30.0208266667
+2024-09-28 01:22:00,0.05173965,275.11054245,26.0,30.0090781333
+2024-09-28 01:23:00,0.0517844833,275.15540025,26.0,30.006942
+2024-09-28 01:24:00,0.0518934833,276.00448325,26.0,30.01335035
+2024-09-28 01:25:00,0.0517620667,274.9118867,26.0,30.0202926667
+2024-09-28 01:26:00,0.0517685667,275.4565833167,26.0,30.02189465
+2024-09-28 01:27:00,0.0515283167,273.6046153667,26.0,30.0090781
+2024-09-28 01:28:00,0.0507303333,270.4517786667,26.0,30.0122823333
+2024-09-28 01:29:00,0.049939,265.9307937,26.0,30.0096121667
+2024-09-28 01:30:00,0.0486831167,260.26273655,26.0,30.0117483333
+2024-09-28 01:31:00,0.04753295,254.78692625,26.0,30.0106802333
+2024-09-28 01:32:00,0.0463825667,249.2502382,26.0,30.0128163167
+2024-09-28 01:33:00,0.0456424667,245.8442835167,26.0,30.0128163167
+2024-09-28 01:34:00,0.0456585167,245.5366876833,26.0,30.0128163333
+2024-09-28 01:35:00,0.04565845,245.4245450833,26.0,30.0101462
+2024-09-28 01:36:00,0.04572905,245.44376855,26.0,30.01815665
+2024-09-28 01:37:00,0.0458475667,245.8442848667,26.0,30.0181566167
+2024-09-28 01:38:00,0.0458091333,246.0429388667,26.0,30.0090781667
+2024-09-28 01:39:00,0.0457675667,245.6872800833,26.0,30.0133503167
+2024-09-28 01:40:00,0.0457835333,245.2226847667,26.0,30.0165544833
+2024-09-28 01:41:00,0.0458797333,245.9692432,26.0,30.0101462167
+2024-09-28 01:42:00,0.045838,245.2835622667,26.0,30.0128163
+2024-09-28 01:43:00,0.0458123833,245.1105410333,26.0,30.0117483333
+2024-09-28 01:44:00,0.0458892833,245.3925031167,26.0,30.0058739667
+2024-09-28 01:45:00,0.0460654,245.3027863333,26.0,30.0042718667
+2024-09-28 01:46:00,0.045963,245.6007711667,26.0,30.0128163667
+2024-09-28 01:47:00,0.0459661667,245.0720899,26.0,30.0085441
+2024-09-28 01:48:00,0.046075,245.5430958667,26.0,30.0085440833
+2024-09-28 01:49:00,0.0450176667,242.5248306667,26.4166666667,30.0064079667
+2024-09-28 01:50:00,-0.0645915,20.5446976,43.0166666667,30.0069420333
+2024-09-28 01:51:00,-0.1123134,0.1377763,45.2666666667,30.0117482833
+2024-09-28 01:52:00,5.755e-05,0.1025312,2.4,30.0021358333
+2024-09-28 01:53:00,0.0010858667,0.1089394,0.0,30.0106802667
+2024-09-28 01:54:00,0.0010058333,0.1025312,0.0,30.0069419833
+2024-09-28 01:55:00,0.00096745,0.1089394,0.0,30.0069420333
+2024-09-28 01:56:00,0.0009066,0.128164,0.0,30.0074760667
+2024-09-28 01:57:00,0.0009642333,0.1025312,0.0,30.0048059
+2024-09-28 01:58:00,0.0009066,0.1345722,0.0,30.0026697667
+2024-09-28 01:59:00,0.0009258,0.1730214,0.0,29.9999996
+2024-09-28 02:00:00,0.0009514,0.1345722,0.0,30.0026698333
+2024-09-28 02:01:00,0.000945,0.160205,0.0,30.0010676667
+2024-09-28 02:02:00,0.00089065,0.1730214,0.0,29.9967956333
+2024-09-28 02:03:00,0.0010058833,0.1217558,0.0,30.0053399667
+2024-09-28 02:04:00,0.0009354167,0.1121435,0.0,30.0005337333
+2024-09-28 02:05:00,0.00093545,0.1377763,0.0,30.0042718667
+2024-09-28 02:06:00,0.0008746,0.1666132,0.0,30.0026697667
+2024-09-28 02:07:00,0.0009162,0.1217558,0.0,30.0048059
+2024-09-28 02:08:00,0.000881,0.1313681,0.0,30.0042718667
+2024-09-28 02:09:00,0.0009707,0.1634091,0.0,30.0037377833
+2024-09-28 02:10:00,0.0009546,0.1698173,0.0,29.9978634667
+2024-09-28 02:11:00,0.0009226333,0.1185517,0.0,29.9999997
+2024-09-28 02:12:00,0.0009674333,0.1217558,2.8,29.9994655667
+2024-09-28 02:13:00,0.0360781833,199.9134902,25.7333333333,30.0101462333
+2024-09-28 02:14:00,0.04706505,264.1557222667,26.0,29.9935916
+2024-09-28 02:15:00,0.04939435,273.8096802167,26.0,29.9999997333
+2024-09-28 02:16:00,0.0515058833,282.8035873333,26.0,29.9925234667
+2024-09-28 02:17:00,0.0528452333,288.2281285833,26.0,29.9962614
+2024-09-28 02:18:00,0.0542422167,294.1749473833,26.0,30.0058739667
+2024-09-28 02:19:00,0.0514289667,282.0346058,26.0,29.9962615333
+2024-09-28 02:20:00,0.0539794833,293.83531315,26.0,29.9989317
+2024-09-28 02:21:00,0.0542293667,293.92823055,26.0,29.9941254667
+2024-09-28 02:22:00,0.0542133833,293.7487999667,26.0,29.9978636333
+2024-09-28 02:23:00,0.0543287833,294.5530322667,26.0,29.9877174
+2024-09-28 02:24:00,0.0543895833,293.9730909,26.0,29.9941254333
+2024-09-28 02:25:00,0.0545049167,294.4440906167,26.0,29.9903873667
+2024-09-28 02:26:00,0.0547740667,294.7260521833,26.0,29.9898535
+2024-09-28 02:27:00,0.05447625,293.7103508167,26.0,29.9877174
+2024-09-28 02:28:00,0.05457865,293.4956784333,26.0,29.9909214333
+2024-09-28 02:29:00,0.0546940167,293.7648221,26.0,29.9839791333
+2024-09-28 02:30:00,0.054694,293.3707185667,26.0,29.9861152333
+2024-09-28 02:31:00,0.05483165,293.4187797667,26.0,29.9877173667
+2024-09-28 02:32:00,0.0549021667,293.75200385,26.0,29.9845132667
+2024-09-28 02:33:00,0.0548990167,293.0535108333,26.0,29.9813091667
+2024-09-28 02:34:00,0.05480285,293.4988853333,26.0,29.9861151667
+2024-09-28 02:35:00,0.0549086167,293.1400204833,26.0,29.9882515667
+2024-09-28 02:36:00,0.0550785,294.2005786167,26.0,29.9823771667
+2024-09-28 02:37:00,0.0549663167,293.0919594333,26.0,29.9807752
+2024-09-28 02:38:00,0.0550592833,292.9221460167,26.0,29.9925236667
+2024-09-28 02:39:00,0.0550785333,293.0182666333,26.0,29.9882511
+2024-09-28 02:40:00,0.0548734333,291.9256673333,26.0,29.9813092667
+2024-09-28 02:41:00,0.05392505,287.3822489,26.0,29.9935915
+2024-09-28 02:42:00,0.0527908,281.9384817167,26.0,29.9983975333
+2024-09-28 02:43:00,0.0514899333,275.5879528,26.0,29.9850473333
+2024-09-28 02:44:00,0.0504614167,270.5863524667,26.0,29.9903876333
+2024-09-28 02:45:00,0.04932075,265.0112146667,26.0,29.9813091667
+2024-09-28 02:46:00,0.04879535,262.5889160333,26.0,29.9871835
+2024-09-28 02:47:00,0.0442197667,242.0698509,26.0,29.9855811333
+2024-09-28 02:48:00,0.0363602167,201.9000354167,26.0,29.9850473333
+2024-09-28 02:49:00,0.0365267667,202.4831811333,26.0,29.9935914
+2024-09-28 02:50:00,0.0364434333,202.3197724333,26.0,29.9951935333
+2024-09-28 02:51:00,0.0364435167,202.23646605,26.0,29.9941254
+2024-09-28 02:52:00,0.0363826167,201.76866645,26.0,29.9935913667
+2024-09-28 02:53:00,0.0399263333,217.99103015,26.0,29.9967955333
+2024-09-28 02:54:00,0.04676075,251.76546235,26.0,29.9807752667
+2024-09-28 02:55:00,0.04665185,250.6280054333,26.0,29.9845134
+2024-09-28 02:56:00,0.0464788,250.323614,26.0,29.9818432333
+2024-09-28 02:57:00,0.0465621833,250.3973096833,26.0,29.9893193
+2024-09-28 02:58:00,0.0466775,250.3428375833,26.0,29.9898535333
+2024-09-28 02:59:00,0.05422305,282.6882404,26.0,29.9861153333
+2024-09-28 03:00:00,0.0625824667,321.3393109333,26.0,29.9781050667
+2024-09-28 03:01:00,0.0612398833,314.94393325,26.0,29.9781051
+2024-09-28 03:02:00,0.0533097333,281.9256644167,26.0,29.978639
+2024-09-28 03:03:00,0.0499102,265.1746234667,26.0,29.9855812667
+2024-09-28 03:04:00,0.0507304167,268.3755221,26.0,29.973299
+2024-09-28 03:05:00,0.0508362,268.9298345833,26.0,29.9818430667
+2024-09-28 03:06:00,0.0509675333,269.11567245,26.0,29.9882517
+2024-09-28 03:07:00,0.0510668333,269.4072418333,26.0,29.9786392
+2024-09-28 03:08:00,0.05101565,269.1605292833,26.0,29.9823771
+2024-09-28 03:09:00,0.0510507833,268.7600134833,26.0,29.9770371
+2024-09-28 03:10:00,0.0511758167,269.5386095667,26.0,29.972765
+2024-09-28 03:11:00,0.0511630667,269.1925725667,26.0,29.974901
+2024-09-28 03:12:00,0.05120145,269.2822811833,26.0,29.9807752
+2024-09-28 03:13:00,0.0512207,269.2534470833,26.0,29.973833
+2024-09-28 03:14:00,0.05117255,268.5261165667,26.0,29.9765031
+2024-09-28 03:15:00,0.0503652333,264.6331286,26.0,29.976503
+2024-09-28 03:16:00,0.04932715,259.6443446833,26.0,29.975969
+2024-09-28 03:17:00,0.0482987167,254.7741078833,26.0,29.9781051
+2024-09-28 03:18:00,0.0472637833,249.8974707833,26.0,29.978639
+2024-09-28 03:19:00,0.0467607167,247.6257620833,26.0,29.9690269
+2024-09-28 03:20:00,0.0469528167,248.3627027833,26.0,29.96795895
+2024-09-28 03:21:00,0.0470617333,247.9365588333,26.0,29.97009495
+2024-09-28 03:22:00,0.0404004667,219.05478965,28.8,29.9749009667
+2024-09-28 03:23:00,-0.0933961833,0.2659403333,44.85,29.9797070667
+2024-09-28 03:24:00,-0.0927907333,0.7465555167,42.9333333333,29.972231
+2024-09-28 03:25:00,0.0616629833,320.1441862667,25.9833333333,29.973833
+2024-09-28 03:26:00,0.0698269667,362.4479324,26.0,29.9631529167
+2024-09-28 03:27:00,0.0600192333,318.5325226333,26.0,29.9732989667
+2024-09-28 03:28:00,0.05058315,274.380007,26.0,29.9674249
+2024-09-28 03:29:00,0.0506792333,274.37360085,26.0,29.9695609667
+2024-09-28 03:30:00,0.0507111667,273.9218226333,26.0,29.9738329167
+2024-09-28 03:31:00,0.0507561167,274.08843695,26.0,29.9727649833
+2024-09-28 03:32:00,0.0507721833,274.0307627833,26.0,29.971697
+2024-09-28 03:33:00,0.0509292,274.4665187333,26.0,29.970095
+2024-09-28 03:34:00,0.0507913667,273.2361398333,26.0,29.967959
+2024-09-28 03:35:00,0.0508266333,273.3675129667,26.0,29.970629
+2024-09-28 03:36:00,0.0510219,273.31944875,26.0,29.9674249
+2024-09-28 03:37:00,0.0510251167,273.8609430833,26.0,29.971697
+2024-09-28 03:38:00,0.0510122833,273.3226556833,26.0,29.966357
+2024-09-28 03:39:00,0.0511051833,273.5533528667,26.0,29.9636868833
+2024-09-28 03:40:00,0.0511662167,273.24255055,26.0,29.96795895
+2024-09-28 03:41:00,0.0512109667,273.3258600333,26.0,29.96795895
+2024-09-28 03:42:00,0.051086,272.8516488167,26.0,29.9615508167
+2024-09-28 03:43:00,0.0512206667,273.0791442,26.0,29.9578127667
+2024-09-28 03:44:00,0.0513425167,273.0438953167,26.0,29.9636868667
+2024-09-28 03:45:00,0.05128805,272.3389958,26.0,29.9615508833
+2024-09-28 03:46:00,0.0513586833,272.6786289667,26.0,29.9588807833
+2024-09-28 03:47:00,0.0513104167,272.5312392833,26.0,29.9626188833
+2024-09-28 03:48:00,0.05142265,272.78756905,26.0,29.9610169
+2024-09-28 03:49:00,0.0515572,272.9029143,26.0,29.9530066833
+2024-09-28 03:50:00,0.05151875,272.1948052833,26.0,29.96101685
+2024-09-28 03:51:00,0.0512302667,270.9355962167,26.0,29.95727875
+2024-09-28 03:52:00,0.0502371,265.7289346333,26.0,29.966357
+2024-09-28 03:53:00,0.0490452667,259.71804055,26.0,29.9594148333
+2024-09-28 03:54:00,0.0478983167,255.1650110167,26.0,29.9642209167
+2024-09-28 03:55:00,0.0473695833,252.1787909167,26.0,29.96742495
+2024-09-28 03:56:00,0.0474049,252.0089741833,26.0,29.9620847
+2024-09-28 03:57:00,0.0603204,307.6353747,26.0,29.9583467167
+2024-09-28 03:58:00,0.06714205,341.5764139167,26.0,29.9599488667
+2024-09-28 03:59:00,0.0650048667,330.6536385333,26.0,29.9508705833
+2024-09-28 04:00:00,0.0510156167,269.9006733167,26.0,29.9588808
+2024-09-28 04:01:00,0.0509739667,267.41429395,26.0,29.964221
+2024-09-28 04:02:00,0.05149625,269.6091033167,26.0,29.9631528833
+2024-09-28 04:03:00,0.0516148333,269.8013478333,26.0,29.9524727333
+2024-09-28 04:04:00,0.0514738667,269.3816124,26.0,29.9556767667
+2024-09-28 04:05:00,0.0513905333,269.1380978167,26.0,29.9519386167
+2024-09-28 04:06:00,0.0516500333,269.6603677667,26.0,29.9487345333
+2024-09-28 04:07:00,0.0516156154,269.7409651026,26.0,29.9567037692
+2024-09-28 04:09:00,0.0518263333,269.0804243333,26.0,29.9423264167
+2024-09-28 04:10:00,0.0517621167,268.8208941167,26.0,29.9460645667
+2024-09-28 04:11:00,0.0607562167,306.8503717167,26.0,29.9519387
+2024-09-28 04:12:00,0.0690933667,344.5626422833,26.0,29.9535406833
+2024-09-28 04:13:00,0.06612635,331.6661307833,26.0,29.9610169333
+2024-09-28 04:14:00,0.0545816102,282.5430768983,26.0,29.9561745254
+2024-09-28 04:15:00,0.0515250667,267.4463308667,26.0,29.9514046833
+2024-09-28 04:16:00,0.05161795,267.5841065,26.0,29.9508707167
+2024-09-28 04:17:00,0.0516595667,266.9272649167,26.0,29.9514046833
+2024-09-28 04:18:00,0.0516852,267.2636968667,26.0,29.9562107167
+2024-09-28 04:19:00,0.0515870508,267.1894572881,26.0,29.9496577797
+2024-09-28 04:20:00,0.05170445,266.94969485,26.0,29.9460645167
+2024-09-28 04:21:00,0.0518453833,267.3982707833,26.0,29.9492686167
+2024-09-28 04:22:00,0.0518357333,267.2476766667,26.0,29.94499655
+2024-09-28 04:23:00,0.0515091167,265.4277453667,26.0,29.9540746667
+2024-09-28 04:24:00,0.0519912034,266.8994574746,26.0,29.9431411017
+2024-09-28 04:25:00,0.0520602167,267.3502019167,26.0,29.9487346333
+2024-09-28 04:26:00,0.0520661017,267.228563322,26.0,29.945313322
+2024-09-28 04:27:00,0.05210495,266.5812214667,26.0,29.9492685833
+2024-09-28 04:28:00,0.0522172333,267.96860375,26.0,29.9471326167
+2024-09-28 04:29:00,0.0518478305,265.6384565763,26.0,29.9480286271
+2024-09-28 04:30:00,0.0509195167,260.8522909167,26.0,29.9492686667
+2024-09-28 04:31:00,0.04965715,254.90547765,26.0,29.9455306
+2024-09-28 04:32:00,0.0486159333,250.0801023167,26.0,29.9396564167
+2024-09-28 04:33:00,0.0479654833,246.76065655,26.0,29.9449965833
+2024-09-28 04:34:00,0.0391368475,204.4911733898,29.7796610169,29.9485717119
+2024-09-28 04:35:00,-0.1177540333,0.1185517,44.9166666667,29.9417924167
+2024-09-28 04:36:00,-0.0965269,8.3819291333,42.0333333333,29.9433945333
+2024-09-28 04:37:00,0.0661230333,339.990385,26.0,29.9460645833
+2024-09-28 04:38:00,0.0714291667,363.63024765,26.0,29.9332481167
+2024-09-28 04:39:00,0.0591955254,310.8781940169,26.0,29.9387965593
+2024-09-28 04:40:00,0.0512944333,272.9573856833,26.0,29.9417924833
+2024-09-28 04:41:00,0.0513264,272.9958352667,26.0,29.9444625667
+2024-09-28 04:42:00,0.0514642333,272.99262995,26.0,29.9433945333
+2024-09-28 04:43:00,0.0514418333,272.8740782667,26.0,29.9391224
+2024-09-28 04:44:00,0.0515577797,272.6635833559,26.0,29.9404257288
+2024-09-28 04:45:00,0.0515923667,272.6273619,26.0,29.9433945333
+2024-09-28 04:46:00,0.0516378621,272.6645948966,26.0,29.9401167759
+2024-09-28 04:47:00,0.0515699667,271.9480911,26.0,29.9455305167
+2024-09-28 04:48:00,0.05170115,272.3614185333,26.0,29.9385883833
+2024-09-28 04:49:00,0.0516652203,272.2139248644,26.0,29.9349949831
+2024-09-28 04:50:00,0.0518487,272.5600757167,26.0,29.9364522667
+2024-09-28 04:51:00,0.0518935,272.5857074833,26.0,29.9343161667
+2024-09-28 04:52:00,0.0510604667,268.3915384667,26.0,29.93752035
+2024-09-28 04:53:00,0.0450112167,239.0676054167,26.0,29.9401904
+2024-09-28 04:54:00,0.0561131186,290.0309004915,26.0,29.9404257458
+2024-09-28 04:55:00,0.0561744167,290.1922466833,26.0,29.9268399667
+2024-09-28 04:56:00,0.0560623167,289.5257951,26.0,29.9300440167
+2024-09-28 04:57:00,0.0561584,289.8558147333,26.0,29.9364523167
+2024-09-28 04:58:00,0.0562671833,289.7853267833,26.0,29.9385883167
+2024-09-28 04:59:00,0.0562987627,289.4932660847,26.0,29.9290212712
+2024-09-28 05:00:00,0.0565300667,289.5033656833,26.0,29.9359183
+2024-09-28 05:01:00,0.0562704333,288.0262766667,26.0,29.9327142
+2024-09-28 05:02:00,0.0554886167,284.5113774833,26.0,29.9348502
+2024-09-28 05:03:00,0.0542870833,279.6443455667,26.0,29.9279079
+2024-09-28 05:04:00,0.053020678,273.6085221186,26.0,29.9344518814
+2024-09-28 05:05:00,0.0518230333,267.9878239667,26.0,29.9343161667
+2024-09-28 05:06:00,0.0508017288,262.793868339,26.0,29.9339088475
+2024-09-28 05:07:00,0.0503586333,260.5639228167,26.0,29.93912235
+2024-09-28 05:08:00,0.0498909167,258.2249297,26.0,29.9348502667
+2024-09-28 05:09:00,0.0479749167,249.9391228,26.0,29.93004395
+2024-09-28 05:10:00,0.0503488136,260.4966897797,26.0,29.9404257119
+2024-09-28 05:11:00,0.0519544833,268.08715255,26.0,29.94019035
+2024-09-28 05:12:00,0.0542710167,277.8148051167,26.0,29.93431625
+2024-09-28 05:13:00,0.0556872333,283.9794932833,26.0,29.9396563833
+2024-09-28 05:14:00,0.0568120667,289.487346,26.0,29.9300440667
+2024-09-28 05:15:00,0.0572242373,290.2817984746,26.0,29.9344519661
+2024-09-28 05:16:00,0.0572059667,290.2819597,26.0,29.9257719
+2024-09-28 05:17:00,0.0573630667,290.68888005,26.0,29.91722735
+2024-09-28 05:18:00,0.0575425,290.8298575667,26.0,29.9166933167
+2024-09-28 05:19:00,0.0574079167,289.8782436333,26.0,29.9193635667
+2024-09-28 05:20:00,0.0574817119,290.3599984237,26.0,29.931736678
+2024-09-28 05:21:00,0.05722845,288.7343836833,26.0,29.928442
+2024-09-28 05:22:00,0.0575360667,289.6379365,26.0,29.9193635667
+2024-09-28 05:23:00,0.0515860833,263.4924733,26.0,29.92844175
+2024-09-28 05:24:00,0.0522428833,264.3671928833,26.0,29.9188294167
+2024-09-28 05:25:00,0.0537930678,271.4253927966,26.0,29.9083841695
+2024-09-28 05:26:00,0.0544281,274.1685400167,26.0,29.9198974833
+2024-09-28 05:27:00,0.0542550333,273.0599186167,26.0,29.9145572
+2024-09-28 05:28:00,0.0543543667,273.236145,26.0,29.9118871
+2024-09-28 05:29:00,0.0546108167,274.0852357,26.0,29.9145572167
+2024-09-28 05:30:00,0.0546499831,273.8431282203,26.0,29.913815
+2024-09-28 05:31:00,0.054662,274.0724177833,26.0,29.9225676833
+2024-09-28 05:32:00,0.05474845,273.7359837667,26.0,29.91562525
+2024-09-28 05:33:00,0.05474835,274.1268845,26.0,29.9161593667
+2024-09-28 05:34:00,0.0547036,273.8929860667,26.0,29.9209654833
+2024-09-28 05:35:00,0.0541775424,270.8291015932,26.0,29.9203319153
+2024-09-28 05:36:00,0.0534508983,267.554399339,26.0,29.9105565424
+2024-09-28 05:37:00,0.0524094,263.1624460667,26.0,29.91829545
+2024-09-28 05:38:00,0.0511855333,257.4847821,26.0,29.9156252667
+2024-09-28 05:39:00,0.05011835,252.5088105333,26.0,29.9118871
+2024-09-28 05:40:00,0.0489805424,247.0915987797,26.0,29.9121857627
+2024-09-28 05:41:00,0.0481193167,243.4700402167,26.0,29.9188294167
+2024-09-28 05:42:00,0.0480648167,242.4287104167,26.0,29.9124211
+2024-09-28 05:43:00,0.0480552167,242.8708750833,26.0,29.92043145
+2024-09-28 05:44:00,0.0481290167,242.5248332333,26.0,29.9092169333
+2024-09-28 05:45:00,0.0479606271,242.2267983898,26.0,29.9176165085
+2024-09-28 05:46:00,0.0338193667,174.9279074667,31.2666666667,29.9198974667
+2024-09-28 05:47:00,-0.1233484167,0.1409804,45.0,29.9150912667
+2024-09-28 05:48:00,-0.0789074,0.1345722,30.2333333333,29.9166931833
+2024-09-28 05:49:00,0.0009064833,0.1537968,0.0,29.9177613833
+2024-09-28 05:50:00,0.001084661,0.1401114915,0.0,29.9121857966
+2024-09-28 05:51:00,0.0010090333,0.1441845,0.0,29.9124211333
+2024-09-28 05:52:00,0.0010091167,0.1890419,0.0,29.9076148333
+2024-09-28 05:53:00,0.0010442333,0.1441845,0.0,29.9124211333
+2024-09-28 05:54:00,0.0009995167,0.1473886,0.0,29.9113530667
+2024-09-28 05:55:00,0.000990339,0.1596619322,0.0,29.9143580339
+2024-09-28 05:56:00,0.01291565,63.1880803,14.6833333333,29.9092169
+2024-09-28 05:57:00,0.0429638667,234.31271785,26.0,29.9150911833
+2024-09-28 05:58:00,0.0425536833,231.2784377167,26.0,29.9145571
+2024-09-28 05:59:00,0.0422459333,228.87856225,26.0,29.9113529167
+2024-09-28 06:00:00,0.0416651356,226.0846422203,26.0,29.9149010847
+2024-09-28 06:01:00,0.0408938167,221.9000328,26.0,29.9172273167
+2024-09-28 06:02:00,0.0434829667,232.3454025167,26.0,29.9198974333
+2024-09-28 06:03:00,0.0499069667,263.1304102667,26.0,29.9188293167
+2024-09-28 06:04:00,0.0520058167,269.3976273,26.0,29.9092169667
+2024-09-28 06:05:00,0.0673870169,339.346908678,26.0,29.9094705254
+2024-09-28 06:06:00,0.06831785,342.7266935667,26.0,29.9054789167
+2024-09-28 06:07:00,0.0536942667,280.25632725,26.0,29.9092169833
+2024-09-28 06:08:00,0.0513488167,267.2060267667,26.0,29.9134892
+2024-09-28 06:09:00,0.0522204167,271.0573511333,26.0,29.9161592
+2024-09-28 06:10:00,0.0524439831,272.3475180508,26.0,29.9089275085
+2024-09-28 06:11:00,0.0524126333,271.3905756167,26.0,29.9108191333
+2024-09-28 06:12:00,0.0524510833,271.5603932833,26.0,29.9065470167
+2024-09-28 06:13:00,0.05253745,271.32329005,26.0,29.9113530667
+2024-09-28 06:14:00,0.0525823833,271.422619,26.0,29.9044107833
+2024-09-28 06:15:00,0.0526721186,271.6762814407,26.0,29.9072980508
+2024-09-28 06:16:00,0.0526945167,271.4258244167,26.0,29.9028087833
+2024-09-28 06:17:00,0.05275545,271.6533131167,26.0,29.9049448167
+2024-09-28 06:18:00,0.0528099167,271.6565145,26.0,29.9081489667
+2024-09-28 06:19:00,0.0527971333,271.17589895,26.0,29.9092170333
+2024-09-28 06:20:00,0.0527568814,271.4449409661,26.0,29.9040398814
+2024-09-28 06:21:00,0.05219475,268.2794007167,26.0,29.9049448167
+2024-09-28 06:22:00,0.0510668667,262.9029206333,26.0,29.9081491667
+2024-09-28 06:23:00,0.0498813333,257.1739822333,26.0,29.9081488667
+2024-09-28 06:24:00,0.0487087167,251.69817595,26.0,29.89586665
+2024-09-28 06:25:00,0.0484362712,250.3760736949,26.0,29.8986095932
+2024-09-28 06:26:00,0.0484428,249.9647519167,26.0,29.9028089333
+2024-09-28 06:27:00,0.0484297797,249.949221,26.0,29.9056690339
+2024-09-28 06:28:00,0.04846205,249.8782429,26.0,29.9060127833
+2024-09-28 06:29:00,0.04858055,249.88785475,26.0,29.9017408667
+2024-09-28 06:30:00,0.0484981695,249.8514677966,26.0,29.9007813729
+2024-09-28 06:31:00,0.0485164833,249.63472885,26.0,29.9006728
+2024-09-28 06:32:00,0.0492279,251.0189021833,26.0,29.9017409667
+2024-09-28 06:33:00,0.0683596167,335.5238731833,26.0,29.8969350667
+2024-09-28 06:34:00,0.06909975,339.0676084667,26.0,29.9033428667
+2024-09-28 06:35:00,0.0602805763,302.272739661,26.0,29.8991522712
+2024-09-28 06:36:00,0.0507592833,258.7311743,26.0,29.8985367667
+2024-09-28 06:37:00,0.0514258667,262.0954830667,26.0,29.8953327667
+2024-09-28 06:38:00,0.0522748,265.1201516667,26.0,29.8883910333
+2024-09-28 06:39:00,0.0528131,267.55526895,26.0,29.90067265
+2024-09-28 06:40:00,0.0527991695,267.7010329153,26.0,29.8926357119
+2024-09-28 06:41:00,0.0529573667,267.45274215,26.0,29.8947988333
+2024-09-28 06:42:00,0.0529670833,267.9301509833,26.0,29.8969346667
+2024-09-28 06:43:00,0.052935,267.2925349833,26.0,29.8915947833
+2024-09-28 06:44:00,0.0530632,268.18007535,26.0,29.8889247667
+2024-09-28 06:45:00,0.0529132712,266.3585644068,26.0,29.8942647627
+2024-09-28 06:46:00,0.0523262,263.7936603,26.0,29.89586675
+2024-09-28 06:47:00,0.0515091333,259.7116344333,26.0,29.8937311167
+2024-09-28 06:48:00,0.0505285,255.6840720167,26.0,29.89586705
+2024-09-28 06:49:00,0.0497306833,252.2941382167,26.0,29.9028088333
+2024-09-28 06:50:00,0.0490031017,248.6197925932,26.0,29.8958940678
+2024-09-28 06:51:00,0.0486638667,247.03620925,26.0,29.8937311667
+2024-09-28 06:52:00,0.0488433833,247.3982715,26.0,29.8910609
+2024-09-28 06:53:00,0.0487310833,246.8824128,26.0,29.88625495
+2024-09-28 06:54:00,0.0487664833,246.6356978667,26.0,29.8894588
+2024-09-28 06:55:00,0.0487978475,246.8569967966,26.0,29.8920926271
+2024-09-28 06:56:00,0.06062165,295.2258932,26.0,29.8825167167
+2024-09-28 06:57:00,0.0658090333,319.1893632,26.0,29.8931963333
+2024-09-28 06:58:00,0.0647612,314.9535390667,26.0,29.8851869833
+2024-09-28 06:59:00,0.0520408667,261.2848481167,26.0,29.8867886833
+2024-09-28 07:00:00,0.0528578814,263.7974600339,26.0,29.8844898475
+2024-09-28 07:01:00,0.0533930833,265.3444400167,26.0,29.88144885
+2024-09-28 07:02:00,0.0534570667,265.7161156833,26.0,29.8883907333
+2024-09-28 07:03:00,0.0535212167,264.9086871667,26.0,29.8841186667
+2024-09-28 07:04:00,0.0534282833,265.5078479167,26.0,29.8803807333
+2024-09-28 07:05:00,0.0534214576,265.4168854576,26.0,29.8785161186
+2024-09-28 07:06:00,0.0010990167,130.8554956167,36.3666666667,29.87504075
+2024-09-28 07:07:00,-0.1327075167,0.1505927,45.0,29.8803807333
+2024-09-28 07:08:00,-0.0111375,146.7638575833,34.2166666667,29.8851869333
+2024-09-28 07:09:00,0.0534379667,279.5322067833,26.0,29.8846528
+2024-09-28 07:10:00,0.0526948983,273.7681905763,26.0,29.8796024407
+2024-09-28 07:11:00,0.0526432667,273.6494776833,26.0,29.8777105167
+2024-09-28 07:12:00,0.0528261167,273.3130441833,26.0,29.8761088167
+2024-09-28 07:13:00,0.0528933833,273.2105091167,26.0,29.8723707833
+2024-09-28 07:14:00,0.0529830667,273.2137139833,26.0,29.8755745833
+2024-09-28 07:15:00,0.0530371864,273.0774086102,26.0,29.8752579153
+2024-09-28 07:16:00,0.0532104,273.0663237833,26.0,29.87664255
+2024-09-28 07:17:00,0.0531688333,273.0567138333,26.0,29.8739726333
+2024-09-28 07:18:00,0.0631015667,312.8868972,26.0,29.8750406
+2024-09-28 07:19:00,0.0741076833,361.45466295,26.0,29.8803807333
+2024-09-28 07:20:00,0.0706944068,348.4704464237,26.0,29.8719994068
+2024-09-28 07:21:00,0.0528133,269.5354050667,26.0,29.87343865
+2024-09-28 07:22:00,0.0529733667,270.4357564667,26.0,29.8702346
+2024-09-28 07:23:00,0.0529990333,269.9455293167,26.0,29.8771767833
+2024-09-28 07:24:00,0.0530534833,270.0160202667,26.0,29.87664255
+2024-09-28 07:25:00,0.053014339,270.0731516949,26.0,29.8752578136
+2024-09-28 07:26:00,0.0531048,269.6443476833,26.0,29.8718367
+2024-09-28 07:27:00,0.0531591667,270.11855405,26.0,29.8771765333
+2024-09-28 07:28:00,0.0531079167,269.90067265,26.0,29.8777106667
+2024-09-28 07:29:00,0.0531912167,269.7404698667,26.0,29.8654288
+2024-09-28 07:30:00,0.0533172542,269.9949521356,26.0,29.8774299492
+2024-09-28 07:31:00,0.0533129833,269.32073425,26.0,29.8739723833
+2024-09-28 07:32:00,0.0532424333,268.5902005833,26.0,29.8654282
+2024-09-28 07:33:00,0.0534218833,269.1829530333,26.0,29.8697004167
+2024-09-28 07:34:00,0.0535116667,269.4008357833,26.0,29.87504045
+2024-09-28 07:35:00,0.0534963729,269.4507994746,26.0,29.8703700508
+2024-09-28 07:36:00,0.0523421833,264.42165735,26.0,29.8680983167
+2024-09-28 07:37:00,0.0470682667,238.99070895,26.0,29.8723703833
+2024-09-28 07:38:00,0.0574785167,285.7065026833,26.0,29.8691667833
+2024-09-28 07:39:00,0.0575425667,285.5559107167,26.0,29.865428
+2024-09-28 07:40:00,0.0570645593,283.2892540169,26.0,29.8692840847
+2024-09-28 07:41:00,0.0560429,278.5421359667,26.0,29.8697003167
+2024-09-28 07:42:00,0.05463955,272.4831772167,26.0,29.8659626833
+2024-09-28 07:43:00,0.0533450667,266.5299580667,26.0,29.8648940167
+2024-09-28 07:44:00,0.0520664667,260.6760636667,26.0,29.8670305
+2024-09-28 07:45:00,0.0480973051,243.1554392542,26.0,29.8654826441
+2024-09-28 07:46:00,0.0506406667,255.0528675667,26.0,29.8664967167
+2024-09-28 07:47:00,0.0531496167,265.8058303333,26.0,29.8723703833
+2024-09-28 07:48:00,0.0553252333,274.7356598833,26.0,29.8616904667
+2024-09-28 07:49:00,0.0567894,280.8651061333,26.0,29.86703045
+2024-09-28 07:50:00,0.0579019153,285.495195322,26.0,29.8567936949
+2024-09-28 07:51:00,0.0568793,282.9894275167,26.0,29.8638262
+2024-09-28 07:52:00,0.04382235,222.35821985,26.0,29.8627578833
+2024-09-28 07:53:00,0.0436303667,221.4802979,26.0,29.8632926667
+2024-09-28 07:54:00,0.0436207167,221.3841749333,26.0,29.8675643833
+2024-09-28 07:55:00,0.0438418475,221.8715242034,26.0,29.8719994576
+2024-09-28 07:56:00,0.0438288333,221.8103212833,26.0,29.8691662833
+2024-09-28 07:57:00,0.04427435,223.2585711333,26.0,29.8707680833
+2024-09-28 07:58:00,0.0531719833,262.2588879167,26.0,29.8638263
+2024-09-28 07:59:00,0.0536238167,265.0464593,26.0,29.8611558833
+2024-09-28 08:00:00,0.0525124068,259.8091622712,26.0,29.8475615085
+2024-09-28 08:01:00,0.0515026167,255.0240286333,26.0,29.8595543333
+2024-09-28 08:02:00,0.0503523667,249.8301830833,26.0,29.8675645333
+2024-09-28 08:03:00,0.0496251,246.75424885,26.0,29.8600881167
+2024-09-28 08:04:00,0.0494582333,245.1457843167,26.0,29.8579522333
+2024-09-28 08:05:00,0.0496677119,246.0619461017,26.0,29.855707322
+2024-09-28 08:06:00,0.0496666,246.23838935,26.0,29.8611560833
+2024-09-28 08:07:00,0.0497500833,245.6936889833,26.0,29.8584860167
+2024-09-28 08:08:00,0.0627779,298.8561352333,26.0,29.8622242
+2024-09-28 08:09:00,0.0635308833,305.0016036,26.0,29.8606218
+2024-09-28 08:10:00,0.0503618475,248.0235018983,26.0,29.8546213051
+2024-09-28 08:11:00,0.0553379833,269.3367487667,26.0,29.8563503833
+2024-09-28 08:12:00,0.0568409333,275.47580935,26.0,29.85581575
+2024-09-28 08:13:00,0.05808085,280.02883795,26.0,29.85261205
+2024-09-28 08:14:00,0.0583372667,281.5539896833,26.0,29.8515439333
+2024-09-28 08:15:00,0.0585439322,281.8848757797,26.0,29.8529924068
+2024-09-28 08:16:00,0.0584717667,281.74623415,26.0,29.8584857667
+2024-09-28 08:17:00,0.0587537,281.9096405,26.0,29.85261225
+2024-09-28 08:18:00,0.0162832167,149.3880148167,34.8666666667,29.8478058
+2024-09-28 08:19:00,-0.1219802667,0.1473886,45.0,29.8472718167
+2024-09-28 08:20:00,-0.0605802712,0.1368530847,27.8305084746,29.8584224915
+2024-09-28 08:21:00,-0.0029382833,0.1570009,18.1,29.8638261
+2024-09-28 08:22:00,-0.1082378167,0.1377763,55.9833333333,29.8413977
+2024-09-28 08:23:00,-0.0474592167,0.1409804,59.0,29.8435336833
+2024-09-28 08:24:00,-0.0256745167,0.1025312,61.65,29.8408636667
+2024-09-28 08:25:00,0.0005505085,0.1498867119,64.0,29.8443030508
+2024-09-28 08:26:00,0.0010923667,0.1345722,64.0,29.8446018
+2024-09-28 08:27:00,0.001038,0.1185517,64.0,29.8488738667
+2024-09-28 08:28:00,0.00098025,0.1730214,64.0,29.8547481333
+2024-09-28 08:29:00,0.0011341167,0.1634091,64.0,29.8483397833
+2024-09-28 08:30:00,0.0010228814,0.1433698983,64.0,29.8432170339

+ 251 - 0
models/uf-rl/训练/uf_train/rl_model/DQN/run_dqn_decide.py

@@ -0,0 +1,251 @@
+"""
+run_dqn_decide.py
+
+UF 超滤 DQN 决策主入口(Inference / Online Assist)
+
+职责:
+1. 构造物理世界(physics)
+2. 实例化决策器(UFDQNDecider)
+3. 构造当前工厂状态(observation)
+4. 调用模型给出策略建议
+5. 生成 PLC 下发指令(限幅 / 限速)
+6. 评估该指令在物理模型下的效果(只评估,不下发)
+"""
+
+from pathlib import Path
+import numpy as np
+
+# ========== 参数 / 物理 ==========
+from uf_train.env.uf_resistance_models_load import load_resistance_models
+from uf_train.env.uf_physics import UFPhysicsModel
+from uf_train.env.env_params import UFState, UFPhysicsParams, UFStateBounds, UFRewardParams, UFActionSpec
+
+
+# ========== 决策器 ==========
+from uf_train.rl_model.DQN.dqn_decider import UFDQNDecider
+
+
+def build_physics():
+    """
+    构造与训练一致的物理模型(只做一次)
+    """
+    phys_params = UFPhysicsParams()
+    res_fp, res_bw = load_resistance_models(phys_params)
+
+    physics = UFPhysicsModel(
+        phys_params=phys_params,
+        resistance_model_fp=res_fp,
+        resistance_model_bw=res_bw,
+    )
+    return physics
+
+def generate_plc_instructions(current_L_s, current_t_bw_s, model_prev_L_s, model_prev_t_bw_s, model_L_s, model_t_bw_s):
+    """
+    根据工厂当前值、模型上一轮决策值和模型当前轮决策值,生成PLC指令。
+
+    新增功能:
+    1. 处理None值情况:如果模型上一轮值为None,则使用工厂当前值;
+       如果工厂当前值也为None,则返回None并提示错误。
+    """
+
+    action_spec = UFActionSpec()
+    adjustment_threshold = 1.0
+
+    # 处理None值情况
+    if model_prev_L_s is None:
+        if current_L_s is None:
+            print("错误: 过滤时长的工厂当前值和模型上一轮值均为None")
+            return None, None
+        else:
+            # 使用工厂当前值作为基准
+            effective_current_L = current_L_s
+            source_L = "工厂当前值(模型上一轮值为None)"
+    else:
+        # 模型上一轮值不为None,继续检查工厂当前值
+        if current_L_s is None:
+            effective_current_L = model_prev_L_s
+            source_L = "模型上一轮值(工厂当前值为None)"
+        else:
+            effective_current_L = model_prev_L_s
+            source_L = "模型上一轮值"
+
+    # 对反洗时长进行同样的处理
+    if model_prev_t_bw_s is None:
+        if current_t_bw_s is None:
+            print("错误: 反洗时长的工厂当前值和模型上一轮值均为None")
+            return None, None
+        else:
+            effective_current_t_bw = current_t_bw_s
+            source_t_bw = "工厂当前值(模型上一轮值为None)"
+    else:
+        if current_t_bw_s is None:
+            effective_current_t_bw = model_prev_t_bw_s
+            source_t_bw = "模型上一轮值(工厂当前值为None)"
+        else:
+            effective_current_t_bw = model_prev_t_bw_s
+            source_t_bw = "模型上一轮值"
+
+    # 检测所有输入值是否在规定范围内(只对非None值进行检查)
+    # 工厂当前值检查(警告)
+    if current_L_s is not None and not (action_spec.L_min_s <= current_L_s <= action_spec.L_max_s):
+        print(f"警告: 当前过滤时长 {current_L_s} 秒不在允许范围内 [{action_spec.L_min_s}, {action_spec.L_max_s}]")
+    if current_t_bw_s is not None and not (action_spec.t_bw_min_s <= current_t_bw_s <= action_spec.t_bw_max_s):
+        print(f"警告: 当前反洗时长 {current_t_bw_s} 秒不在允许范围内 [{action_spec.t_bw_min_s}, {action_spec.t_bw_max_s}]")
+
+    # 模型上一轮决策值检查(警告)
+    if model_prev_L_s is not None and not (action_spec.L_min_s <= model_prev_L_s <= action_spec.L_max_s):
+        print(f"警告: 模型上一轮过滤时长 {model_prev_L_s} 秒不在允许范围内 [{action_spec.L_min_s}, {action_spec.L_max_s}]")
+    if model_prev_t_bw_s is not None and not (action_spec.t_bw_min_s <= model_prev_t_bw_s <= action_spec.t_bw_max_s):
+        print(f"警告: 模型上一轮反洗时长 {model_prev_t_bw_s} 秒不在允许范围内 [{action_spec.t_bw_min_s}, {action_spec.t_bw_max_s}]")
+
+    # 模型当前轮决策值检查(错误)
+    if model_L_s is None:
+        raise ValueError("错误: 决策模型建议的过滤时长不能为None")
+    elif not (action_spec.L_min_s <= model_L_s <= action_spec.L_max_s):
+        raise ValueError(f"错误: 决策模型建议的过滤时长 {model_L_s} 秒不在允许范围内 [{action_spec.L_min_s}, {action_spec.L_max_s}]")
+
+    if model_t_bw_s is None:
+        raise ValueError("错误: 决策模型建议的反洗时长不能为None")
+    elif not (action_spec.t_bw_min_s <= model_t_bw_s <= action_spec.t_bw_max_s):
+        raise ValueError(f"错误: 决策模型建议的反洗时长 {model_t_bw_s} 秒不在允许范围内 [{action_spec.t_bw_min_s}, {action_spec.t_bw_max_s}]")
+
+    print(f"过滤时长基准: {source_L}, 值: {effective_current_L}")
+    print(f"反洗时长基准: {source_t_bw}, 值: {effective_current_t_bw}")
+
+    # 使用选定的基准值进行计算调整
+    L_diff = model_L_s - effective_current_L
+    L_adjustment = 0
+    if abs(L_diff) >= adjustment_threshold * action_spec.L_step_s:
+        if L_diff >= 0:
+            L_adjustment = action_spec.L_step_s
+        else:
+            L_adjustment = -action_spec.L_step_s
+    next_L_s = effective_current_L + L_adjustment
+
+    t_bw_diff = model_t_bw_s - effective_current_t_bw
+    t_bw_adjustment = 0
+    if abs(t_bw_diff) >= adjustment_threshold * action_spec.t_bw_step_s:
+        if t_bw_diff >= 0:
+            t_bw_adjustment = action_spec.t_bw_step_s
+        else:
+            t_bw_adjustment = -action_spec.t_bw_step_s
+    next_t_bw_s = effective_current_t_bw + t_bw_adjustment
+
+    return next_L_s, next_t_bw_s
+
+
+
+def calc_uf_cycle_metrics(current_state, max_tmp_during_filtration, min_tmp_during_filtration, L_s: float, t_bw_s: float):
+    """
+    计算 UF 超滤系统的核心性能指标
+
+    参数:
+
+        L_s (float): 单次过滤时间(秒)
+        t_bw_s (float): 单次反洗时间(秒)
+
+    返回:
+        dict: {
+            "k_bw_per_ceb": 小周期次数,
+            "ton_water_energy_kWh_per_m3": 吨水电耗,
+            "recovery": 回收率,
+            "net_delivery_rate_m3ph": 净供水率 (m³/h),
+            "daily_prod_time_h": 日均产水时间 (小时/天)
+            "max_permeability": 全周期最高渗透率(lmh/bar)
+        }
+    """
+
+    # 模拟该参数下的超级周期
+    info, next_state = physics.simulate_one_supercycle(current_state, L_s=L_s, t_bw_s=t_bw_s)
+
+    # 获得模型模拟周期信息
+    k_bw_per_ceb = info["k_bw_per_ceb"]
+    ton_water_energy_kWh_per_m3 = info["ton_water_energy_kWh_per_m3"]
+    recovery = info["recovery"]
+    daily_prod_time_h = info["daily_prod_time_h"]
+
+    # 获得模型模拟周期内最高跨膜压差/最低跨膜压差
+    if max_tmp_during_filtration is None:
+        max_tmp_during_filtration = info["max_TMP_during_filtration"]
+    if min_tmp_during_filtration is None:
+        min_tmp_during_filtration = info["min_TMP_during_filtration"]
+
+    # 计算最高渗透率
+    max_permeability = 100 * current_state.q_UF / (128*40) / min_tmp_during_filtration
+
+
+    return {
+        "k_bw_per_ceb": k_bw_per_ceb,
+        "ton_water_energy_kWh_per_m3": ton_water_energy_kWh_per_m3,
+        "recovery": recovery,
+        "daily_prod_time_h": daily_prod_time_h,
+        "max_permeability": max_permeability
+    }
+
+def run_dqn_decide(
+    model_path: Path,
+    physics,
+    # -------- 工厂当前值 --------
+    current_state: UFState
+):
+    """
+    单轮 DQN 决策流程
+    """
+
+    # 构造决策器
+    decider = UFDQNDecider(
+        physics=physics,
+        model_path=model_path,
+        seed=0,
+    )
+    # 模型决策(不推进真实环境)
+
+    decision = decider.decide(current_state)
+    action_id = decision["action_id"]
+    model_L_s = decision["L_s"]
+    model_t_bw_s = decision["t_bw_s"]
+
+    return action_id, model_L_s, model_t_bw_s
+
+
+# ==============================
+# 示例调用
+# ==============================
+if __name__ == "__main__":
+
+    MODEL_PATH = "model/dqn_model.zip"
+    TMP0 = 0.019  # 原始 TMP0
+    q_UF = 300 # 进水流量
+    temp = 20.0 #进水温度
+    current_state = UFState(TMP=TMP0, q_UF=q_UF, temp=temp)
+
+    physics = build_physics()
+
+    action_id, model_L_s, model_t_bw_s = run_dqn_decide(
+        model_path=MODEL_PATH,
+        physics=physics,
+        current_state=current_state,
+    ) # 环境实例化,模型加载等功能放在UFDQNDecider类中
+
+    current_L_s = 3800
+    current_t_bw_s = 40
+    model_prev_L_s = 4040
+    model_prev_t_bw_s = 60
+    L_s, t_bw_s = generate_plc_instructions(current_L_s, current_t_bw_s, model_prev_L_s, model_prev_t_bw_s, model_L_s,
+                                            model_t_bw_s)  # 获取模型下发指令
+
+    L_s = 4100
+    t_bw_s = 96
+    max_tmp_during_filtration = 0.050176 # 新增工厂数据接口:周期最高/最低跨膜压差,无工厂数据接入时传入None,calc_uf_cycle_metrics()自动获取模拟周期中的跨膜压差最值
+    min_tmp_during_filtration = 0.012496
+    execution_result = calc_uf_cycle_metrics(current_state, max_tmp_during_filtration, min_tmp_during_filtration, L_s, t_bw_s)
+    print("\n===== 单步决策结果 =====")
+    print(f"模型选择的动作: {action_id}")
+    print(f"模型选择的L_s: {model_L_s} 秒, 模型选择的t_bw_s: {model_t_bw_s} 秒")
+    print(f"指令下发的L_s: {L_s} 秒, 指令下发的t_bw_s: {t_bw_s} 秒")
+    print(f"指令对应的反洗次数: {execution_result['k_bw_per_ceb']}")
+    print(f"指令对应的吨水电耗: {execution_result['ton_water_energy_kWh_per_m3']}")
+    print(f"指令对应的回收率: {execution_result['recovery']}")
+    print(f"指令对应的日均产水时间: {execution_result['daily_prod_time_h']}")
+    print(f"指令对应的最高渗透率: {execution_result['max_permeability']}")
+

+ 267 - 0
models/uf-rl/训练/uf_train/rl_model/DQN/run_dqn_deicde_totalstate.py

@@ -0,0 +1,267 @@
+"""
+run_dqn_decide_totalstate.py
+
+全状态UF 超滤 DQN 决策主入口(Inference / Online Assist)
+
+要求数据:
+1. UF1_prev_cycle.csv 示例数据,实际路径可更改,水厂上一周期完整数据,必须包含上一周期全部进水数据,且不能包含其他周期进水数据,分辨率1min,至少包含时间,进水流量,跨膜压差,温度
+2. UF1_init_cycle.csv 示例数据,实际路径可更改,水厂当前周期初始数据,建议在水厂当前周期进水稳定后传入并调用,程序计算该csv数据均值作为初始数据,eg:传入前10min数据,取周期前十分钟均值为初始值
+职责:
+0. 根据水厂csv数据构造状态
+1. 构造物理世界(physics)
+2. 实例化决策器(UFDQNDecider)
+3. 构造当前工厂状态(observation)
+4. 调用模型给出策略建议
+5. 生成 PLC 下发指令(限幅 / 限速)
+6. 评估该指令在物理模型下的效果(只评估,不下发)
+"""
+
+from pathlib import Path
+import numpy as np
+
+# ========== 参数 / 物理 ==========
+from uf_train.env.uf_resistance_models_load import load_resistance_models
+from uf_train.env.uf_physics import UFPhysicsModel
+from uf_train.env.env_params import UFState, UFPhysicsParams, UFStateBounds, UFRewardParams, UFActionSpec
+
+
+# ========== 决策器 ==========
+from uf_train.rl_model.DQN.dqn_decider import UFDQNDecider
+# ========== 决策状态构建器 ==========
+from uf_train.rl_model.DQN.dqn_statebuilder import DQNStateBuilder
+
+
+
+def build_physics():
+    """
+    构造与训练一致的物理模型(只做一次)
+    """
+    phys_params = UFPhysicsParams()
+    res_fp, res_bw = load_resistance_models(phys_params)
+
+    physics = UFPhysicsModel(
+        phys_params=phys_params,
+        resistance_model_fp=res_fp,
+        resistance_model_bw=res_bw,
+    )
+    return physics
+
+def generate_plc_instructions(current_L_s, current_t_bw_s, model_prev_L_s, model_prev_t_bw_s, model_L_s, model_t_bw_s):
+    """
+    根据工厂当前值、模型上一轮决策值和模型当前轮决策值,生成PLC指令。
+
+    新增功能:
+    1. 处理None值情况:如果模型上一轮值为None,则使用工厂当前值;
+       如果工厂当前值也为None,则返回None并提示错误。
+    """
+
+    action_spec = UFActionSpec()
+    adjustment_threshold = 1.0
+
+    # 处理None值情况
+    if model_prev_L_s is None:
+        if current_L_s is None:
+            print("错误: 过滤时长的工厂当前值和模型上一轮值均为None")
+            return None, None
+        else:
+            # 使用工厂当前值作为基准
+            effective_current_L = current_L_s
+            source_L = "工厂当前值(模型上一轮值为None)"
+    else:
+        # 模型上一轮值不为None,继续检查工厂当前值
+        if current_L_s is None:
+            effective_current_L = model_prev_L_s
+            source_L = "模型上一轮值(工厂当前值为None)"
+        else:
+            effective_current_L = model_prev_L_s
+            source_L = "模型上一轮值"
+
+    # 对反洗时长进行同样的处理
+    if model_prev_t_bw_s is None:
+        if current_t_bw_s is None:
+            print("错误: 反洗时长的工厂当前值和模型上一轮值均为None")
+            return None, None
+        else:
+            effective_current_t_bw = current_t_bw_s
+            source_t_bw = "工厂当前值(模型上一轮值为None)"
+    else:
+        if current_t_bw_s is None:
+            effective_current_t_bw = model_prev_t_bw_s
+            source_t_bw = "模型上一轮值(工厂当前值为None)"
+        else:
+            effective_current_t_bw = model_prev_t_bw_s
+            source_t_bw = "模型上一轮值"
+
+    # 检测所有输入值是否在规定范围内(只对非None值进行检查)
+    # 工厂当前值检查(警告)
+    if current_L_s is not None and not (action_spec.L_min_s <= current_L_s <= action_spec.L_max_s):
+        print(f"警告: 当前过滤时长 {current_L_s} 秒不在允许范围内 [{action_spec.L_min_s}, {action_spec.L_max_s}]")
+    if current_t_bw_s is not None and not (action_spec.t_bw_min_s <= current_t_bw_s <= action_spec.t_bw_max_s):
+        print(f"警告: 当前反洗时长 {current_t_bw_s} 秒不在允许范围内 [{action_spec.t_bw_min_s}, {action_spec.t_bw_max_s}]")
+
+    # 模型上一轮决策值检查(警告)
+    if model_prev_L_s is not None and not (action_spec.L_min_s <= model_prev_L_s <= action_spec.L_max_s):
+        print(f"警告: 模型上一轮过滤时长 {model_prev_L_s} 秒不在允许范围内 [{action_spec.L_min_s}, {action_spec.L_max_s}]")
+    if model_prev_t_bw_s is not None and not (action_spec.t_bw_min_s <= model_prev_t_bw_s <= action_spec.t_bw_max_s):
+        print(f"警告: 模型上一轮反洗时长 {model_prev_t_bw_s} 秒不在允许范围内 [{action_spec.t_bw_min_s}, {action_spec.t_bw_max_s}]")
+
+    # 模型当前轮决策值检查(错误)
+    if model_L_s is None:
+        raise ValueError("错误: 决策模型建议的过滤时长不能为None")
+    elif not (action_spec.L_min_s <= model_L_s <= action_spec.L_max_s):
+        raise ValueError(f"错误: 决策模型建议的过滤时长 {model_L_s} 秒不在允许范围内 [{action_spec.L_min_s}, {action_spec.L_max_s}]")
+
+    if model_t_bw_s is None:
+        raise ValueError("错误: 决策模型建议的反洗时长不能为None")
+    elif not (action_spec.t_bw_min_s <= model_t_bw_s <= action_spec.t_bw_max_s):
+        raise ValueError(f"错误: 决策模型建议的反洗时长 {model_t_bw_s} 秒不在允许范围内 [{action_spec.t_bw_min_s}, {action_spec.t_bw_max_s}]")
+
+    print(f"过滤时长基准: {source_L}, 值: {effective_current_L}")
+    print(f"反洗时长基准: {source_t_bw}, 值: {effective_current_t_bw}")
+
+    # 使用选定的基准值进行计算调整
+    L_diff = model_L_s - effective_current_L
+    L_adjustment = 0
+    if abs(L_diff) >= adjustment_threshold * action_spec.L_step_s:
+        if L_diff >= 0:
+            L_adjustment = action_spec.L_step_s
+        else:
+            L_adjustment = -action_spec.L_step_s
+    next_L_s = effective_current_L + L_adjustment
+
+    t_bw_diff = model_t_bw_s - effective_current_t_bw
+    t_bw_adjustment = 0
+    if abs(t_bw_diff) >= adjustment_threshold * action_spec.t_bw_step_s:
+        if t_bw_diff >= 0:
+            t_bw_adjustment = action_spec.t_bw_step_s
+        else:
+            t_bw_adjustment = -action_spec.t_bw_step_s
+    next_t_bw_s = effective_current_t_bw + t_bw_adjustment
+
+    return next_L_s, next_t_bw_s
+
+
+
+def calc_uf_cycle_metrics(current_state, max_tmp_during_filtration, min_tmp_during_filtration, L_s: float, t_bw_s: float):
+    """
+    计算 UF 超滤系统的核心性能指标
+
+    参数:
+
+        L_s (float): 单次过滤时间(秒)
+        t_bw_s (float): 单次反洗时间(秒)
+
+    返回:
+        dict: {
+            "k_bw_per_ceb": 小周期次数,
+            "ton_water_energy_kWh_per_m3": 吨水电耗,
+            "recovery": 回收率,
+            "net_delivery_rate_m3ph": 净供水率 (m³/h),
+            "daily_prod_time_h": 日均产水时间 (小时/天)
+            "max_permeability": 全周期最高渗透率(lmh/bar)
+        }
+    """
+
+    # 模拟该参数下的超级周期
+    info, next_state = physics.simulate_one_supercycle(current_state, L_s=L_s, t_bw_s=t_bw_s)
+
+    # 获得模型模拟周期信息
+    k_bw_per_ceb = info["k_bw_per_ceb"]
+    ton_water_energy_kWh_per_m3 = info["ton_water_energy_kWh_per_m3"]
+    recovery = info["recovery"]
+    daily_prod_time_h = info["daily_prod_time_h"]
+
+    # 获得模型模拟周期内最高跨膜压差/最低跨膜压差
+    if max_tmp_during_filtration is None:
+        max_tmp_during_filtration = info["max_TMP_during_filtration"]
+    if min_tmp_during_filtration is None:
+        min_tmp_during_filtration = info["min_TMP_during_filtration"]
+
+    # 计算最高渗透率
+    max_permeability = 100 * current_state.q_UF / (128*40) / min_tmp_during_filtration
+
+
+    return {
+        "k_bw_per_ceb": k_bw_per_ceb,
+        "ton_water_energy_kWh_per_m3": ton_water_energy_kWh_per_m3,
+        "recovery": recovery,
+        "daily_prod_time_h": daily_prod_time_h,
+        "max_permeability": max_permeability
+    }
+
+def run_dqn_decide(
+    model_path: Path,
+    physics,
+    # -------- 工厂当前值 --------
+    current_state: UFState
+):
+    """
+    单轮 DQN 决策流程
+    """
+
+    # 构造决策器
+    decider = UFDQNDecider(
+        physics=physics,
+        model_path=model_path,
+        seed=0,
+    )
+    # 模型决策(不推进真实环境)
+
+    decision = decider.decide(current_state)
+    action_id = decision["action_id"]
+    model_L_s = decision["L_s"]
+    model_t_bw_s = decision["t_bw_s"]
+
+    return action_id, model_L_s, model_t_bw_s
+
+
+# ==============================
+# 示例调用
+# ==============================
+if __name__ == "__main__":
+
+    THIS_FILE = Path(__file__).resolve()
+    UF_RL_ROOT = THIS_FILE.parents[3]
+    CONFIG_PATH = UF_RL_ROOT / "config" / "uf_analyze_config.yaml"
+
+    MODEL_PATH = "model/dqn_model.zip"
+    prev_cycle_csv = "online_datasets/UF1_prev_cycle.csv"
+    init_cycle_csv = "online_datasets/UF1_init_cycle.csv"
+
+    # 构建强化学习状态
+    state_builder = DQNStateBuilder(config_path=CONFIG_PATH)
+    current_state: UFState = state_builder.build_from_csv_pair(
+        prev_cycle_csv=prev_cycle_csv,
+        init_cycle_csv=init_cycle_csv,
+    )
+
+    physics = build_physics()
+
+    action_id, model_L_s, model_t_bw_s = run_dqn_decide(
+        model_path=MODEL_PATH,
+        physics=physics,
+        current_state=current_state,
+    ) # 环境实例化,模型加载等功能放在UFDQNDecider类中
+
+    current_L_s = 3800
+    current_t_bw_s = 40
+    model_prev_L_s = 4040
+    model_prev_t_bw_s = 60
+    L_s, t_bw_s = generate_plc_instructions(current_L_s, current_t_bw_s, model_prev_L_s, model_prev_t_bw_s, model_L_s,
+                                            model_t_bw_s)  # 获取模型下发指令
+
+    L_s = 4100
+    t_bw_s = 96
+    max_tmp_during_filtration = 0.050176 # 新增工厂数据接口:周期最高/最低跨膜压差,无工厂数据接入时传入None,calc_uf_cycle_metrics()自动获取模拟周期中的跨膜压差最值
+    min_tmp_during_filtration = 0.012496
+    execution_result = calc_uf_cycle_metrics(current_state, max_tmp_during_filtration, min_tmp_during_filtration, L_s, t_bw_s)
+    print("\n===== 单步决策结果 =====")
+    print(f"模型选择的动作: {action_id}")
+    print(f"模型选择的L_s: {model_L_s} 秒, 模型选择的t_bw_s: {model_t_bw_s} 秒")
+    print(f"指令下发的L_s: {L_s} 秒, 指令下发的t_bw_s: {t_bw_s} 秒")
+    print(f"指令对应的反洗次数: {execution_result['k_bw_per_ceb']}")
+    print(f"指令对应的吨水电耗: {execution_result['ton_water_energy_kWh_per_m3']}")
+    print(f"指令对应的回收率: {execution_result['recovery']}")
+    print(f"指令对应的日均产水时间: {execution_result['daily_prod_time_h']}")
+    print(f"指令对应的最高渗透率: {execution_result['max_permeability']}")
+

+ 219 - 0
models/uf-rl/训练/uf_train/rl_model/DQN/run_dqn_train.py

@@ -0,0 +1,219 @@
+"""
+DQN 超滤强化学习训练与测试主脚本(工程化优化版)
+"""
+
+import os
+import sys
+import random
+from pathlib import Path
+import numpy as np
+import torch
+
+# ============================================================
+# 1. 导入模块
+# ============================================================
+CURRENT_DIR = Path(__file__).resolve().parent
+
+PROJECT_ROOT = CURRENT_DIR.parents[2]     # uf_train  # uf-rl
+
+
+# ---------- 数据 ----------
+from uf_train.data_to_rl.data_splitter import ResetStatePoolLoader
+
+# ---------- 阻力模型 ----------
+from uf_train.env.uf_resistance_models_load import load_resistance_models
+from uf_train.env.uf_physics import UFPhysicsModel
+from uf_train.env.env_params import  UFPhysicsParams, UFStateBounds, UFRewardParams, UFActionSpec
+from uf_train.env.uf_env import UFSuperCycleEnv
+
+from uf_train.env.env_visual import UFEpisodeRecorder, UFTrainingCallback
+
+from uf_train.rl_model.DQN.dqn_params import DQNParams
+from uf_train.rl_model.DQN.dqn_trainer import DQNTrainer
+
+
+# ---------- SB3 ----------
+from stable_baselines3.common.monitor import Monitor
+from stable_baselines3.common.vec_env import DummyVecEnv
+
+
+
+
+
+# ============================================================
+#  随机种子
+# ============================================================
+def set_global_seed(seed: int):
+    random.seed(seed)
+    np.random.seed(seed)
+    torch.manual_seed(seed)
+    torch.cuda.manual_seed_all(seed)
+
+    torch.backends.cudnn.deterministic = True
+    torch.backends.cudnn.benchmark = False
+
+    print(f"[Seed] Global random seed = {seed}")
+
+
+# ============================================================
+# 4. Reset State Pool 加载与划分
+# ============================================================
+def load_reset_state_pools():
+    loader = ResetStatePoolLoader(
+        csv_path=RESET_STATE_CSV,
+        train_ratio=0.8,
+        shuffle=True,
+        random_state=RANDOM_SEED,
+    )
+
+    train_pool, val_pool = loader.split()
+
+    print("[Data] Reset state pool loaded")
+    print(f"       Train pool size: {len(train_pool)}")
+    print(f"       Val   pool size: {len(val_pool)}")
+
+    return train_pool, val_pool
+
+
+# ============================================================
+# 5. 环境构造函数
+# ============================================================
+def make_env(
+    physics: UFPhysicsModel,
+    reward_params: UFRewardParams,
+    action_spec: UFActionSpec,
+    statebounds: UFStateBounds,
+    reset_state_pool,
+    seed: int,
+):
+    def _init():
+        env = UFSuperCycleEnv(
+            physics=physics,
+            reward_params=reward_params,
+            action_spec=action_spec,
+            statebounds=statebounds,
+            real_state_pool=reset_state_pool,
+            RANDOM_SEED=seed,
+        )
+        env.action_space.seed(seed)
+        env.observation_space.seed(seed)
+        return Monitor(env)
+    return _init
+
+
+
+# ============================================================
+# 6. 主流程
+# ============================================================
+def main():
+    # ---------- Seed ----------
+    set_global_seed(RANDOM_SEED)
+
+    # ---------- Reset states ----------
+    train_pool, val_pool = load_reset_state_pools()
+
+    # ---------- Resistance models ----------
+    phys_params = UFPhysicsParams()
+    res_fp, res_bw = load_resistance_models(phys_params)
+
+    # ---------- Physics ----------
+    physics_model = UFPhysicsModel(
+        phys_params=phys_params,
+        resistance_model_fp=res_fp,
+        resistance_model_bw=res_bw,
+    )
+
+    # ---------- RL specs ----------
+    reward_params = UFRewardParams()
+    action_spec = UFActionSpec()
+    state_bounds = UFStateBounds()
+
+    # ---------- Environments ----------
+    train_env = DummyVecEnv([
+        make_env(
+            physics_model,
+            reward_params,
+            action_spec,
+            state_bounds,
+            train_pool,
+            RANDOM_SEED,
+        )
+    ])
+
+    val_env = DummyVecEnv([
+        make_env(
+            physics_model,
+            reward_params,
+            action_spec,
+            state_bounds,
+            val_pool,
+            RANDOM_SEED,
+        )
+    ])
+
+    # ---------- Callback ----------
+    recorder = UFEpisodeRecorder()
+    callback = UFTrainingCallback(recorder, verbose=1)
+
+    # ---------- Trainer ----------
+    dqn_params = DQNParams(remark="uf_dqn_real_reset")
+    trainer = DQNTrainer(
+        env=train_env,
+        params=dqn_params,
+        callback=callback,
+        PROJECT_ROOT=PROJECT_ROOT
+    )
+
+
+    # ---------- Training ----------
+    print("\n Start training")
+    trainer.train(total_timesteps=TOTAL_TIMESTEPS)
+    trainer.save()
+
+    # ========================================================
+    # 验证
+    # ========================================================
+    print("\n[Eval] Start validation rollout")
+
+    rewards = []
+
+    for _ in range(len(val_pool)):
+        obs = val_env.reset()
+        episode_reward = 0.0
+
+        for _ in range(10):
+            action, _ = trainer.model.predict(
+                obs, deterministic=True
+            )
+            obs, reward, done, _ = val_env.step(action)
+            episode_reward += reward[0]
+            if done:
+                break
+
+        rewards.append(episode_reward)
+
+    rewards = np.asarray(rewards)
+
+    save_path = Path(trainer.log_dir) / "val_rewards.npy"
+    np.save(save_path, rewards)
+
+    print(f"[Eval] Saved to {save_path}")
+    print(f"[Eval] Mean reward = {rewards.mean():.3f}")
+
+
+# ============================================================
+# 入口
+# ============================================================
+if __name__ == "__main__":
+    # ============================================================
+    # 2. 全局配置
+    # ============================================================
+    RANDOM_SEED = 2025
+    TOTAL_TIMESTEPS = 1500000
+
+    RESET_STATE_CSV = (
+            PROJECT_ROOT
+            / "datasets/rl_ready/output/reset_state_pool.csv"
+    )
+
+    main()

+ 0 - 0
models/uf-rl/训练/uf_train/rl_model/__init__.py


+ 161 - 0
models/uf-rl/训练/进水动作版超滤训练源码/UF_resistance_models.py

@@ -0,0 +1,161 @@
+"""
+超滤膜阻力模型模块
+====================
+本模块定义了超滤膜阻力的动态变化模型,包括:
+1. ResistanceIncreaseModel: 过滤阶段膜阻力上升模型
+2. ResistanceDecreaseModel: 反洗阶段膜阻力下降模型
+
+这些模型用于模拟超滤膜在运行过程中的阻力变化,是强化学习环境的核心组件。
+"""
+
+import torch
+import numpy as np
+
+
+# ==================== 膜阻力上升模型 ====================
+class ResistanceIncreaseModel(torch.nn.Module):
+    """
+    过滤阶段膜阻力上升模型
+    
+    功能说明:
+    - 计算在过滤阶段膜阻力的增长量 ΔR
+    - 膜阻力上升主要由污染物在膜表面的累积引起
+    - 阻力增长速率与膜通量(J)和过滤时长(L_s)相关
+    
+    模型公式:
+        ΔR = nuK × J × L_s
+        其中:
+        - nuK: 膜阻力增长系数(反映水质污染特性)
+        - J: 膜通量 = q_UF / A / 3600 [m/s]
+        - L_s: 过滤时长 [秒]
+    """
+    
+    def __init__(self):
+        """初始化膜阻力上升模型(无需训练参数)"""
+        super().__init__()
+
+    def forward(self, p, L_s):
+        """
+        前向传播:计算膜阻力上升量
+        
+        参数:
+            p (UFParams): 超滤运行参数对象,包含:
+                - q_UF: 过滤进水流量 [m³/h]
+                - nuK: 膜阻力增长系数 [m⁻¹/s]
+            L_s (float): 过滤时长 [秒]
+        
+        返回:
+            float: 膜阻力上升量 ΔR(已缩放1e10)
+        
+        注意:
+            - 实际膜阻力量级为1e12,为便于数值计算已缩放至1e2量级
+            - 膜面积 A = 128组 × 40 m²/组 = 5120 m²
+        """
+        # 计算膜有效面积(锡山水厂配置:128组膜,每组40m²)
+        A = 128 * 40.0  # [m²]
+        
+        # 计算膜通量 J = 流量 / 面积 / 时间单位转换
+        # q_UF [m³/h] → J [m³/(m²·s)]
+        J = p.q_UF / A / 3600  # [m/s]
+        
+        # 膜阻力上升模型(线性模型,已缩放)
+        # nuK: 阻力增长速率,反映水质污染特性
+        # J: 膜通量,通量越大污染速率越快
+        # L_s: 过滤时间,时间越长累积污染越多
+        dR = p.nuK * J * L_s  # [缩放后的阻力单位]
+        
+        return float(dR)
+
+
+# ==================== 膜阻力下降模型 ====================
+class ResistanceDecreaseModel(torch.nn.Module):
+    """
+    反洗阶段膜阻力下降模型
+    
+    功能说明:
+    - 计算物理反冲洗能够去除的膜阻力量
+    - 区分可逆污染和不可逆污染
+    - 反洗时长影响去除效率
+    
+    模型原理:
+    1. 膜污染分为两类:
+       - 可逆污染:可通过物理反洗去除(如表面颗粒物)
+       - 不可逆污染:无法通过物理反洗去除(如孔内吸附污染)
+    
+    2. 不可逆污染累积模型:
+       R_irr = R0 + slope × t^power
+       其中 t 为累积运行时间
+    
+    3. 反洗效率模型:
+       time_gain = 1 - exp(-t_bw / τ)
+       反洗时间越长,去除效率越高,但存在上限
+    """
+    
+    def __init__(self):
+        """初始化膜阻力下降模型(无需训练参数)"""
+        super().__init__()
+
+    def forward(self, p, R0, R_end, L_h_next_start, t_bw_s):
+        """
+        前向传播:计算物理反洗能够去除的膜阻力
+        
+        参数:
+            p (UFParams): 超滤运行参数对象,包含:
+                - slope: 不可逆污染增长斜率
+                - power: 不可逆污染增长幂次
+                - tau_bw_s: 反洗时长影响的时间尺度
+            R0 (float): 本超级周期初始膜阻力
+            R_end (float): 过滤结束时的膜阻力(峰值)
+            L_h_next_start (float): 下一小周期起始时的累积运行时间 [小时]
+            t_bw_s (float): 物理反洗时长 [秒]
+        
+        返回:
+            float: 物理反洗实际去除的膜阻力量
+        
+        计算步骤:
+            1. 基于长期污染模型计算本周期的不可逆污染增量
+            2. 计算可逆污染量 = 当前总污染 - 不可逆污染
+            3. 应用时间因子(反洗时长的影响)
+            4. 返回实际去除的阻力(不超过可逆污染量)
+        """
+        # ========== 步骤1:计算不可逆污染累积 ==========
+        # 使用幂律模型描述长期不可逆污染的累积
+        # R_irr(t) = R0 + slope × t^power
+
+        
+        # 下一小周期开始时的理论膜阻力
+        delta_R = p.slope * (L_h_next_start ** p.power)
+        R_next_start = R0 + delta_R
+
+        # 这部分污染可以通过物理反洗去除
+        reversible_R = max(R_end - R_next_start, 0.0)
+        
+        # ========== 步骤3:计算反洗时间效率因子 ==========
+        # 使用指数衰减模型:time_gain = 1 - exp(-t_bw / τ)
+        # τ (tau_bw_s): 时间尺度参数
+        # - 反洗时间 t_bw = 0 时,time_gain = 0(无去除效果)
+        # - 反洗时间 t_bw → ∞ 时,time_gain → 1(达到最大效率)
+        # - 反洗时间 t_bw = τ 时,time_gain ≈ 0.632(去除63.2%)
+        # τ参考计算: 以60s去除效率高于95%的要求推算得τ取20s
+        # 该取值下:
+        # - 反洗时间 t_bw = 40s 时,time_gain ≈ 0.865(去除86.5%)
+        # - 反洗时间 t_bw = 60s 时,time_gain ≈ 0.950(去除95.0%)
+        time_gain = 1.0 - np.exp(- (t_bw_s / p.tau_bw_s))
+        
+        # ========== 步骤4:计算实际去除的膜阻力 ==========
+        # 实际去除量 = 可逆污染量 × 时间效率因子
+        dR_bw = reversible_R * time_gain
+        
+        # 确保去除量不超过可逆污染总量(物理约束)
+        return float(np.clip(dR_bw, 0.0, reversible_R))
+
+
+# ===== 主程序 =====
+if __name__ == "__main__":
+    model_fp = ResistanceIncreaseModel()
+    model_bw = ResistanceDecreaseModel()
+
+    torch.save(model_fp.state_dict(), "resistance_model_fp.pth")
+    torch.save(model_bw.state_dict(), "resistance_model_bw.pth")
+
+    print("模型已安全保存为 resistance_model_fp.pth、resistance_model_bw.pth")

+ 233 - 0
models/uf-rl/训练/进水动作版超滤训练源码/fixed_DQN_decide.py

@@ -0,0 +1,233 @@
+import numpy as np
+from stable_baselines3 import DQN
+from fixed_DQN_env import UFSuperCycleEnv, UFParams
+from fixed_DQN_env import simulate_one_supercycle
+
+# 模型路径
+MODEL_PATH = "model/dqn_model.zip"
+
+# 加载模型(只加载一次,提高效率)
+model = DQN.load(MODEL_PATH)
+
+def run_uf_DQN_decide(uf_params, TMP0_value: float):
+    """
+    单步决策函数(新版):
+    当前模型只输出进水时间 L_s,不输出反洗时间。
+    """
+
+    # 1. 初始化环境
+    env = UFSuperCycleEnv(uf_params)
+
+    # 2. 设置 TMP0
+    env.current_params.TMP0 = TMP0_value
+
+    # 3. 获取观察(归一化)
+    obs = env._get_obs().reshape(1, -1)
+
+    # 4. 模型预测动作
+    action, _ = model.predict(obs, deterministic=True)
+
+    # 5. 新模型动作只返回 L_s
+    L_s = env._get_action_values(action[0])   # 单值
+
+    # 6. 在环境中执行动作
+    next_obs, reward, terminated, truncated, info = env.step(action[0])
+
+    # 7. 返回结构化结果
+    return {
+        "action": int(action[0]),
+        "L_s": float(L_s),
+        "t_bw_s": None,       # 保留字段但固定为 None
+        "next_obs": next_obs,
+        "reward": reward,
+        "terminated": terminated,
+        "truncated": truncated,
+        "info": info
+    }
+
+def generate_plc_instructions(current_L_s, model_prev_L_s, model_L_s):
+    """
+    根据工厂当前值、模型上一轮决策值和模型当前轮决策值,生成PLC指令。
+
+    新增功能:
+    1. 处理None值情况:如果模型上一轮值为None,则使用工厂当前值;
+       如果工厂当前值也为None,则返回None并提示错误。
+    """
+    # 参数配置保持不变
+    params = UFParams(
+        L_min_s=3600.0, L_max_s=4800.0, L_step_s=60.0,
+    )
+
+    # 参数解包
+    L_step_s = params.L_step_s
+    L_min_s = params.L_min_s
+    L_max_s = params.L_max_s
+    adjustment_threshold = 1.0
+
+    # 处理None值情况
+    if model_prev_L_s is None:
+        if current_L_s is None:
+            print("错误: 过滤时长的工厂当前值和模型上一轮值均为None")
+            return None, None
+        else:
+            # 使用工厂当前值作为基准
+            effective_current_L = current_L_s
+            source_L = "工厂当前值(模型上一轮值为None)"
+    else:
+        # 模型上一轮值不为None,继续检查工厂当前值
+        if current_L_s is None:
+            effective_current_L = model_prev_L_s
+            source_L = "模型上一轮值(工厂当前值为None)"
+        else:
+            effective_current_L = model_prev_L_s
+            source_L = "模型上一轮值"
+
+
+    # 检测所有输入值是否在规定范围内(只对非None值进行检查)
+    # 工厂当前值检查(警告)
+    if current_L_s is not None and not (L_min_s <= current_L_s <= L_max_s):
+        print(f"警告: 当前过滤时长 {current_L_s} 秒不在允许范围内 [{L_min_s}, {L_max_s}]")
+
+    # 模型上一轮决策值检查(警告)
+    if model_prev_L_s is not None and not (L_min_s <= model_prev_L_s <= L_max_s):
+        print(f"警告: 模型上一轮过滤时长 {model_prev_L_s} 秒不在允许范围内 [{L_min_s}, {L_max_s}]")
+
+    # 模型当前轮决策值检查(错误)
+    if model_L_s is None:
+        raise ValueError("错误: 决策模型建议的过滤时长不能为None")
+    elif not (L_min_s <= model_L_s <= L_max_s):
+        raise ValueError(f"错误: 决策模型建议的过滤时长 {model_L_s} 秒不在允许范围内 [{L_min_s}, {L_max_s}]")
+
+    print(f"过滤时长基准: {source_L}, 值: {effective_current_L}")
+
+    # 使用选定的基准值进行计算调整
+    L_diff = model_L_s - effective_current_L
+    L_adjustment = 0
+    if abs(L_diff) >= adjustment_threshold * L_step_s:
+        if L_diff >= 0:
+            L_adjustment = L_step_s
+        else:
+            L_adjustment = -L_step_s
+    next_L_s = effective_current_L + L_adjustment
+
+    return next_L_s
+
+
+def calc_uf_cycle_metrics(p, TMP0, max_tmp_during_filtration, min_tmp_during_filtration, L_s: float, t_bw_s):
+    """
+    计算 UF 超滤系统的核心性能指标
+
+    参数:
+        p (UFParams): UF 系统参数
+        L_s (float): 单次过滤时间(秒)
+        t_bw_s (float): 单次反洗时间(秒)
+
+    返回:
+        dict: {
+            "k_bw_per_ceb": 小周期次数,
+            "ton_water_energy_kWh_per_m3": 吨水电耗,
+            "recovery": 回收率,
+            "net_delivery_rate_m3ph": 净供水率 (m³/h),
+            "daily_prod_time_h": 日均产水时间 (小时/天)
+            "max_permeability": 全周期最高渗透率(lmh/bar)
+        }
+    """
+    # 将跨膜压差写入参数
+    p.TMP0 = TMP0
+
+    # 模拟该参数下的超级周期
+    info, next_params = simulate_one_supercycle(p, L_s, t_bw_s)
+
+    # 获得模型模拟周期信息
+    k_bw_per_ceb = info["k_bw_per_ceb"]
+    ton_water_energy_kWh_per_m3 = info["ton_water_energy_kWh_per_m3"]
+    recovery = info["recovery"]
+    daily_prod_time_h = info["daily_prod_time_h"]
+
+    # 获得模型模拟周期内最高跨膜压差/最低跨膜压差
+    if max_tmp_during_filtration is None:
+        max_tmp_during_filtration = info["max_TMP_during_filtration"]
+    if min_tmp_during_filtration is None:
+        min_tmp_during_filtration = info["min_TMP_during_filtration"]
+
+    # 计算最高渗透率
+    max_permeability = 100 * p.q_UF / (128*40) / min_tmp_during_filtration
+
+
+    return {
+        "k_bw_per_ceb": k_bw_per_ceb,
+        "ton_water_energy_kWh_per_m3": ton_water_energy_kWh_per_m3,
+        "recovery": recovery,
+        "daily_prod_time_h": daily_prod_time_h,
+        "max_permeability": max_permeability
+    }
+
+
+# ==============================
+# 示例调用
+# ==============================
+if __name__ == "__main__":
+    # -------------------------
+    # 1. 初始化参数
+    # -------------------------
+    uf_params = UFParams()
+    TMP0 = 0.01   # 原始跨膜压差
+
+    # -------------------------
+    # 2. 调用模型做一次决策(只输出 L_s)
+    # -------------------------
+    model_decide_result = run_uf_DQN_decide(uf_params, TMP0)
+    model_L_s = model_decide_result["L_s"]     # 只输出 L_s
+    print(f"模型决策进水时长 L_s = {model_L_s}")
+
+    # -------------------------
+    # 3. 工厂当前值 + 模型上一轮值(示例值)
+    # -------------------------
+    current_L_s = 3800
+    model_prev_L_s = 4040
+
+    # -------------------------
+    # 4. 生成 PLC 指令(新版仅 L_s)
+    # -------------------------
+    plc_L_s = generate_plc_instructions(current_L_s,model_prev_L_s,model_L_s)
+
+    print(f"PLC 指令 L_s = {plc_L_s}")
+
+    # -------------------------
+    # 5. 反洗时长由工厂参数决定/固定值
+    #    (新模型不输出 t_bw_s)
+    # -------------------------
+    plc_t_bw_s = uf_params.fixed_t_bw_s
+
+    # -------------------------
+    # 6. 工厂 TMP 最大/最小(可为空 None)
+    # -------------------------
+    max_tmp_during_filtration = 0.050176
+    min_tmp_during_filtration = 0.012496
+
+    # -------------------------
+    # 7. 计算周期指标(模型动作实际效果)
+    # -------------------------
+    execution_result = calc_uf_cycle_metrics(
+        p=uf_params,
+        TMP0=TMP0,
+        max_tmp_during_filtration=max_tmp_during_filtration,
+        min_tmp_during_filtration=min_tmp_during_filtration,
+        L_s=plc_L_s,
+        t_bw_s=plc_t_bw_s   # 仍需要反洗时长参数
+    )
+
+    # -------------------------
+    # 8. 打印结果
+    # -------------------------
+    print("\n===== 单步决策结果 =====")
+    print(f"模型动作编号: {model_decide_result['action']}")
+    print(f"模型选择的 L_s: {model_L_s} 秒")
+    print(f"PLC 下发 L_s: {plc_L_s} 秒")
+    print(f"PLC 下发反洗时长(固定) t_bw_s: {plc_t_bw_s} 秒")
+
+    print(f"周期对应的反洗次数: {execution_result['k_bw_per_ceb']}")
+    print(f"吨水电耗: {execution_result['ton_water_energy_kWh_per_m3']}")
+    print(f"回收率: {execution_result['recovery']}")
+    print(f"日均产水时间: {execution_result['daily_prod_time_h']}")
+    print(f"最高渗透率: {execution_result['max_permeability']}")

+ 1052 - 0
models/uf-rl/训练/进水动作版超滤训练源码/fixed_DQN_env.py

@@ -0,0 +1,1052 @@
+"""
+超滤强化学习环境模块
+========================
+本模块定义了超滤系统的强化学习环境,包括:
+1. UFParams: 超滤系统参数配置类
+2. 膜阻力与跨膜压差转换函数
+3. simulate_one_supercycle: 超级周期模拟函数
+4. calculate_reward: 奖励函数
+5. is_dead_cycle: 失败判定函数
+6. UFSuperCycleEnv: Gymnasium环境类
+
+模块设计说明:
+- 基于 Gymnasium (原OpenAI Gym) 标准接口
+- 模拟超滤膜的"超级周期"运行(多次物理反洗 + 一次化学反洗)
+- 强化学习智能体通过优化过滤时长和反洗时长来最大化回收率并控制污染累积
+"""
+
+import os
+import torch
+from pathlib import Path
+import numpy as np
+import gymnasium as gym
+from gymnasium import spaces
+from typing import Dict, Tuple, Optional
+import torch
+import torch.nn as nn
+from dataclasses import dataclass, asdict
+from UF_resistance_models import ResistanceIncreaseModel, ResistanceDecreaseModel  # 导入膜阻力模型
+import copy
+
+
+# ==================== 超滤系统参数配置类 ====================
+@dataclass
+class UFParams:
+    """
+    超滤系统参数配置类
+    
+    功能:统一管理超滤系统的所有运行参数,包括:
+    - 膜动态运行参数(流量、温度、压差等)
+    - 膜阻力模型参数(污染增长速率、去除效率等)
+    - 膜运行约束参数(各参数的上下限)
+    - 反洗参数(物理反洗、化学反洗)
+    - 动作搜索范围(过滤时长、反洗时长的取值范围)
+    - 奖励函数参数
+    
+    设计思想:
+    - 使用 dataclass 装饰器,自动生成 __init__、__repr__ 等方法
+    - 所有参数带有类型注解和默认值
+    - 参数值基于锡山水厂的实际运行数据和经验
+    """
+    # ========== 膜动态运行参数 ==========
+    # 这些参数描述超滤膜的实时运行状态,在环境模拟中会动态变化
+    
+    q_UF: float = 360.0  
+    # 过滤进水流量(m³/h)
+    # 说明:影响膜通量,进而影响污染速率
+    # 典型范围:250-400 m³/h
+    
+    TMP0: float = 0.03 
+    # 初始跨膜压差(MPa,兆帕)
+    # 说明:反映膜阻力状态,TMP 越高表示膜污染越严重
+    # 正常范围:0.01-0.035 MPa,超过 0.08 MPa 需停机检修
+    
+    temp: float = 25.0  
+    # 水温(摄氏度)
+    # 说明:影响水的粘度,进而影响跨膜压差
+    # 典型范围:10-40℃,25℃为标准温度
+
+    # ========== 膜阻力模型参数 ==========
+    # 这些参数描述膜污染的物理化学特性,基于历史数据拟合得到
+    
+    nuK: float = 6.92e+01
+    # 过滤阶段膜阻力增长系数(缩放后单位)
+    # 说明:反映水质污染特性,nuK 越大表示水质越差、膜污染越快
+    # 物理意义:单位膜通量、单位时间的阻力增长速率
+    
+    slope: float = 3.44e-01 
+    # 全周期不可逆污染增长斜率
+    # 说明:描述长期不可逆污染的累积速率(幂律模型的系数)
+    
+    power: float = 1.032 
+    # 全周期不可逆污染增长幂次
+    # 说明:描述长期污染的非线性特性(幂律模型的指数)
+    # power > 1 表示污染加速累积,power < 1 表示污染增速放缓
+    
+    tau_bw_s: float = 30.0  
+    # 物理反洗时长影响的时间尺度(秒)
+    # 说明:反洗效率的特征时间,当反洗时长 = tau 时,达到约 63% 效率
+    
+    gamma_t: float = 1.0  
+    # 物理反洗时长作用指数(保留参数,当前未使用)
+    
+    ceb_removal: float = 150  
+    # 化学增强反洗(CEB)可去除的膜阻力(缩放后单位)
+    # 说明:CEB 比物理反洗更彻底,可去除部分不可逆污染
+
+    # ========== 膜运行约束参数 ==========
+    # 定义各运行参数的物理约束和安全限制
+    
+    global_TMP_hard_limit: float = 0.08
+    # TMP 硬上限(MPa)
+    # 说明:超过此值将导致episode失败,需立即停机
+
+    global_TMP_soft_limit: float = 0.06
+    # TMP 软上限 (MPa)
+    # 说明:此上限用于指导奖励函数中膜阻力允许上升值,越接近该上限,系统对膜阻力上升控制的更严格
+
+    # --- 初始TMP约束 ---
+    TMP0_max: float = 0.035  # 初始TMP上限(MPa)
+    TMP0_min: float = 0.01   # 初始TMP下限(MPa)
+    
+    # --- 流量约束 ---
+    q_UF_max: float = 400.0  # 进水流量上限(m³/h)
+    q_UF_min: float = 250.0  # 进水流量下限(m³/h)
+    
+    # --- 温度约束 ---
+    temp_max: float = 40.0   # 温度上限(℃)
+    temp_min: float = 10.0   # 温度下限(℃)
+    
+    # --- 短期污染模型参数约束 ---
+    nuK_max: float = 1e+02   # 阻力增长系数上限
+    nuK_min: float = 6e+01   # 阻力增长系数下限
+    
+    # --- 长期污染模型参数约束 ---
+    slope_max: float = 10    # 不可逆污染斜率上限
+    slope_min: float = 0.5   # 不可逆污染斜率下限
+    power_max: float = 1.5   # 不可逆污染幂次上限
+    power_min: float = 0.5   # 不可逆污染幂次下限
+    
+    # --- CEB去除能力约束 ---
+    ceb_removal_max: float = 200  # CEB去除阻力上限(缩放后)
+    ceb_removal_min: float = 100  # CEB去除阻力下限(缩放后)
+
+    # ========== 反洗参数(固定配置) ==========
+    q_bw_m3ph: float = 1000.0
+    # 物理反洗流量(m³/h)
+    # 说明:反洗流量通常为正常过滤流量的 2-3 倍
+    fixed_t_bw_s = 60 # 固定物理反洗时间
+
+    # ========== CEB 化学反洗参数 ==========
+    T_ceb_interval_h: float = 48.0
+    # CEB 间隔时间(小时)
+    # 说明:每运行约 60 小时执行一次化学增强反洗
+
+    v_ceb_m3: float = 20.0
+    # CEB 用水体积(m³)
+
+    t_ceb_s: float = 40 * 60.0
+    # CEB 时长(秒,这里为 40 分钟)
+
+    # ========== 强化学习动作空间搜索范围 ==========
+    # 定义智能体可选择的动作范围(离散化)
+
+    L_min_s: float = 3800.0  # 过滤时长下限(秒,约 63 分钟)
+    L_max_s: float = 4800.0  # 过滤时长上限,改为 4800s
+    t_bw_min_s: float = 40.0  # 物理反洗时长下限(秒)
+    t_bw_max_s: float = 60.0  # 物理反洗时长上限(秒)
+
+    # ========== 动作离散化网格 ==========
+    L_step_s: float = 60.0  # 过滤时长步长(秒)
+    t_bw_step_s: float = 5.0  # 物理反洗时长步长(秒)
+
+    # ========== 奖励函数参数 ==========
+    k_rec = 5.0  # 回收率敏感度系数(控制回收率奖励的陡峭程度)
+    k_res = 10.0  # 残余污染敏感度系数(控制污染惩罚的陡峭程度)
+    rec_low, rec_high = 0.92, 0.99  # 回收率的正常范围
+    rr0 = 0.08  # 残余污染比例的参考值
+
+
+# ==================== 辅助函数:膜阻力与跨膜压差转换 ====================
+
+def xishan_viscosity(temp):
+    """
+    锡山水厂水温粘度修正公式
+    
+    功能:根据水温计算水的动力粘度(考虑温度影响)
+    
+    参数:
+        temp (float): 水温(摄氏度)
+    
+    返回:
+        float: 水的动力粘度 μ (Pa·s)
+    
+    原理:
+    - 水的粘度随温度升高而降低
+    - 25℃时纯水粘度约为 0.00089 Pa·s
+    - 本公式基于锡山水厂PLC系统的经验修正因子
+    
+    注意:
+    - 本公式基于纯水粘度修正
+    - 实际水厂水质与纯水有差异,对粘度有一定影响
+    - 未来可根据实际水质进一步校准
+    """
+    # 温度归一化(相对于300K)
+    x = (temp + 273.15) / 300  # 摄氏度转开尔文
+    
+    # 温度修正因子(经验公式,基于锡山水厂PLC)
+    factor = 890 / (
+        280.68 * x ** -1.9 + 
+        511.45 * x ** -7.7 + 
+        61.131 * x ** -19.6 + 
+        0.45903 * x ** -40
+    )
+    
+    # 计算修正后的粘度(25℃标准粘度 / 修正因子)
+    mu = 0.00089 / factor  # [Pa·s]
+    
+    return mu
+
+
+def _calculate_resistance(tmp, q_UF, temp):
+    """
+    由跨膜压差计算膜阻力
+    
+    功能:根据 Darcy 定律,由跨膜压差反推膜阻力
+    
+    参数:
+        tmp (float): 跨膜压差 TMP (MPa)
+        q_UF (float): 过滤流量 (m³/h)
+        temp (float): 水温 (℃)
+    
+    返回:
+        float: 膜阻力 R(已缩放 1e10)
+    
+    原理:
+        Darcy 定律:J = TMP / (μ × R)
+        其中:
+        - J: 膜通量 [m/s]
+        - TMP: 跨膜压差 [Pa]
+        - μ: 水的动力粘度 [Pa·s]
+        - R: 膜阻力 [m⁻¹]
+        
+        反解得:R = TMP / (J × μ)
+    
+    注意:
+        - 超滤膜阻力实际量级为 1e12 m⁻¹
+        - 为便于数值计算,已缩放 1e10 倍至 1e2 量级
+    """
+    # 膜有效面积(锡山水厂配置:128组 × 40 m²/组)
+    A = 128 * 40  # [m²]
+    
+    # 温度修正后的水粘度
+    mu = xishan_viscosity(temp)  # [Pa·s]
+    
+    # 跨膜压差单位转换:MPa → Pa
+    TMP_Pa = tmp * 1e6  # [Pa]
+    
+    # 计算膜通量:流量 / 面积
+    J = q_UF / A / 3600  # [m³/h] → [m³/(m²·s)] = [m/s]
+    
+    # 物理约束检查:通量和粘度必须为正
+    if J <= 0 or mu <= 0:
+        return np.nan
+    
+    # 根据 Darcy 定律计算膜阻力并缩放
+    R = TMP_Pa / (J * mu) / 1e10  # [m⁻¹] → [缩放单位]
+
+    return float(R)
+
+
+def _calculate_tmp(R, q_UF, temp):
+    """
+    由膜阻力计算跨膜压差
+    
+    功能:根据 Darcy 定律,由膜阻力计算跨膜压差(_calculate_resistance 的逆运算)
+    
+    参数:
+        R (float): 膜阻力(已缩放 1e10)
+        q_UF (float): 过滤流量 (m³/h)
+        temp (float): 水温 (℃)
+    
+    返回:
+        float: 跨膜压差 TMP (MPa)
+    
+    原理:
+        Darcy 定律:TMP = J × μ × R
+        其中:
+        - J: 膜通量 [m/s]
+        - μ: 水的动力粘度 [Pa·s]
+        - R: 膜阻力 [m⁻¹]
+    """
+    # 膜有效面积
+    A = 128 * 40  # [m²]
+    
+    # 温度修正后的水粘度
+    mu = xishan_viscosity(temp)  # [Pa·s]
+    
+    # 计算膜通量
+    J = q_UF / A / 3600  # [m/s]
+    
+    # 根据 Darcy 定律计算跨膜压差(还原缩放)
+    TMP_Pa = R * J * mu * 1e10  # [缩放单位] → [Pa]
+    
+    # 单位转换:Pa → MPa
+    tmp = TMP_Pa / 1e6  # [MPa]
+
+    return float(tmp)
+
+
+# ==================== 膜阻力模型加载函数 ====================
+
+def load_resistance_models():
+    """
+    加载膜阻力预测模型(单例模式)
+    
+    功能:
+    - 加载预训练的膜阻力上升模型和下降模型
+    - 使用全局变量实现单例模式,避免重复加载
+    - 仅在首次调用时执行加载操作
+    
+    返回:
+        tuple: (resistance_model_fp, resistance_model_bw)
+            - resistance_model_fp: 过滤阶段阻力上升模型
+            - resistance_model_bw: 反洗阶段阻力下降模型
+    
+    注意:
+    - 模型文件必须与本脚本位于同一目录
+    - 模型已设置为推理模式(eval),不会更新参数
+    """
+    # 声明全局变量(实现单例模式)
+    global resistance_model_fp, resistance_model_bw
+
+    # 检查模型是否已加载(避免重复加载)
+    if "resistance_model_fp" in globals() and resistance_model_fp is not None:
+        return resistance_model_fp, resistance_model_bw
+
+    print("🔄 正在加载膜阻力模型...")
+
+    # 初始化模型对象
+    resistance_model_fp = ResistanceIncreaseModel()
+    resistance_model_bw = ResistanceDecreaseModel()
+
+    # 获取当前脚本所在目录
+    base_dir = Path(__file__).resolve().parent
+
+    # 构造模型文件路径
+    fp_path = base_dir / "resistance_model_fp.pth"   # 过滤阶段模型
+    bw_path = base_dir / "resistance_model_bw.pth"   # 反洗阶段模型
+
+    # 检查模型文件是否存在
+    assert fp_path.exists(), f"缺少膜阻力上升模型文件: {fp_path.name}"
+    assert bw_path.exists(), f"缺少膜阻力下降模型文件: {bw_path.name}"
+
+    # 加载模型权重(map_location="cpu" 确保在没有GPU的环境也能运行)
+    resistance_model_fp.load_state_dict(torch.load(fp_path, map_location="cpu"))
+    resistance_model_bw.load_state_dict(torch.load(bw_path, map_location="cpu"))
+
+    # 设置为推理模式(禁用 dropout、batchnorm 等训练特性)
+    resistance_model_fp.eval()
+    resistance_model_bw.eval()
+
+    print("✅ 膜阻力模型加载成功!")
+    return resistance_model_fp, resistance_model_bw
+
+
+# ==================== 膜阻力模型调用函数 ====================
+
+def _delta_resistance(p, L_s: float) -> float:
+    """
+    计算过滤阶段膜阻力上升量
+    
+    功能:调用预训练的膜阻力上升模型
+    
+    参数:
+        p (UFParams): 超滤运行参数
+        L_s (float): 过滤时长(秒)
+    
+    返回:
+        float: 膜阻力上升量 ΔR
+    """
+    return resistance_model_fp(p, L_s)
+
+
+def phi_bw_of(p, R0: float, R_end: float, L_h_next_start: float, t_bw_s: float) -> float:
+    """
+    计算物理反洗可去除的膜阻力
+    
+    功能:调用预训练的膜阻力下降模型
+    
+    参数:
+        p (UFParams): 超滤运行参数
+        R0 (float): 本超级周期初始膜阻力
+        R_end (float): 过滤结束时的膜阻力
+        L_h_start (float): 本小周期起始累积运行时间(小时)
+        L_h_next_start (float): 下一小周期起始累积运行时间(小时)
+        t_bw_s (float): 物理反洗时长(秒)
+    
+    返回:
+        float: 物理反洗去除的膜阻力量
+    """
+    return resistance_model_bw(p, R0, R_end, L_h_next_start, t_bw_s)
+
+
+def _v_bw_m3(p, t_bw_s: float) -> float:
+    """
+    计算物理反洗水耗
+    
+    参数:
+        p (UFParams): 超滤运行参数(包含反洗流量 q_bw_m3ph)
+        t_bw_s (float): 物理反洗时长(秒)
+    
+    返回:
+        float: 反洗水耗(立方米)
+    
+    公式:
+        V = Q × t
+        其中 Q 为反洗流量 [m³/h],t 为反洗时长 [h]
+    """
+    return float(p.q_bw_m3ph * (float(t_bw_s) / 3600.0))
+
+def simulate_one_supercycle(p: UFParams, L_s: float, t_bw_s: float):
+    """
+    模拟一个完整的超级周期(Super Cycle)
+    
+    功能:
+    - 模拟超滤膜的完整运行周期:多次"过滤+物理反洗"小周期 + 一次化学增强反洗(CEB)
+    - 计算周期内的各项性能指标(回收率、TMP变化、污染累积等)
+    - 返回周期结束后的状态参数,用于下一周期的模拟
+    
+    参数:
+        p (UFParams): 当前超滤系统参数
+        L_s (float): 过滤时长(秒)
+        t_bw_s (float): 物理反洗时长(秒)
+    
+    返回:
+        tuple: (info, next_params)
+            - info (dict): 本周期的性能指标字典
+            - next_params (UFParams): 更新后的系统参数(用于下一周期)
+    
+    超级周期结构:
+        超级周期 = k 次小周期 + 1 次 CEB
+        小周期 = 过滤 + 物理反洗
+        
+        时间轴:
+        |-- 小周期1 --|-- 小周期2 --|-- ... --|-- 小周期k --|-- CEB --|
+        | 滤 | 物洗 | 滤 | 物洗 |  ...  | 滤 | 物洗 |   化洗  |
+    """
+    
+    # ========== 初始化周期参数 ==========
+    L_h = float(L_s) / 3600.0  # 过滤时长转换:秒 → 小时
+
+    # 初始状态
+    tmp = p.TMP0  # 当前跨膜压差
+    R0 = _calculate_resistance(p.TMP0, p.q_UF, p.temp)  # 初始膜阻力
+    
+    # 跟踪变量(用于记录周期内的极值)
+    max_tmp_during_filtration = tmp  # 周期内最大TMP
+    min_tmp_during_filtration = tmp  # 周期内最小TMP
+    max_residual_increase = 0.0      # 周期内最大残余污染增量
+
+    # ========== 计算小周期数量 ==========
+    # 小周期时长 = 过滤时长 + 物理反洗时长
+    t_small_cycle_h = (L_s + t_bw_s) / 3600.0  # [小时]
+    
+    # 计算一个超级周期内包含多少个小周期
+    # k = floor(CEB间隔时间 / 小周期时长)
+    k_bw_per_ceb = int(np.floor(p.T_ceb_interval_h / t_small_cycle_h))
+    if k_bw_per_ceb < 1:
+        k_bw_per_ceb = 1  # 至少包含1个小周期
+
+    # ========== 吨水电耗查找表 ==========
+    # 键:过滤时长(秒),值:吨水电耗(kWh/m³)
+    energy_lookup = {
+        3600: 0.1034, 3660: 0.1031, 3720: 0.1029, 3780: 0.1026,
+        3840: 0.1023, 3900: 0.1021, 3960: 0.1019, 4020: 0.1017,
+        4080: 0.1015, 4140: 0.1012, 4200: 0.1011, 4260: 0.1008,
+        4320: 0.1007, 4380: 0.1005, 4440: 0.1003, 4500: 0.1001,
+        4560: 0.0999, 4620: 0.0998, 4680: 0.0996, 4740: 0.0995,
+        4800: 0.0993,
+    }
+
+    # ========== 循环模拟每个小周期(过滤 + 物理反洗) ==========
+    for idx in range(k_bw_per_ceb):
+        # --- 小周期开始状态 ---
+        tmp_run_start = tmp      # 本次过滤开始时的TMP
+        q_UF = p.q_UF            # 过滤流量
+        temp = p.temp            # 水温
+
+        # --- 过滤阶段:膜阻力上升 ---
+        R_run_start = _calculate_resistance(tmp_run_start, q_UF, temp)  # 过滤开始时的膜阻力
+        d_R = _delta_resistance(p, L_s)  # 过滤阶段膜阻力增量
+        R_peak = R_run_start + d_R       # 过滤结束时的膜阻力(峰值)
+        tmp_peak = _calculate_tmp(R_peak, q_UF, temp)  # 过滤结束时的TMP(峰值)
+
+        # 更新TMP极值记录
+        max_tmp_during_filtration = max(max_tmp_during_filtration, tmp_peak)
+        min_tmp_during_filtration = min(min_tmp_during_filtration, tmp_run_start)
+
+        # --- 物理反洗阶段:膜阻力下降 ---
+        # 计算累积运行时间(用于长期污染模型)
+        L_h_next_start = (L_s + t_bw_s) / 3600.0 * (idx + 1)  # 下一小周期起始时间
+        
+        # 调用膜阻力下降模型,计算物理反洗可去除的阻力
+        reversible_R = phi_bw_of(p, R0, R_peak, L_h_next_start, t_bw_s)
+        
+        # 物理反洗后的膜阻力
+        R_after_bw = R_peak - reversible_R
+        tmp_after_bw = _calculate_tmp(R_after_bw, q_UF, temp)
+
+        # 计算残余污染增量(反洗后的TMP相对本次开始的增加)
+        residual_inc = tmp_after_bw - tmp_run_start
+        max_residual_increase = max(max_residual_increase, residual_inc)
+
+        # 更新TMP(作为下一小周期的起始TMP)
+        tmp = tmp_after_bw
+
+    # ========== 化学增强反洗 (CEB) ==========
+    # CEB比物理反洗更彻底,可去除部分不可逆污染
+    R_after_ceb = R_peak - p.ceb_removal  # CEB后的膜阻力
+    tmp_after_ceb = _calculate_tmp(R_after_ceb, q_UF, temp)  # CEB后的TMP
+
+    # ============================================================
+    # 计算周期性能指标
+    # ============================================================
+
+    # ========== 水量平衡计算 ==========
+    # 进水总量(所有小周期的过滤进水之和)
+    V_feed_super = k_bw_per_ceb * p.q_UF * L_h  # [m³]
+    
+    # 损失水量(物理反洗 + 化学反洗)
+    V_loss_super = k_bw_per_ceb * _v_bw_m3(p, t_bw_s) + p.v_ceb_m3  # [m³]
+    
+    # 净产水量
+    V_net = max(0.0, V_feed_super - V_loss_super)  # [m³]
+    
+    # 回收率(净产水 / 进水总量)
+    # 加1e-12避免除零,max确保非负
+    recovery = max(0.0, V_net / max(V_feed_super, 1e-12))  # [无量纲,0-1之间]
+
+    # ========== 时间与能耗计算 ==========
+    # 超级周期总时长
+    T_super_h = k_bw_per_ceb * (L_s + t_bw_s) / 3600.0 + p.t_ceb_s / 3600.0  # [小时]
+    
+    # 日均产水时间(24小时内实际产水的时间)
+    daily_prod_time_h = k_bw_per_ceb * L_h / T_super_h * 24.0  # [小时]
+
+    # 吨水电耗(从查找表获取最接近的值)
+    closest_L = min(energy_lookup.keys(), key=lambda x: abs(x - L_s))
+    ton_water_energy = energy_lookup[closest_L]  # [kWh/m³]
+
+    # ===== 新指标:膜阻力允许上升空间 =====
+    R_max = _calculate_resistance(max_tmp_during_filtration, p.q_UF, p.temp)
+    R_soft_limit = _calculate_resistance(p.global_TMP_soft_limit, p.q_UF, p.temp)
+    delta_R_allow = max(R_soft_limit - R_max, 1e-6)  # 供奖励函数使用的“污染允许增长空间”
+
+    # ========== 构建性能指标字典 ==========
+    info = {
+        # 运行参数
+        "q_UF": p.q_UF,                                        # 过滤流量
+        "temp": p.temp,                                        # 水温
+        
+        # 水量指标
+        "recovery": recovery,                                  # 回收率
+        "V_feed_super_m3": V_feed_super,                      # 进水总量
+        "V_loss_super_m3": V_loss_super,                      # 损失水量
+        "V_net_super_m3": V_net,                              # 净产水量
+        
+        # 时间指标
+        "supercycle_time_h": T_super_h,                       # 超级周期时长
+        "daily_prod_time_h": daily_prod_time_h,               # 日均产水时间
+        "k_bw_per_ceb": k_bw_per_ceb,                         # 小周期数量
+        
+        # TMP指标
+        "max_TMP_during_filtration": max_tmp_during_filtration,  # 周期内最大TMP
+        "min_TMP_during_filtration": min_tmp_during_filtration,  # 周期内最小TMP
+        "global_TMP_limit": p.global_TMP_hard_limit,                  # TMP限制
+        "TMP0": p.TMP0,                                          # 周期初始TMP
+        "TMP_after_ceb": tmp_after_ceb,                          # CEB后TMP
+        
+        # 膜阻力指标
+        "R0": R0,                                              # 周期初始膜阻力
+        "R_after_ceb": R_after_ceb,                            # CEB后膜阻力
+        "max_residual_increase_per_run": max_residual_increase,  # 最大残余污染增量
+        "delta_R_allow": delta_R_allow,                                    # 污染允许增长空间
+        
+        # 能耗指标
+        "ton_water_energy_kWh_per_m3": ton_water_energy,      # 吨水电耗
+    }
+
+    # ============================================================
+    # 状态更新:生成下一周期的初始参数
+    # ============================================================
+
+    # 深拷贝当前参数(避免修改原对象)
+    next_params = copy.deepcopy(p)
+
+    # ========== 必须更新的参数 ==========
+    # 更新TMP为CEB后的值(作为下一超级周期的起始TMP)
+    next_params.TMP0 = tmp_after_ceb
+
+    # ========== 可选更新的参数(当前保持不变) ==========
+    # 这些参数可根据实际情况动态调整,预留扩展接口
+    next_params.slope = p.slope                # 长期污染斜率
+    next_params.power = p.power                # 长期污染幂次
+    next_params.ceb_removal = p.ceb_removal    # CEB去除能力
+    next_params.nuK = p.nuK                    # 短期污染系数
+    next_params.q_UF = p.q_UF                  # 过滤流量
+    next_params.temp = p.temp                  # 水温
+
+    return info, next_params
+
+def calculate_reward(p: UFParams, info: dict) -> float:
+    """
+    计算强化学习奖励函数
+    
+    功能:
+    - 平衡回收率和残余污染两个目标
+    - TMP不直接参与奖励计算(通过失败判定间接影响)
+    - 使用 tanh 函数实现平滑的非线性奖励
+    
+    参数:
+        p (UFParams): 超滤参数(包含奖励函数超参数)
+        info (dict): 周期性能指标字典
+    
+    返回:
+        float: 奖励值(通常在 -2 到 +2 之间)
+    
+    设计思想:
+    - 高回收率 → 水资源利用率高 → 正奖励
+    - 低残余污染 → 膜长期稳定运行 → 正奖励
+    - 两者需要权衡:过短的过滤时间提高回收率但污染去除不彻底;
+                      过长的过滤时间污染控制好但回收率下降
+    
+    参考点设计:
+    - (recovery=0.97, residual_ratio=0.1) → reward ≈ 0(高回收但污染高)
+    - (recovery=0.90, residual_ratio=0.0) → reward ≈ 0(低污染但回收率低)
+    - (recovery≈0.94, residual_ratio≈0.05) → reward > 0(平衡点)
+    """
+    # ========== 提取性能指标 ==========
+    recovery = info["recovery"]  # 回收率 [0-1]
+    
+    # 污染比例:实际上升的阻力 / 允许上升的阻力
+    # 允许上升的阻力值 = 当前阻力值软上限 - 当前阻力
+    residual_ratio = (info["R_after_ceb"] - info["R0"]) / info["delta_R_allow"]
+
+    # ========== 回收率奖励项 ==========
+    # 将回收率归一化到 [0, 1] 区间(基于预期范围)
+    rec_norm = (recovery - p.rec_low) / (p.rec_high - p.rec_low)
+    
+    # 使用 tanh 函数构建平滑的 S 型奖励曲线
+    # - rec_norm = 0.5 时(回收率处于中间值),rec_reward = 0
+    # - rec_norm > 0.5 时,rec_reward > 0(鼓励高回收率)
+    # - rec_norm < 0.5 时,rec_reward < 0(惩罚低回收率)
+    # - k_rec 控制曲线陡峭程度,越大变化越陡
+    rec_reward = np.clip(np.tanh(p.k_rec * (rec_norm - 0.5)), -1, 1)
+
+    # ========== 污染惩罚项 ==========
+    # 使用 tanh 函数构建惩罚曲线
+    # - residual_ratio < rr0 时,res_penalty > 0(奖励低污染)
+    # - residual_ratio > rr0 时,res_penalty < 0(惩罚高污染)
+    # - k_res 控制曲线陡峭程度
+    res_penalty = -np.tanh(p.k_res * (residual_ratio / p.rr0 - 1))
+
+    # ========== 组合奖励 ==========
+    # 简单线性组合两项(也可以加权)
+    total_reward = rec_reward + res_penalty
+
+    # 可选:添加平移项使特定点的奖励为零(当前未使用)
+    # total_reward -= offset
+
+    return total_reward
+
+
+def is_dead_cycle(info: dict) -> bool:
+    """
+    判断当前超级周期是否成功(可行)
+    
+    功能:
+    - 检查超级周期是否违反运行约束
+    - 用于强化学习的失败判定(terminated条件)
+    - True表示成功,False表示失败
+    
+    参数:
+        info (dict): simulate_one_supercycle() 返回的性能指标字典
+    
+    返回:
+        bool: True表示成功周期,False表示失败周期
+    
+    失败条件(任一满足即失败):
+    1. TMP超限:max_TMP > global_TMP_limit
+       - 原因:TMP过高会损坏膜或影响产水质量
+       - 阈值:0.08 MPa(可配置)
+    
+    2. 回收率过低:recovery < 0.75
+       - 原因:回收率太低说明反洗水耗过大,经济性差
+       - 阈值:75%(可调整)
+    
+    3. 残余污染累积过快:(R_after_ceb - R0) / R0 > 0.1
+       - 原因:单个超级周期污染增长超过10%,长期运行不可持续
+       - 阈值:10%(可调整)
+    """
+    # ========== 获取关键指标 ==========
+    TMP_limit = info.get("global_TMP_limit", 0.08)  # TMP硬约束上限
+    max_tmp = info.get("max_TMP_during_filtration", 0)  # 周期内最大TMP
+    recovery = info.get("recovery", 1.0)  # 回收率
+    R_after_ceb = info.get("R_after_ceb", 0)  # CEB后膜阻力
+    R0 = info.get("R0", 1e-6)  # 初始膜阻力
+    delta_R_allow = info.get("delta_R_allow", 1e-6) # 允许上升的膜阻力(加小值避免除零)
+
+    # ========== 失败条件检查 ==========
+    # 条件1:TMP超限
+    if max_tmp > TMP_limit:
+        return False  # 失败
+    
+    # 条件2:回收率过低
+    if recovery < 0.75:
+        return False  # 失败
+    
+    # 条件3:污染增长量超过容许范围
+    residual_increase = (R_after_ceb - R0) / delta_R_allow
+    if residual_increase > 1/15:
+        return False  # 失败
+
+    # 所有条件通过
+    return True  # 成功
+
+
+class UFSuperCycleEnv(gym.Env):
+    """
+    超滤系统强化学习环境(Gymnasium标准接口)
+    
+    功能:
+    - 模拟超滤膜的超级周期运行
+    - 智能体在每个超级周期选择过滤时长和反洗时长
+    - 目标:最大化回收率同时控制污染累积
+    
+    状态空间 (8维,归一化到 [0,1]):
+        1. TMP0: 初始跨膜压差
+        2. q_UF: 过滤流量
+        3. temp: 水温
+        4. R0: 初始膜阻力
+        5. nuK: 短期污染系数
+        6. slope: 长期污染斜率
+        7. power: 长期污染幂次
+        8. ceb_removal: CEB去除能力
+    
+    动作空间 (离散):
+        - 二维离散动作组合:(过滤时长, 反洗时长)
+        - 过滤时长: L_min_s ~ L_max_s,步长 L_step_s
+        - 反洗时长: t_bw_min_s ~ t_bw_max_s,步长 t_bw_step_s
+        - 总动作数 = len(L_values) × len(t_bw_values)
+    
+    奖励机制:
+        - 基于回收率和残余污染的平衡
+        - 失败 (TMP超限、回收率过低、污染过快) 时给予大负奖励 (-10)
+    
+    终止条件:
+        - terminated: 违反运行约束(失败)
+        - truncated: 达到最大步数 (max_episode_steps)
+    """
+
+    metadata = {"render_modes": ["human"]}
+
+    def __init__(self, base_params, resistance_models=None, max_episode_steps: int = 15):
+        """
+        初始化超滤强化学习环境
+        
+        参数:
+            base_params (UFParams): 基础超滤参数配置
+            resistance_models (tuple, optional): 预加载的膜阻力模型,默认None(自动加载)
+            max_episode_steps (int): 每个episode的最大步数,默认15
+                注:每步代表一个超级周期(约2-3天),15步约一个月
+        """
+        super(UFSuperCycleEnv, self).__init__()
+
+        # ========== 参数初始化 ==========
+        self.base_params = base_params  # 基础参数(不变)
+        self.current_params = copy.deepcopy(base_params)  # 当前参数(动态更新)
+        self.max_episode_steps = max_episode_steps  # 最大步数
+        self.current_step = 0  # 当前步数计数器
+
+        # ========== 加载膜阻力模型 ==========
+        if resistance_models is None:
+            # 自动加载模型
+            self.resistance_model_fp, self.resistance_model_bw = load_resistance_models()
+        else:
+            # 使用预加载的模型(用于并行环境避免重复加载)
+            self.resistance_model_fp, self.resistance_model_bw = resistance_models
+
+        # ========== 构建进水时间离散动作空间 ==========
+        # 过滤时长候选值(例:3800, 3860, 3920, ..., 5940, 6000秒)
+        self.L_values = np.arange( self.base_params.L_min_s,
+                                   self.base_params.L_max_s,
+                                   self.base_params.L_step_s )
+        self.action_space = spaces.Discrete(len(self.L_values))
+
+        # ========== 定义状态空间 ==========
+        # 8维连续状态,归一化到 [0, 1]
+        self.observation_space = spaces.Box(
+            low=np.zeros(8, dtype=np.float32),
+            high=np.ones(8, dtype=np.float32),
+            dtype=np.float32
+        )
+
+        # ========== 初始化环境(调用reset) ==========
+        self.reset(seed=None)
+
+    def generate_initial_state(self):
+        """
+        随机生成一个初始状态,并判断其污染增长速率约束是否满足。
+        若满足返回 True,不满足返回 False。
+        (不负责重复采样,由 reset() 控制)
+        """
+
+        # 基础常数
+        A = 128 * 40.0  # 有效膜面积
+
+        # ---- 1. 随机生成 TMP、q_UF、温度 ----
+        self.current_params.TMP0 = np.random.uniform(
+            self.current_params.TMP0_min, self.current_params.TMP0_max
+        )
+        self.current_params.q_UF = np.random.uniform(
+            self.current_params.q_UF_min, self.current_params.q_UF_max
+        )
+        self.current_params.temp = np.random.uniform(
+            self.current_params.temp_min, self.current_params.temp_max
+        )
+
+        q_UF = self.current_params.q_UF
+
+        # ---- 2. 随机 slope、power ----
+        slope = np.random.uniform(self.current_params.slope_min, self.current_params.slope_max)
+        power = np.random.uniform(self.current_params.power_min, self.current_params.power_max)
+
+        # ---- 3. 计算满足条件所需的最小 nuK ----
+        # 计算 t_max
+        t_max = 60 if power >= 1 else 1
+
+        required_nuK_min = (
+                slope * power * (t_max ** (power - 1)) * (A / q_UF)
+        )
+
+        # 若 required_nuK_min 超过可选范围 → 初始状态非法
+        if required_nuK_min > self.current_params.nuK_max:
+            return False
+
+        # ---- 4. 在可行范围中采样 nuK ----
+        nuK_low = max(required_nuK_min, self.current_params.nuK_min)
+        nuK_high = self.current_params.nuK_max
+        self.current_params.nuK = np.random.uniform(nuK_low, nuK_high)
+
+        # ---- 5. 生成 CEB 去除率 ----
+        self.current_params.ceb_removal = np.random.uniform(
+            self.current_params.ceb_removal_min,
+            self.current_params.ceb_removal_max
+        )
+
+        # ---- 6. 计算初始膜阻力 ----
+        self.current_params.R0 = _calculate_resistance(
+            self.current_params.TMP0,
+            self.current_params.q_UF,
+            self.current_params.temp
+        )
+
+        # ---- 7. slope/power 写入 ----
+        self.current_params.slope = slope
+        self.current_params.power = power
+
+        return True
+
+    def reset(self, seed=None, options=None, max_attempts: int = 1000):
+        super().reset(seed=seed)
+
+        attempts = 0
+        while attempts < max_attempts:
+            attempts += 1
+
+            # ==== Step 0: 生成初始状态(含污染速率约束) ====
+            ok_init = self.generate_initial_state()
+            if not ok_init:
+                continue
+
+            # 运行稳定性检测
+            ok_run = self.check_dead_initial_state(
+                max_steps=getattr(self, "max_episode_steps", 15),
+                L_s=3800, t_bw_s=60
+            )
+
+            # 满足 → 成功生成初始状态
+            if ok_run:
+                break
+
+        else:
+            raise RuntimeError(f"在 {max_attempts} 次尝试后仍无法生成可行初始状态。")
+
+        # 初始化环境状态
+        self.current_step = 0
+        self.last_action = (self.base_params.L_min_s, self.base_params.t_bw_min_s)
+        self.max_TMP_during_filtration = self.current_params.TMP0
+        self.TMP0 = self.current_params.TMP0
+
+        return self._get_obs(), {}
+
+    def check_dead_initial_state(self, max_steps: int = None,
+                                 L_s: int = 3800, t_bw_s: int = 60) -> bool:
+        """
+        判断当前环境生成的初始状态是否为可行(non-dead)。
+        使用最保守策略连续模拟 max_steps 次:
+            若任意一次 is_dead_cycle(info) 返回 False 或 next_params[0] < 0,则视为必死状态。
+
+        参数:
+            max_steps: 模拟步数,默认使用 self.max_episode_steps
+            L_s: 过滤时长(s),默认 3800
+            t_bw_s: 物理反洗时长(s),默认 60
+
+        返回:
+            bool: True 表示可行状态(non-dead),False 表示必死状态
+        """
+        if max_steps is None:
+            max_steps = getattr(self, "max_episode_steps", 15)
+
+        if not hasattr(self, "current_params"):
+            raise AttributeError("generate_initial_state() 未设置 current_params。")
+
+        import copy
+        curr_p = copy.deepcopy(self.current_params)
+
+        # 逐步模拟
+        for step in range(max_steps):
+            try:
+                info, next_params = simulate_one_supercycle(curr_p, L_s, t_bw_s)
+            except Exception:
+                # 异常即视为不可行
+                return False
+
+            # 任意一次不可行即为必死状态
+            if not is_dead_cycle(info):
+                return False
+
+            # 新增判断:下一步跨膜压差 TMP 不可能为负
+            if next_params.TMP0 < 0:
+                return False
+
+            # 更新参数,进入下一步模拟
+            curr_p = next_params
+
+        return True
+
+    def _get_state_copy(self):
+        return copy.deepcopy(self.current_params)
+
+    def _get_obs(self):
+        """
+        构建当前环境归一化状态向量
+        """
+        # === 1. 从 current_params 读取动态参数 ===
+        TMP0 = self.current_params.TMP0
+        q_UF = self.current_params.q_UF
+        temp = self.current_params.temp
+
+        # === 2. 计算本周期初始膜阻力 ===
+        R0 = _calculate_resistance(TMP0, q_UF, temp)
+
+        # === 3. 从 current_params 读取膜阻力增长模型参数 ===
+        nuk = self.current_params.nuK
+        slope = self.current_params.slope
+        power = self.current_params.power
+        ceb_removal = self.current_params.ceb_removal
+
+        # === 4. 从 current_params 动态读取上下限 ===
+        TMP0_min, TMP0_max = self.current_params.TMP0_min, self.current_params.global_TMP_hard_limit
+        q_UF_min, q_UF_max = self.current_params.q_UF_min, self.current_params.q_UF_max
+        temp_min, temp_max = self.current_params.temp_min, self.current_params.temp_max
+        nuK_min, nuK_max = self.current_params.nuK_min, self.current_params.nuK_max
+        slope_min, slope_max = self.current_params.slope_min, self.current_params.slope_max
+        power_min, power_max = self.current_params.power_min, self.current_params.power_max
+        ceb_min, ceb_max = self.current_params.ceb_removal_min, self.current_params.ceb_removal_max
+
+        # === 5. 归一化计算(clip防止越界) ===
+        TMP0_norm = np.clip((TMP0 - TMP0_min) / (TMP0_max - TMP0_min), 0, 1)
+        q_UF_norm = np.clip((q_UF - q_UF_min) / (q_UF_max - q_UF_min), 0, 1)
+        temp_norm = np.clip((temp - temp_min) / (temp_max - temp_min), 0, 1)
+
+        # R0 不在 current_params 中定义上下限,设定经验范围
+        R0_norm = np.clip((R0 - 100.0) / (600.0 - 100.0), 0, 1)
+
+        short_term_norm = np.clip((nuk - nuK_min) / (nuK_max - nuK_min), 0, 1)
+        long_term_slope_norm = np.clip((slope - slope_min) / (slope_max - slope_min), 0, 1)
+        long_term_power_norm = np.clip((power - power_min) / (power_max - power_min), 0, 1)
+        ceb_removal_norm = np.clip((ceb_removal - ceb_min) / (ceb_max - ceb_min), 0, 1)
+
+        # === 6. 构建观测向量 ===
+        obs = np.array([
+            TMP0_norm,
+            q_UF_norm,
+            temp_norm,
+            R0_norm,
+            short_term_norm,
+            long_term_slope_norm,
+            long_term_power_norm,
+            ceb_removal_norm
+        ], dtype=np.float32)
+
+        return obs
+
+    def _get_action_values(self, action: int):
+        """ 新版动作解释函数: action 只对应一个 L_s,不再包含 t_bw_s。 """
+
+        L_s = self.L_values[action]
+        return L_s
+
+    def step(self, action):
+        self.current_step += 1
+        L_s= self._get_action_values(action)
+        L_s = np.clip(L_s, self.base_params.L_min_s, self.base_params.L_max_s)
+        t_bw_s = self.current_params.fixed_t_bw_s
+
+        # 模拟超级周期
+        info, next_params = simulate_one_supercycle(self.current_params, L_s, t_bw_s)
+        # 根据 info 判断是否成功
+        feasible = is_dead_cycle(info)  # True 表示成功循环,False 表示失败
+
+        if feasible:
+            # 每步奖励
+            reward = calculate_reward(self.current_params, info)
+            self.current_params = next_params
+            terminated = False
+        else:
+            # 中途失败惩罚
+            reward = -20
+            terminated = True
+
+        # 判断是否到达最大步数
+        truncated = self.current_step >= self.max_episode_steps
+
+        self.last_action = (L_s, t_bw_s)
+        next_obs = self._get_obs()
+
+        info["feasible"] = feasible
+        info["step"] = self.current_step
+
+        # ===================== 测试终末奖励:鼓励 TMP 接近初始状态 =====================
+        # 仅在 episode 自然结束(满步但未提前失败)时触发
+        if truncated and not terminated:
+            TMP_initial = self.TMP0  # reset 时记录的初始 TMP
+            TMP_final = next_obs[0]  # next_obs 提供的最终 TMP
+
+            delta_ratio = abs((TMP_final - TMP_initial) / TMP_initial)
+
+            alpha = 4.0  # TMP 偏差敏感度
+            gamma = 5.0  # 奖励幅度
+            stability_reward = gamma * (np.exp(-alpha * delta_ratio) - 1) # 量级在0到-5之间
+
+            reward += stability_reward
+            terminated = True  # episode 正式结束
+
+
+        return next_obs, reward, terminated, truncated, info
+
+
+
+

+ 536 - 0
models/uf-rl/训练/进水动作版超滤训练源码/fixed_DQN_train.py

@@ -0,0 +1,536 @@
+"""
+DQN 强化学习训练模块
+======================
+本模块实现基于 Stable-Baselines3 的 DQN 强化学习训练流程,包括:
+1. DQNParams: DQN超参数配置类
+2. UFEpisodeRecorder: Episode数据记录器
+3. UFTrainingCallback: 训练回调器
+4. DQNTrainer: DQN训练器封装
+5. train_uf_rl_agent: 主训练函数
+
+DQN算法简介:
+- Deep Q-Network(深度Q网络)
+- 基于价值的强化学习算法
+- 使用经验回放和目标网络稳定训练
+- 适用于离散动作空间
+
+训练流程:
+1. 初始化环境和DQN智能体
+2. 收集经验(exploration)
+3. 从经验池采样训练(exploitation)
+4. 周期性更新目标网络
+5. 记录训练指标到TensorBoard
+"""
+
+import os
+import time
+import random
+import numpy as np
+import torch
+from stable_baselines3 import DQN
+from stable_baselines3.common.monitor import Monitor
+from stable_baselines3.common.vec_env import DummyVecEnv
+from stable_baselines3.common.callbacks import BaseCallback
+
+from fixed_DQN_env import UFParams, UFSuperCycleEnv
+
+
+# ==================== DQN超参数配置类 ====================
+class DQNParams:
+    """
+    DQN 超参数配置类
+    
+    功能:统一管理DQN算法的所有超参数
+    
+    超参数说明:
+    - learning_rate: 神经网络学习率,控制梯度下降的步长
+    - buffer_size: 经验回放缓冲区大小,存储历史经验
+    - learning_starts: 开始训练前先收集的经验数量(warm-up)
+    - batch_size: 每次训练采样的batch大小
+    - gamma: 折扣因子,权衡即时奖励和长期奖励
+    - train_freq: 训练频率,每隔多少步训练一次
+    - target_update_interval: 目标网络更新频率
+    - tau: 软更新系数(soft update)
+    - exploration_*: ε-贪心策略的探索率参数
+    """
+    # ========== 神经网络参数 ==========
+    learning_rate: float = 1e-4  
+    # 学习率,控制神经网络权重更新的步长
+    # 典型范围:1e-5 ~ 1e-3
+    # 过大:训练不稳定;过小:收敛慢
+
+    # ========== 经验回放参数 ==========
+    buffer_size: int = 100000  
+    # 经验回放缓冲区大小(可存储的transition数量)
+    # 作用:打破样本间的时间相关性,提高训练稳定性
+    # 建议:至少存储几个完整episode的经验
+
+    learning_starts: int = 10000  
+    # 开始训练前先收集的步数(预填充缓冲区)
+    # 作用:确保缓冲区有足够的多样性样本再开始训练
+    # 建议:设为buffer_size的10%-20%
+
+    batch_size: int = 32  
+    # 每次训练从缓冲区采样的样本数量
+    # 典型值:32, 64, 128, 256
+    # 过大:显存占用高,训练慢;过小:梯度估计不准确
+
+    # ========== 强化学习参数 ==========
+    gamma: float = 0.95  
+    # 折扣因子(discount factor),γ ∈ [0, 1]
+    # 作用:权衡即时奖励和长期奖励
+    # γ=0:只考虑当前奖励(短视)
+    # γ=1:完全考虑未来奖励(长视)
+    # 通常设为0.9-0.99
+
+    train_freq: int = 4  
+    # 训练频率:每收集多少步执行一次训练
+    # 作用:平衡数据收集和网络更新
+    # 典型值:1(每步训练)或4-16(批量训练)
+
+    # ========== 目标网络参数 ==========
+    target_update_interval: int = 1  
+    # 目标网络更新间隔(硬更新)
+    # 作用:目标网络每隔多少次训练更新一次
+    # 注:使用软更新(tau)时此参数通常设为1
+
+    tau: float = 0.005  
+    # 软更新系数(soft update)
+    # θ_target = τ×θ + (1-τ)×θ_target
+    # τ=1:硬更新(完全复制)
+    # τ<<1:软更新(平滑过渡,更稳定)
+    # 典型值:0.001 - 0.01
+
+    # ========== 探索策略参数(ε-greedy) ==========
+    exploration_initial_eps: float = 1.0  
+    # 初始探索率 ε_0
+    # ε=1:完全随机探索
+    # ε=0:完全利用已学知识
+
+    exploration_fraction: float = 0.3  
+    # 探索率衰减比例
+    # 表示训练总步数的前30%进行ε衰减
+    # 例:总共10万步,前3万步ε从1.0衰减到0.02
+
+    exploration_final_eps: float = 0.02  
+    # 最终探索率 ε_final
+    # 衰减结束后保持此值(保留小概率探索)
+    # 典型值:0.01 - 0.05
+
+    # ========== 日志参数 ==========
+    remark: str = "default"  
+    # 实验备注,用于区分不同训练实验
+    # 会自动添加到TensorBoard日志目录名中
+
+# ==================== Episode数据记录器 ====================
+class UFEpisodeRecorder:
+    """
+    Episode数据记录器
+    
+    功能:
+    - 记录训练过程中每个episode的详细数据
+    - 存储每步的状态、动作、奖励、info等信息
+    - 计算episode级别的统计指标
+    
+    用途:
+    - 训练监控:实时查看智能体表现
+    - 调试分析:定位问题episode
+    - 数据分析:评估策略改进效果
+    """
+
+    def __init__(self):
+        """初始化记录器"""
+        self.episode_data = []      # 存储所有完成的episode数据
+        self.current_episode = []   # 当前正在进行的episode数据
+
+    def record_step(self, obs, action, reward, done, info):
+        """
+        记录单步交互数据
+        
+        参数:
+            obs: 当前状态观测
+            action: 执行的动作
+            reward: 获得的奖励
+            done: 是否结束
+            info: 额外信息字典
+        """
+        # 构建单步数据字典
+        step_data = {
+            "obs": obs.copy(),                        # 状态(深拷贝避免引用问题)
+            "action": action.copy(),                  # 动作
+            "reward": reward,                         # 奖励
+            "done": done,                             # 是否终止
+            "info": info.copy() if info else {}      # 环境信息
+        }
+        
+        # 添加到当前episode
+        self.current_episode.append(step_data)
+
+        # 如果episode结束,保存并重置
+        if done:
+            self.episode_data.append(self.current_episode)
+            self.current_episode = []
+
+    def get_episode_stats(self, episode_idx=-1):
+        """
+        获取指定episode的统计信息
+        
+        参数:
+            episode_idx (int): episode索引,默认-1(最后一个)
+        
+        返回:
+            dict: 包含以下统计指标的字典
+                - total_reward: 总奖励
+                - avg_recovery: 平均回收率
+                - feasible_steps: 可行步数
+                - total_steps: 总步数
+        """
+        if not self.episode_data:
+            return {}
+
+        episode = self.episode_data[episode_idx]
+        
+        # 计算总奖励
+        total_reward = sum(step["reward"] for step in episode)
+        
+        # 计算平均回收率(从info中提取)
+        recovery_values = [
+            step["info"].get("recovery", 0) 
+            for step in episode 
+            if "recovery" in step["info"]
+        ]
+        avg_recovery = np.mean(recovery_values) if recovery_values else 0.0
+        
+        # 计算可行步数(成功的超级周期数)
+        feasible_steps = sum(
+            1 for step in episode 
+            if step["info"].get("feasible", False)
+        )
+
+        return {
+            "total_reward": total_reward,
+            "avg_recovery": avg_recovery,
+            "feasible_steps": feasible_steps,
+            "total_steps": len(episode)
+        }
+
+
+# ==================== 训练回调器 ====================
+class UFTrainingCallback(BaseCallback):
+    """
+    自定义训练回调器
+    
+    功能:
+    - 在每个训练步骤调用,记录数据到recorder
+    - 兼容Stable-Baselines3的回调机制
+    - 不依赖环境内部属性,使用标准接口获取数据
+    
+    回调时机:
+    - _on_step(): 每执行一步环境交互后调用
+    
+    设计特点:
+    1. 从self.locals获取当前步的数据(SB3提供的接口)
+    2. 处理向量化环境(DummyVecEnv)的数据格式
+    3. 自动检测episode结束并触发记录
+    """
+
+    def __init__(self, recorder, verbose=0):
+        """
+        初始化回调器
+        
+        参数:
+            recorder (UFEpisodeRecorder): 数据记录器实例
+            verbose (int): 日志详细程度,0=关闭,1=打印每步信息
+        """
+        super(UFTrainingCallback, self).__init__(verbose)
+        self.recorder = recorder
+
+    def _on_step(self) -> bool:
+        """
+        每步回调函数(Stable-Baselines3标准接口)
+        
+        返回:
+            bool: True表示继续训练,False表示提前终止
+        """
+        try:
+            # 从SB3的self.locals获取当前步数据
+            new_obs = self.locals.get("new_obs")      # 新状态
+            actions = self.locals.get("actions")      # 执行的动作
+            rewards = self.locals.get("rewards")      # 获得的奖励
+            dones = self.locals.get("dones")          # 是否结束
+            infos = self.locals.get("infos")          # 环境信息
+
+            # 处理向量化环境(取第一个环境的数据)
+            if len(new_obs) > 0:
+                step_obs = new_obs[0]
+                step_action = actions[0] if actions is not None else None
+                step_reward = rewards[0] if rewards is not None else 0.0
+                step_done = dones[0] if dones is not None else False
+                step_info = infos[0] if infos is not None else {}
+
+                # 可选:打印当前步信息(用于调试)
+                if self.verbose:
+                    print(f"[Step {self.num_timesteps}] "
+                          f"动作={step_action}, "
+                          f"奖励={step_reward:.3f}, "
+                          f"Done={step_done}")
+
+                # 记录数据到recorder
+                self.recorder.record_step(
+                    obs=step_obs,
+                    action=step_action,
+                    reward=step_reward,
+                    done=step_done,
+                    info=step_info,
+                )
+
+        except Exception as e:
+            # 异常处理:避免回调错误中断训练
+            if self.verbose:
+                print(f"[Callback Error] {e}")
+
+        # 返回True继续训练
+        return True
+
+
+
+
+# ==================== DQN训练器封装类 ====================
+class DQNTrainer:
+    def __init__(self, env, params, callback=None):
+        """
+        初始化训练器
+
+        参数:
+            env: Gymnasium环境
+            params: DQN 超参数配置
+            callback: 可选训练回调
+        """
+        self.env = env
+        self.params = params
+        self.callback = callback
+        self.log_dir = self._create_log_dir()  # 创建日志目录
+        self.model = self._create_model()  # 创建 DQN 模型
+
+    def _create_log_dir(self):
+        """
+        创建 TensorBoard 日志目录,保证 Windows 下路径安全
+
+        返回:
+            str: 可用的日志目录路径
+        """
+        timestamp = time.strftime("%Y%m%d-%H%M%S")
+
+        # 用整数代替浮点数,避免路径中包含小数点
+        lr_int = int(self.params.learning_rate * 1e4)
+        gamma_int = int(self.params.gamma * 100)
+        exp_int = int(self.params.exploration_fraction * 100)
+
+        # 生成目录名
+        log_name = f"DQN_lr{lr_int}_buf{self.params.buffer_size}_bs{self.params.batch_size}_gamma{gamma_int}_exp{exp_int}_{self.params.remark}_{timestamp}"
+
+        # 使用短路径,避免 Windows 路径过长
+        base_dir = r"E:\Greentech\models\uf-rl\uf_dqn_tensorboard"
+        os.makedirs(base_dir, exist_ok=True)
+        log_dir = os.path.join(base_dir, log_name)
+
+        # 尝试创建目录,防止偶发锁或占用
+        attempt = 0
+        while attempt < 5:
+            try:
+                os.makedirs(log_dir, exist_ok=True)
+                if not os.path.isdir(log_dir):
+                    raise RuntimeError(f"{log_dir} 已存在但不是目录!")
+                break
+            except Exception as e:
+                attempt += 1
+                time.sleep(0.1)
+                log_dir += f"_{attempt}"
+        else:
+            raise RuntimeError(f"无法创建日志目录: {log_dir}")
+
+        return log_dir
+
+    def _create_model(self):
+        """
+        创建 Stable-Baselines3 DQN 模型
+        """
+        model = DQN(
+            policy="MlpPolicy",
+            env=self.env,
+            learning_rate=self.params.learning_rate,
+            buffer_size=self.params.buffer_size,
+            learning_starts=self.params.learning_starts,
+            batch_size=self.params.batch_size,
+            gamma=self.params.gamma,
+            train_freq=self.params.train_freq,
+            target_update_interval=1,
+            tau=0.005,
+            exploration_initial_eps=self.params.exploration_initial_eps,
+            exploration_fraction=self.params.exploration_fraction,
+            exploration_final_eps=self.params.exploration_final_eps,
+            verbose=1,
+            tensorboard_log=self.log_dir
+        )
+        return model
+
+    def train(self, total_timesteps: int):
+        """
+        执行训练
+        
+        参数:
+            total_timesteps (int): 总训练步数
+                注:对于超滤环境,每步代表一个超级周期(约2-3天)
+                    150000步 ≈ 10000个episode ≈ 10000个超级周期 ≈ 约54年
+        """
+        if self.callback:
+            # 使用回调器训练
+            self.model.learn(total_timesteps=total_timesteps, callback=self.callback)
+        else:
+            # 不使用回调器训练
+            self.model.learn(total_timesteps=total_timesteps)
+        
+        print(f"✅ 模型训练完成!")
+        print(f"📊 日志保存在:{self.log_dir}")
+        print(f"💡 使用以下命令查看TensorBoard:")
+        print(f"   tensorboard --logdir={self.log_dir}")
+
+    def save(self, path=None):
+        """
+        保存模型
+        
+        参数:
+            path (str, optional): 保存路径,默认保存到日志目录下的dqn_model.zip
+        """
+        if path is None:
+            path = os.path.join(self.log_dir, "dqn_model.zip")
+        self.model.save(path)
+        print(f"💾 模型已保存到:{path}")
+
+    def load(self, path):
+        """
+        加载模型
+        
+        参数:
+            path (str): 模型文件路径(.zip文件)
+        """
+        self.model = DQN.load(path, env=self.env)
+        print(f"📥 模型已从 {path} 加载")
+
+
+# ==================== 辅助函数:随机种子设置 ====================
+def set_global_seed(seed: int):
+    """
+    固定全局随机种子,保证训练可复现
+    
+    参数:
+        seed (int): 随机种子
+    
+    作用:
+    - 固定Python、NumPy、PyTorch的随机数生成器
+    - 确保相同种子产生相同的训练结果
+    - 便于实验对比和问题复现
+    
+    注意:
+    - 即使固定种子,多线程/多进程仍可能产生微小差异
+    - GPU运算的非确定性也可能影响复现性
+    """
+    random.seed(seed)           # Python随机数
+    np.random.seed(seed)        # NumPy随机数
+    torch.manual_seed(seed)     # PyTorch CPU随机数
+    torch.cuda.manual_seed_all(seed)  # PyTorch GPU随机数
+    
+    # 设置PyTorch为确定性模式(可能影响性能)
+    torch.backends.cudnn.deterministic = True
+    torch.backends.cudnn.benchmark = False
+
+
+# ==================== 主训练函数 ====================
+def train_uf_rl_agent(params: UFParams, total_timesteps: int = 10000, seed: int = 2025):
+    """
+    超滤强化学习智能体训练主函数
+    
+    参数:
+        params (UFParams): 超滤环境参数
+        total_timesteps (int): 总训练步数,默认10000
+        seed (int): 随机种子,默认2025
+    
+    返回:
+        DQN: 训练好的DQN模型
+    
+    训练流程:
+    1. 固定随机种子(确保可复现)
+    2. 创建记录器和回调器
+    3. 创建并包装环境(Monitor + DummyVecEnv)
+    4. 初始化DQN训练器
+    5. 执行训练
+    6. 保存模型
+    7. 输出统计信息
+    """
+    # 步骤1:固定随机种子
+    set_global_seed(seed)
+    print(f"🎲 随机种子已设置为: {seed}")
+    
+    # 步骤2:创建数据记录器和回调器
+    recorder = UFEpisodeRecorder()
+    callback = UFTrainingCallback(recorder, verbose=1)
+    
+    # 步骤3:创建环境(使用闭包和向量化)
+    def make_env():
+        """环境工厂函数"""
+        env = UFSuperCycleEnv(params)  # 创建超滤环境
+        env = Monitor(env)              # 包装Monitor(记录episode统计)
+        return env
+    
+    # 向量化环境(即使只有一个环境,也需要向量化以兼容SB3)
+    env = DummyVecEnv([make_env])
+    
+    # 步骤4:创建DQN训练器
+    dqn_params = DQNParams()
+    trainer = DQNTrainer(env, dqn_params, callback=callback)
+    
+    # 步骤5:执行训练
+    trainer.train(total_timesteps)
+    
+    # 步骤6:保存模型
+    trainer.save()
+    
+    # 步骤7:输出最终统计信息
+    stats = callback.recorder.get_episode_stats()
+    print("\n" + "="*60)
+    print("📈 训练统计")
+    print("="*60)
+    print(f"总奖励: {stats.get('total_reward', 0):.2f}")
+    print(f"平均回收率: {stats.get('avg_recovery', 0):.3f}")
+    print(f"可行步数: {stats.get('feasible_steps', 0)}")
+    print(f"总步数: {stats.get('total_steps', 0)}")
+    print("="*60)
+    
+    return trainer.model
+
+
+# ==================== 主程序入口 ====================
+if __name__ == "__main__":
+    """
+    训练脚本入口
+    
+    使用方法:
+        python fixed_DQN_train.py
+    
+    训练参数:
+        - total_timesteps=150000: 总训练步数
+        - 约10000个episode(每个episode最多15步)
+        - 约需训练数小时至数天(取决于硬件)
+    """
+    print("="*60)
+    print("🚀 开始训练超滤强化学习智能体")
+    print("="*60)
+    
+    # 初始化超滤参数
+    params = UFParams()
+    
+    # 执行训练
+    train_uf_rl_agent(params, total_timesteps=200000)
+    
+    print("\n🎉 训练流程全部完成!")
+

BIN
models/uf-rl/训练/进水动作版超滤训练源码/model/dqn_model.zip


BIN
models/uf-rl/训练/进水动作版超滤训练源码/resistance_model_bw.pth


BIN
models/uf-rl/训练/进水动作版超滤训练源码/resistance_model_fp.pth