Explorar el Código

1:更新相关逻辑 ,补充注释内容

wmy hace 6 meses
padre
commit
2285543e6d

+ 627 - 0
models/anomaly_detection/README.md

@@ -0,0 +1,627 @@
+# 异常检测模块 (Anomaly Detection)
+
+> **版本**: 1.0.0  
+> **最后更新**: 2025-11-04  
+> **功能**: 对双膜系统(UF超滤 + RO反渗透)关键运行指标进行实时异常检测
+
+---
+
+## 模块概述
+
+异常检测模块用于监测双膜水处理系统的关键运行指标,及时发现设备异常、性能退化等问题。
+
+### 主要特性
+
+- **多模型融合**: 支持孤立森林(Isolation Forest)、3σ统计、One-Class SVM三种检测方法
+- **逐列检测**: 对每个指标独立建模,精准定位异常来源
+- **无需标注**: 基于无监督学习,不需要人工标注异常样本
+- **实时检测**: 支持在线预测,快速识别异常点
+- **批量处理**: 自动读取和合并多批次历史数据
+
+### 应用场景
+
+1. **设备异常预警**: 膜组件堵塞、泵压异常、流量突变
+2. **性能监控**: 膜渗透率下降、产水量异常
+3. **维护决策**: 基于异常频率制定清洗/更换计划
+4. **质量控制**: 确保系统稳定运行
+
+---
+
+## 目录结构
+
+```
+models/anomaly_detection/
+├── detection.py                    # 主程序(训练+预测)
+├── README.md                        # 本文档
+├── scaler.pkl                       # 数据归一化器(训练产物)
+├── isolation_forest_models.pkl      # 孤立森林模型(训练产物)
+├── three_sigma_model.pkl            # 3σ统计模型(训练产物)
+└── datasets_export_xishan/          # 数据目录(需自行准备)
+    ├── data_export5_1.csv           # UF渗透率数据
+    ├── data_export8_1.csv           # RO1/RO2压差数据
+    ├── data_export9_1.csv           # RO3/RO4压差数据
+    ├── data_export11_1.csv          # RO产水流量数据
+    └── ... (data_export*_2.csv ~ data_export*_26.csv)
+```
+
+---
+
+## 核心功能
+
+### 1. 数据加载与合并
+
+- 批量读取 26 批次历史数据(`data_export*_1.csv ~ data_export*_26.csv`)
+- 自动提取关键指标列并纵向合并
+- 容错处理:文件缺失时跳过并记录日志
+
+### 2. 数据预处理
+
+- **MinMax归一化**: 将所有指标缩放至 [0, 1] 区间
+- **逐列处理**: 每个指标独立归一化,避免量纲影响
+- **保存scaler**: 归一化器保存为 `scaler.pkl`,用于在线预测
+
+### 3. 模型训练
+
+#### 孤立森林 (Isolation Forest)
+- 基于树结构随机分割,异常点更容易被孤立
+- 参数:`n_estimators=100`, `contamination='auto'`
+- 适用场景:单变量离群点检测
+
+#### 3σ统计法 (Three Sigma)
+- 基于正态分布假设,超出 μ±3σ 视为异常
+- 参数:`n_sigma=3`(可调)
+- 适用场景:稳态数据、可解释性要求高
+
+#### One-Class SVM (可选)
+- 基于核函数学习正常数据边界
+- 参数:`nu=0.05`, `kernel='rbf'`
+- 适用场景:复杂边界、非线性分布
+
+### 4. 异常预测
+
+- 输入归一化后的数据
+- 输出预测标签:`-1=异常`, `1=正常`
+- 支持批量预测和实时检测
+
+---
+
+## 监测指标
+
+### UF(超滤)指标
+
+| 指标名称 | 含义 | 单位 | 正常范围 |
+|---------|------|------|---------|
+| UF1Per  | UF1膜渗透率 | - | 根据历史数据确定 |
+| UF2Per  | UF2膜渗透率 | - | 根据历史数据确定 |
+| UF3Per  | UF3膜渗透率 | - | 根据历史数据确定 |
+| UF4Per  | UF4膜渗透率 | - | 根据历史数据确定 |
+
+### RO(反渗透)指标
+
+| 指标名称 | 含义 | 单位 | 异常情况 |
+|---------|------|------|---------|
+| C.M.RO1_DB@DPT_1 | RO1一段压差 | kPa | 压差过高→膜污堵 |
+| C.M.RO1_DB@DPT_2 | RO1二段压差 | kPa | 压差过高→膜污堵 |
+| C.M.RO2_DB@DPT_1 | RO2一段压差 | kPa | 压差过高→膜污堵 |
+| C.M.RO2_DB@DPT_2 | RO2二段压差 | kPa | 压差过高→膜污堵 |
+| C.M.RO3_DB@DPT_1 | RO3一段压差 | kPa | 压差过高→膜污堵 |
+| C.M.RO3_DB@DPT_2 | RO3二段压差 | kPa | 压差过高→膜污堵 |
+| C.M.RO4_DB@DPT_1 | RO4一段压差 | kPa | 压差过高→膜污堵 |
+| C.M.RO4_DB@DPT_2 | RO4二段压差 | kPa | 压差过高→膜污堵 |
+| RO1_CSFlow | RO1产水流量 | m³/h | 流量异常→泵/阀问题 |
+| RO2_CSFlow | RO2产水流量 | m³/h | 流量异常→泵/阀问题 |
+| RO3_CSFlow | RO3产水流量 | m³/h | 流量异常→泵/阀问题 |
+| RO4_CSFlow | RO4产水流量 | m³/h | 流量异常→泵/阀问题 |
+
+**共16个监测指标**
+
+---
+
+## 技术架构
+
+### 整体流程图
+
+```mermaid
+graph TB
+    subgraph 数据加载阶段
+        A[开始] --> B1[读取data_export5_*.csv]
+        A --> B2[读取data_export8_*.csv]
+        A --> B3[读取data_export9_*.csv]
+        A --> B4[读取data_export11_*.csv]
+        B1 --> C[提取UF1Per/UF2Per/UF3Per/UF4Per]
+        B2 --> D[提取RO1/RO2的DPT_1和DPT_2]
+        B3 --> E[提取RO3/RO4的DPT_1和DPT_2]
+        B4 --> F[提取RO1~RO4的CSFlow]
+        C --> G[纵向合并所有批次数据]
+        D --> G
+        E --> G
+        F --> G
+    end
+    
+    subgraph 数据预处理阶段
+        G --> H[DataFrame: 16个监测指标列]
+        H --> I[MinMaxScaler归一化到0-1区间]
+        I --> J[保存scaler.pkl]
+    end
+    
+    subgraph 模型训练阶段
+        J --> K{逐列训练}
+        K --> L1[孤立森林训练]
+        K --> L2[3σ统计计算]
+        K --> L3[One-Class SVM训练可选]
+        
+        L1 --> M1[遍历16个指标列]
+        M1 --> N1[每列fit一个IsolationForest模型]
+        N1 --> O1[保存isolation_forest_models.pkl]
+        
+        L2 --> M2[遍历16个指标列]
+        M2 --> N2[每列计算mean和std]
+        N2 --> O2[保存three_sigma_model.pkl]
+        
+        L3 --> M3[遍历16个指标列]
+        M3 --> N3[每列fit一个OneClassSVM模型]
+        N3 --> O3[保存one_class_svm_models.pkl]
+    end
+    
+    subgraph 异常检测阶段
+        O1 --> P[加载新数据]
+        O2 --> P
+        O3 --> P
+        P --> Q[使用scaler归一化新数据]
+        Q --> R{选择模型预测}
+        R --> S1[孤立森林预测]
+        R --> S2[3σ预测]
+        R --> S3[One-Class SVM预测]
+        S1 --> T[结果融合可选]
+        S2 --> T
+        S3 --> T
+        T --> U[输出预测结果: -1异常/1正常]
+        U --> V[结束]
+    end
+```
+
+### 详细训练与预测流程
+
+```mermaid
+sequenceDiagram
+    participant User as 用户
+    participant Script as detection.py
+    participant Data as 数据文件
+    participant Model as 模型
+    participant Result as 预测结果
+    
+    Note over User,Result: 训练阶段
+    User->>Script: 运行python detection.py
+    Script->>Data: 批量读取26批次CSV
+    Data-->>Script: 返回DataFrame(N行×16列)
+    Script->>Script: MinMaxScaler归一化
+    Script->>Script: 保存scaler.pkl
+    
+    loop 对每个指标列
+        Script->>Model: 训练IsolationForest
+        Script->>Model: 计算3σ统计量
+        Script->>Model: 训练OneClassSVM(可选)
+    end
+    
+    Script->>Script: 保存所有模型.pkl
+    Script-->>User: 训练完成
+    
+    Note over User,Result: 预测阶段
+    User->>Script: 提供新数据DataFrame
+    Script->>Script: 加载scaler.pkl和模型
+    Script->>Script: 归一化新数据
+    
+    loop 对每个指标列
+        Script->>Model: 调用predict()
+        Model-->>Script: 返回-1或1
+    end
+    
+    Script->>Result: 生成预测DataFrame
+    Result-->>User: 返回异常检测结果
+```
+
+### 关键技术
+
+- **sklearn.ensemble.IsolationForest**: 孤立森林实现
+- **sklearn.svm.OneClassSVM**: 单类SVM实现
+- **sklearn.preprocessing.MinMaxScaler**: 数据归一化
+- **pandas**: 数据处理
+- **joblib**: 模型序列化
+
+---
+
+## 数据来源
+
+### 文件命名规则
+
+| 文件模板 | 包含指标 | 数量 |
+|---------|---------|------|
+| `data_export5_{i}.csv` | UF1Per, UF2Per, UF3Per, UF4Per | 4列 |
+| `data_export8_{i}.csv` | RO1/RO2的DPT_1和DPT_2 | 4列 |
+| `data_export9_{i}.csv` | RO3/RO4的DPT_1和DPT_2 | 4列 |
+| `data_export11_{i}.csv` | RO1~RO4的CSFlow | 4列 |
+
+其中 `{i}` 为批次号,范围 `1~26`
+
+### 数据格式要求
+
+```csv
+UF1Per,UF2Per,UF3Per,UF4Per
+0.85,0.87,0.83,0.86
+0.84,0.86,0.82,0.85
+...
+```
+
+- CSV格式,逗号分隔
+- 第一行为列名(必须与代码中定义的列名一致)
+- 数值型数据,缺失值用空或NaN表示
+
+---
+
+## 使用指南
+
+### 1. 环境准备
+
+```bash
+# 安装依赖
+pip install numpy pandas scikit-learn joblib matplotlib
+
+# 确认目录结构
+cd models/anomaly_detection/
+ls datasets_export_xishan/  # 确认数据文件存在
+```
+
+### 2. 训练模型
+
+```bash
+# 运行主程序
+python detection.py
+```
+
+**输出示例**:
+```
+开始加载数据...
+成功读取: data_export5_1.csv
+成功读取: data_export8_1.csv
+...
+数据合并完成,总样本数: 125680
+
+开始数据归一化...
+归一化器已保存为 scaler.pkl
+
+开始训练孤立森林模型...
+已训练 UF1Per 的孤立森林模型
+已训练 UF2Per 的孤立森林模型
+...
+孤立森林模型已保存为 isolation_forest_models.pkl
+
+开始训练3σ模型...
+已计算 UF1Per 的3σ统计量
+...
+3σ模型已保存为 three_sigma_model.pkl
+
+所有模型训练和保存完成!
+```
+
+### 3. 使用模型预测(示例代码)
+
+```python
+import pandas as pd
+import joblib
+
+# 加载模型和归一化器
+scaler = joblib.load("scaler.pkl")
+if_model = joblib.load("isolation_forest_models.pkl")
+ts_model = joblib.load("three_sigma_model.pkl")
+
+# 准备新数据(需包含所有16个指标列)
+new_data = pd.DataFrame({
+    'UF1Per': [0.85, 0.82],
+    'UF2Per': [0.87, 0.84],
+    # ... 其他14个指标
+})
+
+# 归一化
+normalized_data = scaler.transform(new_data)
+normalized_df = pd.DataFrame(normalized_data, columns=new_data.columns)
+
+# 孤立森林预测
+if_predictions = if_model.predict(normalized_df)
+print("孤立森林预测:", if_predictions)
+
+# 3σ预测
+ts_predictions = ts_model.predict(normalized_df, n_sigma=3)
+print("3σ预测:", ts_predictions)
+
+# 结果融合(投票法)
+# -1=异常, 1=正常
+final_predictions = (if_predictions + ts_predictions) // 2
+```
+
+---
+
+## 模型说明
+
+### 1. 孤立森林 (Isolation Forest)
+
+**原理**: 
+- 通过随机选择特征和分割点构建多棵树
+- 异常点更容易被孤立(需要更少的分割次数)
+- 路径长度越短,异常性越高
+
+**优点**:
+- 无需标注数据
+- 对高维数据有效
+- 计算效率高
+
+**缺点**:
+- 对正常数据密度不均匀的情况敏感
+- contamination参数需要先验知识
+
+**参数说明**:
+
+| 参数 | 值 | 说明 |
+|-----|-----|------|
+| n_estimators | 100 | 树的数量 |
+| contamination | 'auto' | 异常比例(自动估计) |
+| random_state | 42 | 随机种子 |
+
+### 2. 3σ统计法 (Three Sigma)
+
+**原理**:
+- 假设数据服从正态分布
+- 计算均值μ和标准差σ
+- 超出 [μ-3σ, μ+3σ] 范围视为异常(覆盖99.7%正常数据)
+
+**优点**:
+- 简单直观,易于理解和解释
+- 计算速度快
+- 可调整灵敏度(修改n_sigma)
+
+**缺点**:
+- 假设数据正态分布(偏态分布效果差)
+- 对极端异常值敏感
+
+**参数说明**:
+
+| 参数 | 值 | 说明 |
+|-----|-----|------|
+| n_sigma | 3 | 阈值系数(推荐2~4) |
+
+### 3. One-Class SVM (可选)
+
+**原理**:
+- 在高维空间寻找包含正常数据的最小超球面
+- 核函数将数据映射到高维空间
+- 边界外的点视为异常
+
+**优点**:
+- 适合复杂非线性边界
+- 理论基础扎实
+
+**缺点**:
+- 计算复杂度高(大数据集慢)
+- 参数调优困难(nu、gamma)
+- 对数据尺度敏感
+
+**参数说明**:
+
+| 参数 | 值 | 说明 |
+|-----|-----|------|
+| nu | 0.05 | 异常比例上界(5%) |
+| kernel | 'rbf' | 径向基核函数 |
+| gamma | 'scale' | 核系数(自动) |
+
+**启用方法**:
+1. 在 `detection.py` 中取消 `OneClassSVMModel` 类的注释(第166-203行)
+2. 取消 `main()` 函数中相关代码的注释(第227-250行)
+
+---
+
+## 配置参数
+
+### 数据路径配置
+
+```python
+# detection.py 第14行
+data_folder = "datasets_export_xishan"
+```
+
+### 文件模板配置
+
+```python
+# detection.py 第22-29行
+file_info = {
+    "data_export5_{}.csv": ["UF1Per", "UF2Per", "UF3Per", "UF4Per"],
+    "data_export8_{}.csv": ["C.M.RO1_DB@DPT_1", "C.M.RO1_DB@DPT_2", 
+                           "C.M.RO2_DB@DPT_1", "C.M.RO2_DB@DPT_2"],
+    "data_export9_{}.csv": ["C.M.RO3_DB@DPT_1", "C.M.RO3_DB@DPT_2", 
+                           "C.M.RO4_DB@DPT_1", "C.M.RO4_DB@DPT_2"],
+    "data_export11_{}.csv": ["RO1_CSFlow", "RO2_CSFlow", "RO3_CSFlow", "RO4_CSFlow"]
+}
+```
+
+### 模型参数调整
+
+**孤立森林**:
+```python
+# detection.py 第98行
+model = IsolationForest(
+    n_estimators=100,        # 增大→更稳定,但训练慢
+    contamination='auto',    # 或设置具体值如0.05
+    random_state=42
+)
+```
+
+**3σ统计**:
+```python
+# 预测时调整
+ts_predictions = ts_model.predict(normalized_df, n_sigma=3)
+# n_sigma=2 → 更敏感(95%覆盖)
+# n_sigma=4 → 更宽松(99.99%覆盖)
+```
+
+---
+
+## 输出结果
+
+### 1. 训练产物
+
+| 文件名 | 大小 | 说明 |
+|--------|------|------|
+| scaler.pkl | ~2KB | MinMax归一化器 |
+| isolation_forest_models.pkl | ~500KB | 孤立森林模型(16个) |
+| three_sigma_model.pkl | ~1KB | 3σ统计量(16个指标的μ和σ) |
+
+### 2. 预测结果格式
+
+**DataFrame示例**:
+
+| UF1Per | UF2Per | C.M.RO1_DB@DPT_1 | ... |
+|--------|--------|------------------|-----|
+| 1      | 1      | -1               | ... |
+| 1      | -1     | 1                | ... |
+| 1      | 1      | 1                | ... |
+
+- `1`: 正常
+- `-1`: 异常
+
+### 3. 结果分析
+
+#### 单指标异常统计
+```python
+# 统计每个指标的异常率
+anomaly_rate = (predictions == -1).sum() / len(predictions)
+print(f"异常率: {anomaly_rate * 100:.2f}%")
+```
+
+#### 多模型融合
+```python
+# 投票法:两个模型都判定为异常才认为异常
+consensus = ((if_pred == -1) & (ts_pred == -1)).astype(int)
+consensus = np.where(consensus, -1, 1)
+```
+
+#### 异常时段定位
+```python
+# 找出连续异常的时间段(需要时间戳列)
+anomaly_indices = np.where(predictions == -1)[0]
+print(f"异常点索引: {anomaly_indices}")
+```
+
+---
+
+## 常见问题
+
+### Q1: 读取数据时报错"未找到文件"
+
+**原因**: `datasets_export_xishan/` 目录不存在或文件命名不符合规则
+
+**解决**:
+```bash
+# 检查目录
+ls datasets_export_xishan/
+
+# 确认文件命名格式
+# 正确: data_export5_1.csv, data_export8_2.csv
+# 错误: data_export5-1.csv, export5_1.csv
+```
+
+### Q2: One-Class SVM 报错 `NameError: name 'OneClassSVMModel' is not defined`
+
+**原因**: One-Class SVM 默认被注释
+
+**解决**:
+1. 打开 `detection.py`
+2. 取消第166-203行的注释(类定义)
+3. 取消第227-250行的注释(训练和预测代码)
+
+### Q3: 训练时内存不足
+
+**原因**: 数据量过大(26批次 × 数万样本)
+
+**解决**:
+```python
+# detection.py 第208行后添加下采样
+merged_data = load_and_merge_data()
+# 随机采样50%数据
+merged_data = merged_data.sample(frac=0.5, random_state=42)
+```
+
+### Q4: 预测结果全是异常或全是正常
+
+**原因**: 
+- 数据分布与训练数据差异大
+- contamination参数不合理
+
+**解决**:
+```python
+# 调整contamination参数
+model = IsolationForest(
+    n_estimators=100,
+    contamination=0.05,  # 假设5%为异常
+    random_state=42
+)
+```
+
+### Q5: 3σ方法对某些指标效果差
+
+**原因**: 数据不服从正态分布(偏态、双峰)
+
+**解决**:
+```python
+# 查看数据分布
+import matplotlib.pyplot as plt
+df['UF1Per'].hist(bins=50)
+plt.show()
+
+# 考虑数据变换(如log变换)或使用孤立森林
+```
+
+### Q6: 如何评估模型效果?
+
+**方法**:
+1. **人工标注部分样本**: 构建测试集计算准确率、召回率
+2. **业务反馈**: 结合实际异常事件验证
+3. **多模型对比**: 比较IF、3σ、OCSVM的一致性
+
+```python
+from sklearn.metrics import classification_report
+
+# 需要人工标注的真实标签
+y_true = [1, 1, -1, 1, -1, ...]
+y_pred = if_model.predict(test_data)
+
+print(classification_report(y_true, y_pred))
+```
+
+### Q7: 如何集成到在线系统?
+
+**方案**:
+```python
+# 1. 封装为函数
+def detect_anomaly(new_data):
+    """
+    参数: new_data - DataFrame,包含16个指标列
+    返回: predictions - DataFrame,-1=异常, 1=正常
+    """
+    scaler = joblib.load("scaler.pkl")
+    if_model = joblib.load("isolation_forest_models.pkl")
+    
+    normalized = scaler.transform(new_data)
+    normalized_df = pd.DataFrame(normalized, columns=new_data.columns)
+    
+    return if_model.predict(normalized_df)
+
+# 2. 构建API(FastAPI示例)
+from fastapi import FastAPI
+app = FastAPI()
+
+@app.post("/detect")
+def detect(data: dict):
+    df = pd.DataFrame([data])
+    result = detect_anomaly(df)
+    return {"prediction": result.tolist()}
+```
+
+

+ 69 - 32
models/anomaly_detection/detection.py

@@ -6,14 +6,19 @@ from sklearn.preprocessing import MinMaxScaler
 from sklearn.ensemble import IsolationForest
 from sklearn.svm import OneClassSVM
 
-# 设置中文字体显示
+# 设置中文字体显示(用于本地可视化时中文不乱码)
 import matplotlib.pyplot as plt
 plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
 
-# 数据文件夹路径
+# 数据文件夹路径(批量 CSV 存放目录)
 data_folder = "datasets_export_xishan"
 
-# 定义要读取的文件和对应的列
+# 定义要读取的文件模板与对应列名(逐批次 data_exportX_{i}.csv, i=1..26)
+# 关键字段含义(业务约定):
+# - UF1Per:UF1膜渗透率(UF1Per)
+# - C.M.RO1_DB@DPT_1:RO1一段压差
+# - C.M.RO1_DB@DPT_2:RO1二段压差
+# - RO1_CSFlow:RO1产水流量
 file_info = {
     "data_export5_{}.csv": ["UF1Per", "UF2Per", "UF3Per", "UF4Per"],
     "data_export8_{}.csv": ["C.M.RO1_DB@DPT_1", "C.M.RO1_DB@DPT_2", 
@@ -24,7 +29,12 @@ file_info = {
 }
 
 def load_and_merge_data():
-    """加载并合并所有数据文件"""
+    """加载并合并所有数据文件
+
+    - 按 file_info 中的模板逐列读取各批 CSV(i=1..26),仅选取关心的指标列
+    - 将成功读取的数据 DataFrame 纵向拼接(ignore_index=True)
+    - 返回合并后的 DataFrame;若无任何成功数据则报错
+    """
     all_data = []
     
     # 循环读取每个文件模板和对应的编号1-26
@@ -35,14 +45,14 @@ def load_and_merge_data():
             file_path = os.path.join(data_folder, filename)
             
             try:
-                # 读取CSV文件的指定列
+                # 读取CSV文件的指定列(仅保留关心指标,减小内存开销)
                 df = pd.read_csv(file_path, usecols=columns)
                 all_data.append(df)
                 print(f"成功读取: {filename}")
             except Exception as e:
                 print(f"读取文件 {filename} 时出错: {e}")
     
-    # 合并所有数据
+    # 合并所有数据(纵向堆叠)
     if not all_data:
         raise ValueError("没有成功读取任何数据文件")
     
@@ -51,7 +61,11 @@ def load_and_merge_data():
     return merged_df
 
 def normalize_data(df):
-    """对数据进行归一化处理"""
+    """对数据进行归一化处理(逐列 Min-Max 到 [0,1])
+
+    - 拟合并转换每一列;保存 scaler 至 `scaler.pkl`
+    - 返回归一化后的 DataFrame 以及 scaler(便于线上/后续反归一化)
+    """
     scaler = MinMaxScaler()
     scaled_data = scaler.fit_transform(df)
     scaled_df = pd.DataFrame(scaled_data, columns=df.columns)
@@ -63,14 +77,22 @@ def normalize_data(df):
     return scaled_df, scaler
 
 class IsolationForestModel:
-    """孤立森林异常检测模型"""
+    """孤立森林异常检测模型(逐列一维检测)
+
+    - 特点:无需标注,适合检测孤立点;contamination='auto' 自动估计比例
+    - 输出:predict → -1 表示异常,1 表示正常
+    """
     def __init__(self):
         self.models = {}  # 存储每列的模型
     
     def fit(self, df):
-        """逐列训练孤立森林模型"""
+        """逐列训练孤立森林模型
+
+        参数:
+        - df: 归一化后的 DataFrame(每列为一个监测指标)
+        """
         for column in df.columns:
-            # 准备数据(需要二维数组)
+            # 准备数据(sklearn 需要二维输入
             X = df[column].values.reshape(-1, 1)
             # 训练模型
             model = IsolationForest(n_estimators=100, contamination='auto', random_state=42)
@@ -80,7 +102,7 @@ class IsolationForestModel:
         return self
     
     def predict(self, df):
-        """预测异常值,-1表示异常,1表示正常"""
+        """预测异常值,-1 表示异常,1 表示正常(逐列)"""
         results = pd.DataFrame()
         for column in df.columns:
             if column not in self.models:
@@ -96,12 +118,16 @@ class IsolationForestModel:
         print(f"孤立森林模型已保存为 {filename}")
 
 class ThreeSigmaModel:
-    """3σ异常检测模型"""
+    """3σ 异常检测模型(逐列基于均值±nσ 的阈值法)
+
+    - 特点:简单、可解释;可调 n_sigma(默认 3)
+    - 输出:-1 表示异常,1 表示正常
+    """
     def __init__(self):
         self.stats = {}  # 存储每列的均值和标准差
     
     def fit(self, df):
-        """计算每列的均值和标准差"""
+        """计算每列的均值和标准差,并缓存统计量"""
         for column in df.columns:
             mean = df[column].mean()
             std = df[column].std()
@@ -110,7 +136,12 @@ class ThreeSigmaModel:
         return self
     
     def predict(self, df, n_sigma=3):
-        """预测异常值,-1表示异常,1表示正常"""
+        """预测异常值(-1=异常,1=正常)
+
+        参数:
+        - df: 归一化后的 DataFrame
+        - n_sigma: 阈值宽度因子,默认 3,即 mean ± 3*std
+        """
         results = pd.DataFrame()
         for column in df.columns:
             if column not in self.stats:
@@ -134,12 +165,16 @@ class ThreeSigmaModel:
 
 '''
 class OneClassSVMModel:
-    """One-Class SVM异常检测模型"""
+    """One-Class SVM 异常检测模型(可选)
+
+    - 对复杂边界更有表达力,但对参数与尺度敏感
+    - 启用前请确保样本量与特征尺度合适
+    """
     def __init__(self):
         self.models = {}  # 存储每列的模型
     
     def fit(self, df):
-        """逐列训练One-Class SVM模型"""
+        """逐列训练 One-Class SVM 模型"""
         for column in df.columns:
             # 准备数据(需要二维数组)
             X = df[column].values.reshape(-1, 1)
@@ -151,7 +186,7 @@ class OneClassSVMModel:
         return self
     
     def predict(self, df):
-        """预测异常值,-1表示异常,1表示正常"""
+        """预测异常值,-1 表示异常,1 表示正常"""
         results = pd.DataFrame()
         for column in df.columns:
             if column not in self.models:
@@ -168,36 +203,37 @@ class OneClassSVMModel:
 '''
 
 def main():
-    # 1. 加载并合并数据
+    # 1. 加载并合并数据(仅读取关心指标列)
     print("开始加载数据...")
     merged_data = load_and_merge_data()
     
-    # 2. 数据归一化
+    # 2. 数据归一化(逐列 Min-Max 到 [0,1] 并保存 scaler)
     print("\n开始数据归一化...")
     normalized_data, scaler = normalize_data(merged_data)
     
-    # 3. 训练并保存孤立森林模型
+    # 3. 训练并保存孤立森林模型(逐列训练)
     print("\n开始训练孤立森林模型...")
     if_model = IsolationForestModel()
     if_model.fit(normalized_data)
     if_model.save()
     
-    # 4. 训练并保存3σ模型
+    # 4. 训练并保存3σ模型(逐列计算统计量)
     print("\n开始训练3σ模型...")
     ts_model = ThreeSigmaModel()
     ts_model.fit(normalized_data)
     ts_model.save()
     
-    # 5. 训练并保存One-Class SVM模型
-    print("\n开始训练One-Class SVM模型...")
-    ocsvm_model = OneClassSVMModel()
-    ocsvm_model.fit(normalized_data)
-    ocsvm_model.save()
+    # 5. 训练并保存 One-Class SVM 模型(可选,默认已注释)
+    # 如需启用,请取消下方注释和类定义(第166-203行)的注释
+    # print("\n开始训练One-Class SVM模型...")
+    # ocsvm_model = OneClassSVMModel()
+    # ocsvm_model.fit(normalized_data)
+    # ocsvm_model.save()
     
     print("\n所有模型训练和保存完成!")
     
-    # 使用模型进行预测
-    sample_data = normalized_data.sample(min(100, len(normalized_data)))  # 随机取100个样本或全部样本(如果不足10个)
+    # 使用模型进行预测(示例:随机抽样 100 条)
+    sample_data = normalized_data.sample(min(100, len(normalized_data)))  # 随机取100个样本或全部样本(如果不足100个)
     
     # 孤立森林预测
     if_predictions = if_model.predict(sample_data)
@@ -209,10 +245,11 @@ def main():
     print("\n3σ预测结果(-1表示异常,1表示正常):")
     print(ts_predictions)
     
-    # One-Class SVM预测
-    ocsvm_predictions = ocsvm_model.predict(sample_data)
-    print("\nOne-Class SVM预测结果(-1表示异常,1表示正常):")
-    print(ocsvm_predictions)
+    # One-Class SVM预测(可选,默认已注释)
+    # 如需启用,请取消下方注释
+    # ocsvm_predictions = ocsvm_model.predict(sample_data)
+    # print("\nOne-Class SVM预测结果(-1表示异常,1表示正常):")
+    # print(ocsvm_predictions)
 
 if __name__ == "__main__":
     main()

+ 626 - 229
models/uf-rl/超滤训练源码/DQN_env.py

@@ -1,3 +1,20 @@
+"""
+超滤强化学习环境模块
+========================
+本模块定义了超滤系统的强化学习环境,包括:
+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
@@ -8,399 +25,779 @@ 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  # 导入模型
+from UF_resistance_models import ResistanceIncreaseModel, ResistanceDecreaseModel  # 导入膜阻力模型
 import copy
 
-# =======================
-# 膜运行参数类:定义膜的基础运行参数
-# =======================
+
+# ==================== 超滤系统参数配置类 ====================
 @dataclass
 class UFParams:
-    # —— 膜动态运行参数 ——
-    q_UF: float = 360.0  # 过滤进水流量(m^3/h)
-    TMP0: float = 0.03 # 初始跨膜压差
-    temp: float = 25.0  # 水温,摄氏度
-
-    # —— 膜阻力模型参数 ——
-    nuK: float =4.92e+01 # 过滤阶段膜阻力增长模型参数
-    slope: float = 3.44e-01 # 全周期不可逆污染阻力增长斜率
-    power: float = 1.032 # 全周期不可逆污染阻力增长幂次
-    tau_bw_s: float = 30.0  # 物洗时长影响时间尺度
-    gamma_t: float = 1.0  # 物洗时长作用指数
-    ceb_removal: float = 150  # CEB去除膜阻力
-
-    # —— 膜运行约束参数 ——
-    global_TMP_limit: float = 0.08  # TMP硬上限(MPa)
-    TMP0_max: float = 0.035 # 初始TMP上限(MPa)
-    TMP0_min: float = 0.01 # 初始TMP下限(MPa)
-    q_UF_max: float = 400.0 # 进水流量上限(m^3/h)
-    q_UF_min: float = 250.0 # 进水流量上限(m^3/h)
-    temp_max: float = 40.0 # 温度上限(摄氏度)
-    temp_min: float = 10.0 # 温度下限(摄氏度)
-    nuK_max: float = 6e+01 # 物理周期总阻力增速上限(m^-1/s)
-    nuK_min: float = 3e+01 # 物理周期总阻力增速下限(m^-1/s)
-    slope_max: float = 10 # 化学周期长期阻力增速斜率上限
-    slope_min: float = 0.1 # 化学周期长期阻力增速斜率下限
-    power_max: float = 1.3 # 化学周期长期阻力增速幂次上限
-    power_min: float = 0.8 # 化学周期长期阻力增速幂次下限
-    ceb_removal_max: float = 150 # CEB去除阻力(已缩放)上限(m^-1)
-    ceb_removal_min: float = 100 # CEB去除阻力(已缩放)下限(m^-1)
-
-    # —— 反洗参数(固定) ——
-    q_bw_m3ph: float = 1000.0  # 物理反洗流量(m^3/h)
-
-    # —— CEB参数 ——
-    T_ceb_interval_h: float = 60.0  # 固定每 k 小时做一次CEB
-    v_ceb_m3: float = 30.0  # CEB用水体积(m^3)
-    t_ceb_s: float = 40 * 60.0  # CEB时长(s)
-
-    # —— 搜索范围(秒) ——
-    L_min_s: float = 3800.0  # 过滤时长下限(s)
-    L_max_s: float = 6000.0  # 过滤时长上限(s)
-    t_bw_min_s: float = 40.0  # 物洗时长下限(s)
-    t_bw_max_s: float = 60.0  # 物洗时长上限(s)
-
-    # —— 网格 ——
-    L_step_s: float = 60.0  # 过滤时长步长(s)
-    t_bw_step_s: float = 5.0  # 物洗时长步长(s)
-
-    # —— 奖励函数参数 ——
-    k_rec = 5.0      # 回收率敏感度
-    k_res = 10.0     # 残余污染敏感度
-    rec_low, rec_high = 0.92, 0.99
-    rr0 = 0.08
-
-
-# =======================
-# 辅助函数:转换膜阻力与跨膜压差
-# =======================
+    """
+    超滤系统参数配置类
+    
+    功能:统一管理超滤系统的所有运行参数,包括:
+    - 膜动态运行参数(流量、温度、压差等)
+    - 膜阻力模型参数(污染增长速率、去除效率等)
+    - 膜运行约束参数(各参数的上下限)
+    - 反洗参数(物理反洗、化学反洗)
+    - 动作搜索范围(过滤时长、反洗时长的取值范围)
+    - 奖励函数参数
+    
+    设计思想:
+    - 使用 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 = 4.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_limit: float = 0.08  
+    # TMP 硬上限(MPa)
+    # 说明:超过此值将导致episode失败,需立即停机
+    
+    # --- 初始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 = 6e+01   # 阻力增长系数上限
+    nuK_min: float = 3e+01   # 阻力增长系数下限
+    
+    # --- 长期污染模型参数约束 ---
+    slope_max: float = 10    # 不可逆污染斜率上限
+    slope_min: float = 0.1   # 不可逆污染斜率下限
+    power_max: float = 1.3   # 不可逆污染幂次上限
+    power_min: float = 0.8   # 不可逆污染幂次下限
+    
+    # --- CEB去除能力约束 ---
+    ceb_removal_max: float = 150  # CEB去除阻力上限(缩放后)
+    ceb_removal_min: float = 100  # CEB去除阻力下限(缩放后)
+
+    # ========== 反洗参数(固定配置) ==========
+    q_bw_m3ph: float = 1000.0  
+    # 物理反洗流量(m³/h)
+    # 说明:反洗流量通常为正常过滤流量的 2-3 倍
+
+    # ========== CEB 化学反洗参数 ==========
+    T_ceb_interval_h: float = 60.0  
+    # CEB 间隔时间(小时)
+    # 说明:每运行约 60 小时执行一次化学增强反洗
+    
+    v_ceb_m3: float = 30.0  
+    # CEB 用水体积(m³)
+    
+    t_ceb_s: float = 40 * 60.0  
+    # CEB 时长(秒,这里为 40 分钟)
+
+    # ========== 强化学习动作空间搜索范围 ==========
+    # 定义智能体可选择的动作范围(离散化)
+    
+    L_min_s: float = 3800.0   # 过滤时长下限(秒,约 63 分钟)
+    L_max_s: float = 6000.0   # 过滤时长上限(秒,约 100 分钟)
+    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: 水温,单位摄氏度
     """
-    锡山水厂 PLC水温校正因子经验公式(25摄氏度标准)
-    返回温度修正后的水粘度(纯水修正),TODO:水厂水质与纯水相差较大,对粘度有一定影响
+    锡山水厂水温粘度修正公式
+    
+    功能:根据水温计算水的动力粘度(考虑温度影响)
+    
+    参数:
+        temp (float): 水温(摄氏度)
+    
+    返回:
+        float: 水的动力粘度 μ (Pa·s)
+    
+    原理:
+    - 水的粘度随温度升高而降低
+    - 25℃时纯水粘度约为 0.00089 Pa·s
+    - 本公式基于锡山水厂PLC系统的经验修正因子
+    
+    注意:
+    - 本公式基于纯水粘度修正
+    - 实际水厂水质与纯水有差异,对粘度有一定影响
+    - 未来可根据实际水质进一步校准
     """
-    x = (temp + 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
+    # 温度归一化(相对于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):
     """
-    计算超滤膜阻力 R = TMP / (J * μ)
-    返回缩小1e10的膜阻力(超滤原膜阻力量级为1e12,过大的绝对值容易导致平稳拟合)
+    由跨膜压差计算膜阻力
+    
+    功能:根据 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 量级
     """
-    A = 128 * 40  # m²,有效膜面积
-    mu = xishan_viscosity(temp) # 温度修正后的水粘度
-    TMP_Pa = tmp * 1e6  # 跨膜压差 MPa -> Pa
-    J = q_UF / A / 3600  # 通量 m³/h -> m³/(m²·s)
+    # 膜有效面积(锡山水厂配置: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
-    R = TMP_Pa / (J * mu) / 1e10 # 缩放膜阻力
+    
+    # 根据 Darcy 定律计算膜阻力并缩放
+    R = TMP_Pa / (J * mu) / 1e10  # [m⁻¹] → [缩放单位]
 
     return float(R)
 
+
 def _calculate_tmp(R, q_UF, temp):
     """
-    还原超滤跨膜压差 TMP
+    由膜阻力计算跨膜压差
+    
+    功能:根据 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) # 温度修正后的水粘度
-    J = q_UF / A / 3600  # 通量 m³/h -> m³/(m²·s)
-    TMP_Pa = R * J * mu * 1e10
-    tmp = TMP_Pa / 1e6
+    # 膜有效面积
+    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():
-    """加载阻力变化模型,仅在首次调用时执行"""
+# ==================== 膜阻力模型加载函数 ====================
 
+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("🔄 Loading resistance models...")
+    print("🔄 正在加载膜阻力模型...")
 
-    # 初始化模型
+    # 初始化模型对象
     resistance_model_fp = ResistanceIncreaseModel()
     resistance_model_bw = ResistanceDecreaseModel()
 
-    # 取得当前脚本所在目录(即 rl_dqn_env.py 或 check_initial_state.py 同目录)
+    # 取当前脚本所在目录
     base_dir = Path(__file__).resolve().parent
 
-    # 构造模型路径
-    fp_path = base_dir / "resistance_model_fp.pth"
-    bw_path = base_dir / "resistance_model_bw.pth"
+    # 构造模型文件路径
+    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}"
+    # 检查模型文件是否存在
+    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("✅ Resistance models loaded successfully from current directory.")
+    print("✅ 膜阻力模型加载成功!")
     return resistance_model_fp, resistance_model_bw
 
 
-# =======================
-# 环境体模型模拟函数
-# =======================
-def _delta_resistance(p, L_h: float) -> float:
+# ==================== 膜阻力模型调用函数 ====================
+
+def _delta_resistance(p, L_s: float) -> float:
     """
-    过滤时段膜阻力上升量:调用 resistance_model_fp.pth 模型
+    计算过滤阶段膜阻力上升量
+    
+    功能:调用预训练的膜阻力上升模型
+    
+    参数:
+        p (UFParams): 超滤运行参数
+        L_s (float): 过滤时长(秒)
+    
+    返回:
+        float: 膜阻力上升量 ΔR
     """
-    return resistance_model_fp(p, L_h)
+    return resistance_model_fp(p, L_s)
+
 
 def phi_bw_of(p, R0: float, R_end: float, L_h_start: float, L_h_next_start: float, t_bw_s: float) -> float:
     """
-    物理冲洗去除膜阻力值:调用 resistance_model_bw 模型
+    计算物理反洗可去除的膜阻力
+    
+    功能:调用预训练的膜阻力下降模型
+    
+    参数:
+        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_start, 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):
     """
-    模拟一个超级周期(多次物理反洗 + 一次化学反洗)
-    返回: (info, next_params)
+    模拟一个完整的超级周期(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  # 小周期过滤时间(h)
-
-    tmp = p.TMP0
-    R0 = _calculate_resistance(p.TMP0, p.q_UF, p.temp)
-    max_tmp_during_filtration = tmp
-    min_tmp_during_filtration = tmp
-    max_residual_increase = 0.0
-
-    t_small_cycle_h = (L_s + t_bw_s) / 3600.0
+    
+    # ========== 初始化周期参数 ==========
+    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
+        k_bw_per_ceb = 1  # 至少包含1个小周期
 
+    # ========== 吨水电耗查找表 ==========
+    # TODO: 需根据实际过滤时间范围更新此表
+    # 键:过滤时长(秒),值:吨水电耗(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
     }
 
-    # --- 循环模拟物理反洗 ---
+    # ========== 循环模拟每个小周期(过滤 + 物理反洗) ==========
     for idx in range(k_bw_per_ceb):
-        tmp_run_start = 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_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_start = (L_s + t_bw_s) / 3600.0 * idx
-        L_h_next_start = (L_s + t_bw_s) / 3600.0 * (idx + 1)
+        # --- 物理反洗阶段:膜阻力下降 ---
+        # 计算累积运行时间(用于长期污染模型)
+        L_h_start = (L_s + t_bw_s) / 3600.0 * idx        # 本小周期起始时间
+        L_h_next_start = (L_s + t_bw_s) / 3600.0 * (idx + 1)  # 下一小周期起始时间
+        
+        # 调用膜阻力下降模型,计算物理反洗可去除的阻力
         reversible_R = phi_bw_of(p, R_run_start, R_peak, L_h_start, 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反洗 ---
-    R_after_ceb = R_peak - p.ceb_removal
-    tmp_after_ceb = _calculate_tmp(R_after_ceb, q_UF, temp)
+    # ========== 化学增强反洗 (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
-    V_loss_super = k_bw_per_ceb * _v_bw_m3(p, t_bw_s) + p.v_ceb_m3
-    V_net = max(0.0, V_feed_super - V_loss_super)
-    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 + p.t_ceb_s / 3600.0
-    daily_prod_time_h = k_bw_per_ceb * L_h / T_super_h * 24.0
-
+    # ========== 水量平衡计算 ==========
+    # 进水总量(所有小周期的过滤进水之和)
+    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] #TODO:需确认新过滤时间范围下的吨水电耗
+    ton_water_energy = energy_lookup[closest_L]  # [kWh/m³]
+    # TODO: 需根据实际过滤时间范围更新电耗查找表
 
-    # --- 信息输出 ---
+    # ========== 构建性能指标字典 ==========
     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,
-        "max_TMP_during_filtration": max_tmp_during_filtration,
-        "min_TMP_during_filtration": min_tmp_during_filtration,
-        "global_TMP_limit":p.global_TMP_limit,
-        "max_residual_increase_per_run": max_residual_increase,
-        "R0": R0,
-        "R_after_ceb": R_after_ceb,
-        "TMP0":p.TMP0,
-        "TMP_after_ceb": tmp_after_ceb,
-        "daily_prod_time_h": daily_prod_time_h,
-        "ton_water_energy_kWh_per_m3": ton_water_energy,
-        "k_bw_per_ceb": k_bw_per_ceb
+        # 运行参数
+        "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_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,  # 最大残余污染增量
+        
+        # 能耗指标
+        "ton_water_energy_kWh_per_m3": ton_water_energy,      # 吨水电耗
     }
 
     # ============================================================
-    # 状态更新:生成 next_params(新状态)
+    # 状态更新:生成下一周期的初始参数
     # ============================================================
 
+    # 深拷贝当前参数(避免修改原对象)
     next_params = copy.deepcopy(p)
 
-    # 更新跨膜压差(TMP)
+    # ========== 必须更新的参数 ==========
+    # 更新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
-    next_params.nuK = p.nuK
-    next_params.q_UF = p.q_UF
-    next_params.temp = p.temp
-
+    # ========== 可选更新的参数(当前保持不变) ==========
+    # 这些参数可根据实际情况动态调整,预留扩展接口
+    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不参与奖励计算,仅考虑回收率与残余污染比例之间的权衡。
-    满足:
-      - 当 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
+    计算强化学习奖励函数
+    
+    功能:
+    - 平衡回收率和残余污染两个目标
+    - 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"]
+    # ========== 提取性能指标 ==========
+    recovery = info["recovery"]  # 回收率 [0-1]
+    
+    # 残余污染比例 = (CEB后阻力 - 初始阻力) / 初始阻力
+    # 反映本超级周期后的净污染累积
     residual_ratio = (info["R_after_ceb"] - info["R0"]) / info["R0"]
 
-    # 回收率奖励(在 [rec_low, rec_high] 内平滑上升)
+    # ========== 回收率奖励项 ==========
+    # 将回收率归一化到 [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)
 
-    # 残余比惩罚(超过rr0时快速变为负值)
+    # ========== 残余污染惩罚项 ==========
+    # 使用 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
 
-    # 再平移使指定点为零:
-    # recovery=0.97, residual=0.1 → 0
-    # recovery=0.90, residual=0.0 → 0
-    # 经验上,这两点几乎对称,因此无需额外线性偏移
-    # 若希望严格归零,可用线性校正:
-    total_reward -= 0.0
+    # 可选:添加平移项使特定点的奖励为零(当前未使用)
+    # total_reward -= offset
 
     return total_reward
 
 
-
 def is_dead_cycle(info: dict) -> bool:
     """
-    判断当前循环是否为成功循环(True)或失败循环(False)
-    失败条件:
-    1. 最大TMP超过设定上限;
-    2. 回收率低于75%;
-    3. 化学反冲洗后膜阻力上升超过10%。
-
+    判断当前超级周期是否成功(可行)
+    
+    功能:
+    - 检查超级周期是否违反运行约束
+    - 用于强化学习的失败判定(terminated条件)
+    - True表示成功,False表示失败
+    
     参数:
-        info: dict
-            simulate_one_supercycle() 返回的指标字典,需包含:
-            - max_TMP_during_filtration
-            - recovery
-            - R_after_ceb
-            - R_run_start
-            - TMP_limit(如果有定义)
+        info (dict): simulate_one_supercycle() 返回的性能指标字典
+    
     返回:
-        bool: True 表示成功循环,False 表示失败循环。
+        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)  # 默认硬约束上限
-    max_tmp = info.get("max_TMP_during_filtration", 0)
-    recovery = info.get("recovery", 1.0)
-    R_after_ceb = info.get("R_after_ceb", 0)
-    R0 = info.get("R0", 1e-6)
-
-    # 判断条件
+    # ========== 获取关键指标 ==========
+    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)  # 初始膜阻力(加小值避免除零)
+
+    # ========== 失败条件检查 ==========
+    # 条件1:TMP超限
     if max_tmp > TMP_limit:
-        return False
+        return False  # 失败
+    
+    # 条件2:回收率过低
     if recovery < 0.75:
-        return False
-    if (R_after_ceb - R0) / R0 > 0.1:
-        return False
+        return False  # 失败
+    
+    # 条件3:残余污染增长过快
+    residual_increase_ratio = (R_after_ceb - R0) / R0
+    if residual_increase_ratio > 0.1:
+        return False  # 失败
 
-    return True
+    # 所有条件通过
+    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
+        # ========== 参数初始化 ==========
+        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
         )
+        
+        # 反洗时长候选值(例:40, 45, 50, 55, 60秒)
         self.t_bw_values = np.arange(
             self.base_params.t_bw_min_s,
-            self.base_params.t_bw_max_s + self.base_params.t_bw_step_s,
+            self.base_params.t_bw_max_s + self.base_params.t_bw_step_s,  # +step确保包含上限
             self.base_params.t_bw_step_s
         )
 
-        self.num_L = len(self.L_values)
-        self.num_bw = len(self.t_bw_values)
+        self.num_L = len(self.L_values)      # 过滤时长选项数
+        self.num_bw = len(self.t_bw_values)  # 反洗时长选项数
 
-        # 单一离散动作空间
+        # 定义单一离散动作空间(笛卡尔积编码)
+        # 动作编号 action = L_idx × num_bw + t_bw_idx
+        # 例:num_L=37, num_bw=5 → 总动作数=185
         self.action_space = spaces.Discrete(self.num_L * self.num_bw)
 
-        # 状态空间,归一化在 _get_obs 中处理
+        # ========== 定义状态空间 ==========
+        # 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):

+ 409 - 107
models/uf-rl/超滤训练源码/DQN_train.py

@@ -1,3 +1,27 @@
+"""
+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
@@ -11,79 +35,177 @@ from stable_baselines3.common.callbacks import BaseCallback
 from DQN_env import UFParams, UFSuperCycleEnv
 
 
-# ==== 定义强化学习超参数 ====
+# ==================== DQN超参数配置类 ====================
 class DQNParams:
     """
-    DQN 超参数定义类
-    用于统一管理模型训练参数
+    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
-
-    # 经验回放缓冲区大小(步数)
-    buffer_size: int = 100000
-
-    # 学习开始前需要收集的步数
-    learning_starts: int = 10000
-
-    # 每次从经验池中采样的样本数量
-    batch_size: int = 32
-
-    # 折扣因子,越接近1越重视长期奖励
-    gamma: float = 0.95
-
-    # 每隔多少步训练一次
-    train_freq: int = 4
-
-    # 目标网络更新间隔
-    target_update_interval: int = 1
-
-    # 软更新系数
-    tau: float = 0.005
-
-    # 初始探索率 ε
-    exploration_initial_eps: float = 1.0
-
-    # 从初始ε衰减到最终ε所占的训练比例
-    exploration_fraction: float = 0.3
-
-    # 最终探索率 ε
-    exploration_final_eps: float = 0.02
-
-    # 日志备注(用于区分不同实验)
-    remark: str = "default"
-
+    # ========== 神经网络参数 ==========
+    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数据记录器
+    
+    功能:
+    - 记录训练过程中每个episode的详细数据
+    - 存储每步的状态、动作、奖励、info等信息
+    - 计算episode级别的统计指标
+    
+    用途:
+    - 训练监控:实时查看智能体表现
+    - 调试分析:定位问题episode
+    - 数据分析:评估策略改进效果
+    """
 
     def __init__(self):
-        self.episode_data = []
-        self.current_episode = []
+        """初始化记录器"""
+        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 {}
+            "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的统计信息
+        
+        参数:
+            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)
-        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))
+        
+        # 计算平均回收率(从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,
@@ -93,27 +215,52 @@ class UFEpisodeRecorder:
         }
 
 
-# ==== 定义强化学习训练回调器 ====
+# ==================== 训练回调器 ====================
 class UFTrainingCallback(BaseCallback):
     """
-    强化学习训练回调,用于记录每一步的数据到 recorder。
-    1. 不依赖环境内部 last_* 属性
-    2. 使用环境接口提供的 obs、actions、rewards、dones、infos
-    3. 自动处理 episode 结束时的统计
+    自定义训练回调器
+    
+    功能:
+    - 在每个训练步骤调用,记录数据到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:
-            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")
-
+            # 从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
@@ -121,11 +268,14 @@ class UFTrainingCallback(BaseCallback):
                 step_done = dones[0] if dones is not None else False
                 step_info = infos[0] if infos is not None else {}
 
-                # 打印当前 step 的信息
+                # 可选:打印当前步信息(用于调试)
                 if self.verbose:
-                    print(f"[Step {self.num_timesteps}] 动作={step_action}, 奖励={step_reward:.3f}, Done={step_done}")
+                    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,
@@ -135,110 +285,262 @@ class UFTrainingCallback(BaseCallback):
                 )
 
         except Exception as e:
+            # 异常处理:避免回调错误中断训练
             if self.verbose:
                 print(f"[Callback Error] {e}")
 
+        # 返回True继续训练
         return True
 
 
 
 
+# ==================== DQN训练器封装类 ====================
 class DQNTrainer:
+    """
+    DQN训练器封装类
+    
+    功能:
+    - 封装DQN智能体的创建、训练、保存、加载流程
+    - 自动管理TensorBoard日志目录
+    - 简化训练脚本编写
+    
+    使用流程:
+    1. 初始化:trainer = DQNTrainer(env, params, callback)
+    2. 训练:trainer.train(total_timesteps)
+    3. 保存:trainer.save()
+    4. 加载:trainer.load(path)
+    """
+
     def __init__(self, env, params, callback=None):
+        """
+        初始化训练器
+        
+        参数:
+            env: Gymnasium环境(通常是DummyVecEnv包装的环境)
+            params (DQNParams): DQN超参数配置
+            callback (BaseCallback, optional): 训练回调器
+        """
         self.env = env
         self.params = params
         self.callback = callback
-        self.log_dir = self._create_log_dir()
-        self.model = self._create_model()
+        self.log_dir = self._create_log_dir()  # 创建日志目录
+        self.model = self._create_model()      # 创建DQN模型
 
     def _create_log_dir(self):
-        # 创建训练日志
+        """
+        创建TensorBoard日志目录
+        
+        返回:
+            str: 日志目录路径
+        
+        目录命名规则:
+            DQN_lr{学习率}_buf{缓冲区大小}_bs{batch大小}_gamma{折扣因子}_
+            exp{探索比例}_{备注}_{时间戳}
+        
+        示例:
+            ./uf_dqn_tensorboard/DQN_lr0.0001_buf100000_bs32_gamma0.95_exp0.3_default_20250105-123456/
+        """
         timestamp = time.strftime("%Y%m%d-%H%M%S")
         log_name = (
-            f"DQN_lr{self.params.learning_rate}_buf{self.params.buffer_size}_bs{self.params.batch_size}"
-            f"_gamma{self.params.gamma}_exp{self.params.exploration_fraction}"
-            f"_{self.params.remark}_{timestamp}"
+            f"DQN_lr{self.params.learning_rate}_"
+            f"buf{self.params.buffer_size}_"
+            f"bs{self.params.batch_size}_"
+            f"gamma{self.params.gamma}_"
+            f"exp{self.params.exploration_fraction}_"
+            f"{self.params.remark}_{timestamp}"
         )
         log_dir = os.path.join("./uf_dqn_tensorboard", log_name)
         os.makedirs(log_dir, exist_ok=True)
         return log_dir
 
     def _create_model(self):
+        """
+        创建DQN模型
+        
+        返回:
+            DQN: Stable-Baselines3的DQN模型实例
+        
+        配置说明:
+            - policy="MlpPolicy": 多层感知机策略网络(全连接网络)
+            - verbose=1: 打印训练信息
+            - tensorboard_log: 指定TensorBoard日志保存路径
+        """
         return 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
+            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,                       # batch大小
+            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                             # TensorBoard日志目录
         )
 
     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"模型训练完成,日志保存在:{self.log_dir}")
+        
+        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}")
+        print(f"💾 模型已保存到:{path}")
 
     def load(self, path):
+        """
+        加载模型
+        
+        参数:
+            path (str): 模型文件路径(.zip文件)
+        """
         self.model = DQN.load(path, env=self.env)
-        print(f"模型已从 {path} 加载")
+        print(f"📥 模型已从 {path} 加载")
 
 
+# ==================== 辅助函数:随机种子设置 ====================
 def set_global_seed(seed: int):
-    """固定全局随机种子,保证训练可复现"""
-    random.seed(seed)
-    np.random.seed(seed)
-    torch.manual_seed(seed)
-    torch.cuda.manual_seed_all(seed)  # 如果使用GPU
+    """
+    固定全局随机种子,保证训练可复现
+    
+    参数:
+        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)
+        """环境工厂函数"""
+        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(f"训练完成 - 总奖励: {stats.get('total_reward', 0):.2f}, 平均回收率: {stats.get('avg_recovery', 0):.3f}")
-
+    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 DQN_train.py
+    
+    训练参数:
+        - total_timesteps=150000: 总训练步数
+        - 约10000个episode(每个episode最多15步)
+        - 约需训练数小时至数天(取决于硬件)
+    """
+    print("="*60)
+    print("🚀 开始训练超滤强化学习智能体")
+    print("="*60)
+    
+    # 初始化超滤参数
     params = UFParams()
-
-    # 训练RL代理
-    print("开始训练RL代理...")
+    
+    # 执行训练
     train_uf_rl_agent(params, total_timesteps=150000)
+    
+    print("\n🎉 训练流程全部完成!")
 

+ 129 - 22
models/uf-rl/超滤训练源码/UF_resistance_models.py

@@ -1,52 +1,159 @@
+"""
+超滤膜阻力模型模块
+====================
+本模块定义了超滤膜阻力的动态变化模型,包括:
+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):
         """
-        计算膜阻力上升量 ΔR
+        前向传播:计算膜阻力上升量
+        
+        参数:
+            p (UFParams): 超滤运行参数对象,包含:
+                - q_UF: 过滤进水流量 [m³/h]
+                - nuK: 膜阻力增长系数 [m⁻¹/s]
+            L_s (float): 过滤时长 [秒]
+        
+        返回:
+            float: 膜阻力上升量 ΔR(已缩放1e10)
+        
+        注意:
+            - 实际膜阻力量级为1e12,为便于数值计算已缩放至1e2量级
+            - 膜面积 A = 128组 × 40 m²/组 = 5120 m²
         """
-        A = 128 * 40.0
-        J = p.q_UF / A / 3600
-        # 膜阻力上升模型(已缩放)
-        dR = p.nuK * J * L_s
+        # 计算膜有效面积(锡山水厂配置: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_start, L_h_next_start, t_bw_s):
         """
-        计算物理反冲洗污染去除比例(受反洗时间影响),最大可去除的可逆膜阻力(受过滤时间影响)
+        前向传播:计算物理反洗能够去除的膜阻力
+        
+        参数:
+            p (UFParams): 超滤运行参数对象,包含:
+                - slope: 不可逆污染增长斜率
+                - power: 不可逆污染增长幂次
+                - tau_bw_s: 反洗时长影响的时间尺度
+            R0 (float): 本超级周期初始膜阻力
+            R_end (float): 过滤结束时的膜阻力(峰值)
+            L_h_start (float): 本小周期起始时的累积运行时间 [小时]
+            L_h_next_start (float): 下一小周期起始时的累积运行时间 [小时]
+            t_bw_s (float): 物理反洗时长 [秒]
+        
+        返回:
+            float: 物理反洗实际去除的膜阻力量
+        
+        计算步骤:
+            1. 基于长期污染模型计算本周期的不可逆污染增量
+            2. 计算可逆污染量 = 总增长 - 不可逆增量
+            3. 应用时间因子(反洗时长的影响)
+            4. 返回实际去除的阻力(不超过可逆污染量)
         """
-
-        # 计算单次不可逆膜阻力(线性依赖于进水时间)
-        # 周期起点和下次起点的理论阻力
+        # ========== 步骤1:计算不可逆污染累积 ==========
+        # 使用幂律模型描述长期不可逆污染的累积
+        # R_irr(t) = R0 + slope × t^power
+        
+        # 本小周期开始时的理论膜阻力(包含之前累积的不可逆污染)
         R_start = R0 + p.slope * (L_h_start ** p.power)
+        
+        # 下一小周期开始时的理论膜阻力
         R_next_start = R0 + p.slope * (L_h_next_start ** p.power)
-
-        # 不可逆污染(反洗后残余增加量)
+        
+        # 本周期新增的不可逆污染(反洗后会残留)
+        # 这部分污染无法通过物理反洗去除
         irreversible_R = max(R_next_start - R_start, 0.0)
-
-        # 本周期的总污染增长量
+        
+        # ========== 步骤2:计算可逆污染量 ==========
+        # 本周期过滤阶段总的膜阻力增长
         total_increase = max(R_end - R_start, 0.0)
-
-        # 可逆污染量 = 本周期总增长 - 不可逆残留
+        
+        # 可逆污染 = 总增长 - 不可逆污染
+        # 这部分污染可以通过物理反洗去除
         reversible_R = max(total_increase - irreversible_R, 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%)
         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))