|
|
@@ -1,22 +1,32 @@
|
|
|
"""
|
|
|
run_dqn_decide_totalstate.py
|
|
|
|
|
|
-全状态UF 超滤 DQN 决策主入口(Inference / Online Assist)
|
|
|
+全状态 UF 超滤 DQN 决策主入口(Inference / Online Assist)
|
|
|
|
|
|
要求数据:
|
|
|
-1. UF1_prev_cycle.csv 示例数据,实际路径可更改,水厂上一周期完整数据,必须包含上一周期全部进水数据,且不能包含其他周期进水数据,分辨率1min,至少包含时间,进水流量,跨膜压差,温度
|
|
|
-2. UF1_init_cycle.csv 示例数据,实际路径可更改,水厂当前周期初始数据,建议在水厂当前周期进水稳定后传入并调用,程序计算该csv数据均值作为初始数据,eg:传入前10min数据,取周期前十分钟均值为初始值
|
|
|
+1. UF_prev_cycle.csv 示例数据,实际路径可更改,水厂上一周期完整数据,必须包含上一周期全部数据,不能包含其他周期数据,分辨率1min,至少包含时间,控制字/步序,进水流量,跨膜压差,温度
|
|
|
+2. UF_init_cycle.csv 示例数据,实际路径可更改,水厂当前周期初始实际数据,在水厂当前周期进水稳定后读取并传入,至少包含时间,控制字/步序,进水流量,跨膜压差,温度
|
|
|
+3. UF_predict_cycle.csv 示例数据,实际路径可更改,水厂当前预测数据,在调用决策模型前调用预测模型生成,包含未来20min的时间及渗透率
|
|
|
+
|
|
|
职责:
|
|
|
-0. 根据水厂csv数据构造状态
|
|
|
1. 构造物理世界(physics)
|
|
|
2. 实例化决策器(UFDQNDecider)
|
|
|
-3. 构造当前工厂状态(observation)
|
|
|
-4. 调用模型给出策略建议
|
|
|
-5. 生成 PLC 下发指令(限幅 / 限速)
|
|
|
-6. 评估该指令在物理模型下的效果(只评估,不下发)
|
|
|
+3. 根据水厂csv数据构造当前工厂状态(observation)
|
|
|
+4. 调用模型生成模型指令
|
|
|
+5. 生成 PLC 下发指令
|
|
|
+6. 评估该指令在物理模型下的效果
|
|
|
"""
|
|
|
|
|
|
from pathlib import Path
|
|
|
+from dataclasses import replace
|
|
|
+
|
|
|
+
|
|
|
+# ============================================================
|
|
|
+# 导入模块
|
|
|
+# ============================================================
|
|
|
+CURRENT_DIR = Path(__file__).resolve().parent
|
|
|
+
|
|
|
+UF_RL_ROOT = CURRENT_DIR.parents[2] # uf_train # uf-rl
|
|
|
|
|
|
# ========== 参数 / 物理 ==========
|
|
|
from env.uf_resistance_models_load import load_resistance_models
|
|
|
@@ -24,15 +34,14 @@ from env.uf_physics import UFPhysicsModel
|
|
|
from env.env_params import UFState, UFActionSpec
|
|
|
from env.env_config_loader import EnvConfigLoader, create_env_params_from_yaml
|
|
|
|
|
|
-
|
|
|
# ========== 决策器 ==========
|
|
|
from rl_model.DQN.uf_decide.dqn_decider import UFDQNDecider
|
|
|
+
|
|
|
# ========== 决策状态构建器 ==========
|
|
|
from rl_model.DQN.dqn_model.dqn_statebuilder import DQNStateBuilder
|
|
|
|
|
|
|
|
|
-
|
|
|
-def build_physics(IS_TIMES, phys_params):
|
|
|
+def build_physics(IS_TIMES, phys_params,state_bounds):
|
|
|
"""
|
|
|
构造与训练一致的物理模型(只做一次)
|
|
|
"""
|
|
|
@@ -40,12 +49,75 @@ def build_physics(IS_TIMES, phys_params):
|
|
|
|
|
|
physics = UFPhysicsModel(
|
|
|
phys_params=phys_params,
|
|
|
+ state_bounds=state_bounds,
|
|
|
resistance_model_fp=res_fp,
|
|
|
resistance_model_bw=res_bw,
|
|
|
IS_TIMES = IS_TIMES
|
|
|
)
|
|
|
return physics
|
|
|
|
|
|
+
|
|
|
+def check_state_bounds(current_state, state_bounds, unit_name):
|
|
|
+ """
|
|
|
+ 检查当前状态是否在边界范围内
|
|
|
+
|
|
|
+ 参数:
|
|
|
+ current_state: UFState对象,包含TMP, q_UF, temp
|
|
|
+ state_bounds: 状态边界对象
|
|
|
+ unit_name: 机组名称(如 "UF1")
|
|
|
+
|
|
|
+ 返回:
|
|
|
+ dict: 错误信息字典,格式 {"error_time": str, "error_feature": str}
|
|
|
+ 如果没有错误,返回 None
|
|
|
+ """
|
|
|
+ from datetime import datetime
|
|
|
+
|
|
|
+ error_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
+
|
|
|
+ # 检查各项参数是否在边界范围内
|
|
|
+ TMP0_min = state_bounds.TMP0_min
|
|
|
+ TMP0_max = state_bounds.TMP0_max
|
|
|
+ if not (TMP0_min <= current_state.TMP <= TMP0_max):
|
|
|
+ return {
|
|
|
+ "error_time": error_time,
|
|
|
+ "error_feature": f"{unit_name}Per"
|
|
|
+ }
|
|
|
+
|
|
|
+ nuK_min = state_bounds.nuK_min
|
|
|
+ nuK_max = state_bounds.nuK_max
|
|
|
+ if not (nuK_min <= current_state.nuK <= nuK_max):
|
|
|
+ return {
|
|
|
+ "error_time": error_time,
|
|
|
+ "error_feature": f"{unit_name}Per"
|
|
|
+ }
|
|
|
+
|
|
|
+ slope_min = state_bounds.slope_min
|
|
|
+ slope_max = state_bounds.slope_max
|
|
|
+ if not (slope_min <= current_state.slope <= slope_max):
|
|
|
+ return {
|
|
|
+ "error_time": error_time,
|
|
|
+ "error_feature": f"{unit_name}Per"
|
|
|
+ }
|
|
|
+
|
|
|
+ power_min = state_bounds.power_min
|
|
|
+ power_max = state_bounds.power_max
|
|
|
+ if not (power_min <= current_state.power <= power_max):
|
|
|
+ return {
|
|
|
+ "error_time": error_time,
|
|
|
+ "error_feature": f"{unit_name}Per"
|
|
|
+ }
|
|
|
+
|
|
|
+ ceb_removal_min = state_bounds.ceb_removal_min
|
|
|
+ ceb_removal_max = state_bounds.ceb_removal_max
|
|
|
+ if not (ceb_removal_min <= current_state.ceb_removal <= ceb_removal_max):
|
|
|
+ return {
|
|
|
+ "error_time": error_time,
|
|
|
+ "error_feature": f"{unit_name}Per"
|
|
|
+ }
|
|
|
+
|
|
|
+ return None
|
|
|
+
|
|
|
+
|
|
|
def generate_plc_instructions(action_spec,current_L_s, current_t_bw_s, model_prev_L_s, model_prev_t_bw_s, model_L_s, model_t_bw_s):
|
|
|
"""
|
|
|
根据工厂当前值、模型上一轮决策值和模型当前轮决策值,生成PLC指令。
|
|
|
@@ -105,16 +177,15 @@ def generate_plc_instructions(action_spec,current_L_s, current_t_bw_s, model_pre
|
|
|
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}]")
|
|
|
+ model_L_s = max(action_spec.L_min_s, min(model_L_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}]")
|
|
|
+ model_t_bw_s = max(action_spec.t_bw_min_s, min(model_t_bw_s, action_spec.t_bw_max_s))
|
|
|
+
|
|
|
|
|
|
print(f"过滤时长基准: {source_L}, 值: {effective_current_L}")
|
|
|
print(f"反洗时长基准: {source_t_bw}, 值: {effective_current_t_bw}")
|
|
|
@@ -167,7 +238,8 @@ def calc_uf_cycle_metrics(current_state, max_tmp_during_filtration, min_tmp_duri
|
|
|
|
|
|
# 获得模型模拟周期信息
|
|
|
k_bw_per_ceb = info["k_bw_per_ceb"]
|
|
|
- ton_water_energy_kWh_per_m3 = info["ton_water_energy_kWh_per_m3"]
|
|
|
+ refer_ton_water_energy = info["refer_ton_water_energy"]
|
|
|
+ ton_water_energy = info["ton_water_energy"]
|
|
|
recovery = info["recovery"]
|
|
|
daily_prod_time_h = info["daily_prod_time_h"]
|
|
|
|
|
|
@@ -183,7 +255,8 @@ def calc_uf_cycle_metrics(current_state, max_tmp_during_filtration, min_tmp_duri
|
|
|
|
|
|
return {
|
|
|
"k_bw_per_ceb": k_bw_per_ceb,
|
|
|
- "ton_water_energy_kWh_per_m3": ton_water_energy_kWh_per_m3,
|
|
|
+ "refer_ton_water_energy": refer_ton_water_energy,
|
|
|
+ "ton_water_energy": ton_water_energy,
|
|
|
"recovery": recovery,
|
|
|
"daily_prod_time_h": daily_prod_time_h,
|
|
|
"max_permeability": max_permeability
|
|
|
@@ -211,8 +284,8 @@ def run_dqn_decide(
|
|
|
model_path=model_path,
|
|
|
seed=0,
|
|
|
)
|
|
|
- # 模型决策(不推进真实环境)
|
|
|
|
|
|
+ # 模型决策
|
|
|
decision = decider.decide(current_state)
|
|
|
action_id = decision["action_id"]
|
|
|
model_L_s = decision["L_s"]
|
|
|
@@ -226,22 +299,19 @@ def run_dqn_decide(
|
|
|
# ==============================
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
- THIS_FILE = Path(__file__).resolve()
|
|
|
- UF_RL_ROOT = THIS_FILE.parents[3]
|
|
|
- CONFIG_PATH = UF_RL_ROOT / "xishan" / "uf_analyze_config.yaml"
|
|
|
- ENV_CONFIG_PATH = UF_RL_ROOT / "xishan" / "env_config.yaml"
|
|
|
- MODEL_PATH = UF_RL_ROOT / "xishan" / "48h_dqn_model.zip"
|
|
|
- prev_cycle_csv = "test_online_datasets/UF1_prev_cycle.csv"
|
|
|
- init_cycle_csv = "test_online_datasets/UF1_init_cycle.csv"
|
|
|
- IS_TIMES = False # 新增指定变量,表示CEB间隔为时间控制/次数控制,T表示48次bw一次CEB,F表示48h一次CEB
|
|
|
-
|
|
|
- # 构建强化学习状态
|
|
|
- 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,
|
|
|
- )
|
|
|
+ # ========== 模型及配置路径指定 ==========
|
|
|
+ IS_TIMES = False # 外部指定变量,表示CEB间隔为时间控制/次数控制,T表示48次bw一次CEB,F表示48h一次CEB
|
|
|
+ DATA_CONFIG_PATH = UF_RL_ROOT / "config_and_model" / "xishan" / "uf_analyze_config.yaml"
|
|
|
+ MODEL_PATH = UF_RL_ROOT / "config_and_model" / "xishan" / "48h_dqn_model.zip" # 需根据IS_TIMES变量值指定模型为48h_dqn_model.zip/48times_dqn_model.zip
|
|
|
+ ENV_CONFIG_PATH = UF_RL_ROOT / "config_and_model" / "xishan" / "env_config.yaml" # 环境配置路径
|
|
|
|
|
|
+ # ========== 外部调用输入 ==========
|
|
|
+ unit_name = "UF1"
|
|
|
+ prev_cycle_csv = CURRENT_DIR / "test_online_datasets" / "UF_prev_cycle.csv"
|
|
|
+ init_cycle_csv = CURRENT_DIR / "test_online_datasets" / "UF_init_cycle.csv"
|
|
|
+ predict_cycle_csv = CURRENT_DIR / "test_online_datasets" / "UF_predict_cycle.csv"
|
|
|
+
|
|
|
+ # ========== 模型及配置加载 ==========
|
|
|
config_loader = EnvConfigLoader(ENV_CONFIG_PATH)
|
|
|
config_loader.validate_config()
|
|
|
config_loader.print_config_summary()
|
|
|
@@ -252,8 +322,26 @@ if __name__ == "__main__":
|
|
|
reward_params, # UFRewardParams
|
|
|
state_bounds # UFStateBounds
|
|
|
) = create_env_params_from_yaml(ENV_CONFIG_PATH)
|
|
|
- physics = build_physics(IS_TIMES)
|
|
|
+ physics = build_physics(IS_TIMES, phys_params,state_bounds)
|
|
|
+
|
|
|
+ # ========== 调用模型生成模型指令 ==========
|
|
|
+ # 基于外部输入构建当前状态
|
|
|
+ state_builder = DQNStateBuilder(config_path=DATA_CONFIG_PATH)
|
|
|
+ current_state: UFState = state_builder.build_from_csv_pair(
|
|
|
+ unit_name,
|
|
|
+ uf_state_default,
|
|
|
+ state_bounds,
|
|
|
+ prev_cycle_csv=prev_cycle_csv,
|
|
|
+ init_cycle_csv=init_cycle_csv,
|
|
|
+ predict_cycle_csv=predict_cycle_csv
|
|
|
+ )
|
|
|
+
|
|
|
+ # 状态异常检查(仅检查,不中断,出现异常时后续归一化中将异常状态强制归一化至上下限)
|
|
|
+ error_result = check_state_bounds(current_state, state_bounds, unit_name)
|
|
|
+ if error_result:
|
|
|
+ print(f"错误发生时间: {error_result['error_time']};错误特征量:{error_result['error_feature']}")
|
|
|
|
|
|
+ # 模型输出指令
|
|
|
action_id, model_L_s, model_t_bw_s = run_dqn_decide(
|
|
|
model_path=MODEL_PATH,
|
|
|
physics=physics,
|
|
|
@@ -263,15 +351,15 @@ if __name__ == "__main__":
|
|
|
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,
|
|
|
+ L_s, t_bw_s = generate_plc_instructions(action_spec, 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)
|
|
|
@@ -280,7 +368,8 @@ if __name__ == "__main__":
|
|
|
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['refer_ton_water_energy']}")
|
|
|
+ print(f"指令对应的计算吨水电耗: {execution_result['ton_water_energy']}")
|
|
|
print(f"指令对应的回收率: {execution_result['recovery']}")
|
|
|
print(f"指令对应的日均产水时间: {execution_result['daily_prod_time_h']}")
|
|
|
print(f"指令对应的最高渗透率: {execution_result['max_permeability']}")
|