run_dqn_decide.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. """
  2. run_dqn_decide.py
  3. UF 超滤 DQN 决策主入口(Inference / Online Assist)
  4. 职责:
  5. 1. 构造物理世界(physics)
  6. 2. 实例化决策器(UFDQNDecider)
  7. 3. 构造当前工厂状态(observation)
  8. 4. 调用模型给出策略建议
  9. 5. 生成 PLC 下发指令(限幅 / 限速)
  10. 6. 评估该指令在物理模型下的效果(只评估,不下发)
  11. """
  12. from pathlib import Path
  13. from dataclasses import replace
  14. # ============================================================
  15. # 导入模块
  16. # ============================================================
  17. CURRENT_DIR = Path(__file__).resolve().parent
  18. UF_RL_ROOT = CURRENT_DIR.parents[2] # uf_train # uf-rl
  19. # ========== 参数 / 物理 ==========
  20. from env.uf_resistance_models_load import load_resistance_models
  21. from env.uf_physics import UFPhysicsModel
  22. from env.env_params import UFState, UFActionSpec
  23. from env.env_config_loader import EnvConfigLoader, create_env_params_from_yaml
  24. # ========== 决策器 ==========
  25. from rl_model.DQN.uf_decide.dqn_decider import UFDQNDecider
  26. def build_physics(IS_TIMES, phys_params,state_bounds):
  27. """
  28. 构造与训练一致的物理模型(只做一次)
  29. """
  30. res_fp, res_bw = load_resistance_models(phys_params)
  31. physics = UFPhysicsModel(
  32. phys_params=phys_params,
  33. state_bounds=state_bounds,
  34. resistance_model_fp=res_fp,
  35. resistance_model_bw=res_bw,
  36. IS_TIMES = IS_TIMES
  37. )
  38. return physics
  39. def check_state_bounds(current_state, state_bounds, unit_name):
  40. """
  41. 检查当前状态是否在边界范围内
  42. 参数:
  43. current_state: UFState对象,包含TMP, q_UF, temp
  44. state_bounds: 状态边界对象
  45. unit_name: 机组名称(如 "UF1")
  46. 返回:
  47. dict: 错误信息字典,格式 {"error_time": str, "error_feature": str}
  48. 如果没有错误,返回 None
  49. """
  50. from datetime import datetime
  51. error_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  52. # 检查各项参数是否在边界范围内
  53. TMP0_min = state_bounds.TMP0_min
  54. TMP0_max = state_bounds.TMP0_max
  55. if not (TMP0_min <= current_state.TMP <= TMP0_max):
  56. return {
  57. "error_time": error_time,
  58. "error_feature": f"{unit_name}Per"
  59. }
  60. return None
  61. 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):
  62. """
  63. 根据工厂当前值、模型上一轮决策值和模型当前轮决策值,生成PLC指令。
  64. 新增功能:
  65. 1. 处理None值情况:如果模型上一轮值为None,则使用工厂当前值;
  66. 如果工厂当前值也为None,则返回None并提示错误。
  67. """
  68. action_spec = action_spec
  69. adjustment_threshold = 1.0
  70. # 处理None值情况
  71. if model_prev_L_s is None:
  72. if current_L_s is None:
  73. print("错误: 过滤时长的工厂当前值和模型上一轮值均为None")
  74. return None, None
  75. else:
  76. # 使用工厂当前值作为基准
  77. effective_current_L = current_L_s
  78. source_L = "工厂当前值(模型上一轮值为None)"
  79. else:
  80. # 模型上一轮值不为None,继续检查工厂当前值
  81. if current_L_s is None:
  82. effective_current_L = model_prev_L_s
  83. source_L = "模型上一轮值(工厂当前值为None)"
  84. else:
  85. effective_current_L = model_prev_L_s
  86. source_L = "模型上一轮值"
  87. # 对反洗时长进行同样的处理
  88. if model_prev_t_bw_s is None:
  89. if current_t_bw_s is None:
  90. print("错误: 反洗时长的工厂当前值和模型上一轮值均为None")
  91. return None, None
  92. else:
  93. effective_current_t_bw = current_t_bw_s
  94. source_t_bw = "工厂当前值(模型上一轮值为None)"
  95. else:
  96. if current_t_bw_s is None:
  97. effective_current_t_bw = model_prev_t_bw_s
  98. source_t_bw = "模型上一轮值(工厂当前值为None)"
  99. else:
  100. effective_current_t_bw = model_prev_t_bw_s
  101. source_t_bw = "模型上一轮值"
  102. # 检测所有输入值是否在规定范围内(只对非None值进行检查)
  103. # 工厂当前值检查(警告)
  104. if current_L_s is not None and not (action_spec.L_min_s <= current_L_s <= action_spec.L_max_s):
  105. print(f"警告: 当前过滤时长 {current_L_s} 秒不在允许范围内 [{action_spec.L_min_s}, {action_spec.L_max_s}]")
  106. 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):
  107. print(f"警告: 当前反洗时长 {current_t_bw_s} 秒不在允许范围内 [{action_spec.t_bw_min_s}, {action_spec.t_bw_max_s}]")
  108. # 模型上一轮决策值检查(警告)
  109. if model_prev_L_s is not None and not (action_spec.L_min_s <= model_prev_L_s <= action_spec.L_max_s):
  110. print(f"警告: 模型上一轮过滤时长 {model_prev_L_s} 秒不在允许范围内 [{action_spec.L_min_s}, {action_spec.L_max_s}]")
  111. 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):
  112. print(f"警告: 模型上一轮反洗时长 {model_prev_t_bw_s} 秒不在允许范围内 [{action_spec.t_bw_min_s}, {action_spec.t_bw_max_s}]")
  113. # 模型当前轮决策值检查
  114. if model_L_s is None:
  115. raise ValueError("错误: 决策模型建议的过滤时长不能为None")
  116. model_L_s = max(action_spec.L_min_s, min(model_L_s, action_spec.L_max_s))
  117. if model_t_bw_s is None:
  118. raise ValueError("错误: 决策模型建议的反洗时长不能为None")
  119. model_t_bw_s = max(action_spec.t_bw_min_s, min(model_t_bw_s, action_spec.t_bw_max_s))
  120. print(f"过滤时长基准: {source_L}, 值: {effective_current_L}")
  121. print(f"反洗时长基准: {source_t_bw}, 值: {effective_current_t_bw}")
  122. # 使用选定的基准值进行计算调整
  123. L_diff = model_L_s - effective_current_L
  124. L_adjustment = 0
  125. if abs(L_diff) >= adjustment_threshold * action_spec.L_step_s:
  126. if L_diff >= 0:
  127. L_adjustment = action_spec.L_step_s
  128. else:
  129. L_adjustment = -action_spec.L_step_s
  130. next_L_s = effective_current_L + L_adjustment
  131. t_bw_diff = model_t_bw_s - effective_current_t_bw
  132. t_bw_adjustment = 0
  133. if abs(t_bw_diff) >= adjustment_threshold * action_spec.t_bw_step_s:
  134. if t_bw_diff >= 0:
  135. t_bw_adjustment = action_spec.t_bw_step_s
  136. else:
  137. t_bw_adjustment = -action_spec.t_bw_step_s
  138. next_t_bw_s = effective_current_t_bw + t_bw_adjustment
  139. return next_L_s, next_t_bw_s
  140. def calc_uf_cycle_metrics(current_state, max_tmp_during_filtration, min_tmp_during_filtration, L_s: float, t_bw_s: float):
  141. """
  142. 计算 UF 超滤系统的核心性能指标
  143. 参数:
  144. L_s (float): 单次过滤时间(秒)
  145. t_bw_s (float): 单次反洗时间(秒)
  146. 返回:
  147. dict: {
  148. "k_bw_per_ceb": 小周期次数,
  149. "ton_water_energy_kWh_per_m3": 吨水电耗,
  150. "recovery": 回收率,
  151. "net_delivery_rate_m3ph": 净供水率 (m³/h),
  152. "daily_prod_time_h": 日均产水时间 (小时/天)
  153. "max_permeability": 全周期最高渗透率(lmh/bar)
  154. }
  155. """
  156. # 模拟该参数下的超级周期
  157. info, next_state = physics.simulate_one_supercycle(current_state, L_s=L_s, t_bw_s=t_bw_s)
  158. # 获得模型模拟周期信息
  159. k_bw_per_ceb = info["k_bw_per_ceb"]
  160. refer_ton_water_energy = info["refer_ton_water_energy"]
  161. ton_water_energy = info["ton_water_energy"]
  162. recovery = info["recovery"]
  163. daily_prod_time_h = info["daily_prod_time_h"]
  164. # 获得模型模拟周期内最高跨膜压差/最低跨膜压差
  165. if max_tmp_during_filtration is None:
  166. max_tmp_during_filtration = info["max_TMP_during_filtration"]
  167. if min_tmp_during_filtration is None:
  168. min_tmp_during_filtration = info["min_TMP_during_filtration"]
  169. # 计算最高渗透率
  170. max_permeability = 100 * current_state.q_UF / (128*40) / min_tmp_during_filtration
  171. return {
  172. "k_bw_per_ceb": k_bw_per_ceb,
  173. "refer_ton_water_energy": refer_ton_water_energy,
  174. "ton_water_energy": ton_water_energy,
  175. "recovery": recovery,
  176. "daily_prod_time_h": daily_prod_time_h,
  177. "max_permeability": max_permeability
  178. }
  179. def run_dqn_decide(
  180. model_path: Path,
  181. physics,
  182. action_spec,
  183. reward_params,
  184. state_bounds,
  185. # -------- 工厂当前值 --------
  186. current_state: UFState
  187. ):
  188. """
  189. 单轮 DQN 决策流程
  190. """
  191. # 构造决策器
  192. decider = UFDQNDecider(
  193. physics=physics,
  194. action_spec=action_spec,
  195. reward_params=reward_params,
  196. state_bounds=state_bounds,
  197. model_path=model_path,
  198. seed=0,
  199. )
  200. # 模型决策
  201. decision = decider.decide(current_state)
  202. action_id = decision["action_id"]
  203. model_L_s = decision["L_s"]
  204. model_t_bw_s = decision["t_bw_s"]
  205. return action_id, model_L_s, model_t_bw_s
  206. # ==============================
  207. # 示例调用
  208. # ==============================
  209. if __name__ == "__main__":
  210. # ========== 模型及配置路径指定 ==========
  211. IS_TIMES = False # 外部指定变量,表示CEB间隔为时间控制/次数控制,T表示48次bw一次CEB,F表示48h一次CEB
  212. MODEL_PATH = UF_RL_ROOT / "config_and_model" / "anzhen" / "48h_dqn_model.zip" # 需根据IS_TIMES变量值指定模型为48h_dqn_model.zip/48times_dqn_model.zip
  213. ENV_CONFIG_PATH = UF_RL_ROOT / "config_and_model" / "anzhen" / "env_config.yaml" # 环境配置路径
  214. # ========== 外部调用输入 ==========
  215. # 轻量版,仅输入当前周期起始状态变量
  216. units_to_run = ["UF1"] # 新增输入:本次调用的机组对象名
  217. TMP0 = 0.07 # 原始 TMP0
  218. q_UF = 300 # 进水流量
  219. temp = 20.0 #进水温度
  220. # ========== 模型及配置加载 ==========
  221. config_loader = EnvConfigLoader(ENV_CONFIG_PATH)
  222. config_loader.validate_config()
  223. config_loader.print_config_summary()
  224. (
  225. uf_state_default, # UFState默认值
  226. phys_params, # UFPhysicsParams
  227. action_spec, # UFActionSpec
  228. reward_params, # UFRewardParams
  229. state_bounds # UFStateBounds
  230. ) = create_env_params_from_yaml(ENV_CONFIG_PATH)
  231. physics = build_physics(IS_TIMES, phys_params,state_bounds)
  232. # ========== 调用模型生成模型指令 ==========
  233. # 基于外部输入构建当前状态
  234. current_state = replace(
  235. uf_state_default,
  236. TMP=TMP0,
  237. q_UF=q_UF,
  238. temp=temp
  239. )
  240. # 状态异常检查(仅检查,不中断,出现异常时后续归一化中将异常状态强制归一化至上下限)
  241. for unit_name in units_to_run:
  242. error_result = check_state_bounds(current_state, state_bounds, unit_name)
  243. if error_result:
  244. print(f"错误发生时间: {error_result['error_time']};错误特征量:{error_result['error_feature']}")
  245. # 模型输出指令
  246. action_id, model_L_s, model_t_bw_s = run_dqn_decide(
  247. model_path=MODEL_PATH,
  248. physics=physics,
  249. action_spec=action_spec,
  250. reward_params=reward_params,
  251. state_bounds=state_bounds,
  252. current_state=current_state,
  253. ) # 环境实例化,模型加载等功能放在UFDQNDecider类中
  254. # ========== 生成工厂下发指令 ==========
  255. current_L_s = 3800
  256. current_t_bw_s = 40
  257. model_prev_L_s = 4040
  258. model_prev_t_bw_s = 60
  259. 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,
  260. model_t_bw_s) # 获取模型下发指令
  261. # ========== 生成指令模拟执行结果 ==========
  262. max_tmp_during_filtration = 0.050176 # 新增工厂数据接口:周期最高/最低跨膜压差,无工厂数据接入时传入None,calc_uf_cycle_metrics()自动获取模拟周期中的跨膜压差最值
  263. min_tmp_during_filtration = 0.012496
  264. execution_result = calc_uf_cycle_metrics(current_state, max_tmp_during_filtration, min_tmp_during_filtration, L_s, t_bw_s)
  265. print("\n===== 单步决策结果 =====")
  266. print(f"模型选择的动作: {action_id}")
  267. print(f"模型选择的L_s: {model_L_s} 秒, 模型选择的t_bw_s: {model_t_bw_s} 秒")
  268. print(f"指令下发的L_s: {L_s} 秒, 指令下发的t_bw_s: {t_bw_s} 秒")
  269. print(f"指令对应的反洗次数: {execution_result['k_bw_per_ceb']}")
  270. print(f"指令对应的理论参考吨水电耗: {execution_result['refer_ton_water_energy']}")
  271. print(f"指令对应的计算吨水电耗: {execution_result['ton_water_energy']}")
  272. print(f"指令对应的回收率: {execution_result['recovery']}")
  273. print(f"指令对应的日均产水时间: {execution_result['daily_prod_time_h']}")
  274. print(f"指令对应的最高渗透率: {execution_result['max_permeability']}")