UF_decide.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. # UF_decide.py
  2. from dataclasses import dataclass
  3. import numpy as np
  4. @dataclass
  5. class UFParams:
  6. # —— 膜与运行参数 ——
  7. q_UF: float = 360.0 # 过滤进水流量(m^3/h)
  8. TMP0: float = 0.03 # 初始TMP(MPa)
  9. TMP_max: float = 0.06 # TMP硬上限(MPa)
  10. # —— 膜污染动力学 ——
  11. alpha: float = 1e-6 # TMP增长系数
  12. belta: float = 1.1 # 幂指数
  13. # —— 反洗参数(固定) ——
  14. q_bw_m3ph: float = 1000.0 # 物理反洗流量(m^3/h)
  15. # —— CEB参数(固定) ——
  16. T_ceb_interval_h: float = 48.0 # 固定每 k 小时做一次CEB
  17. v_ceb_m3: float = 30.0 # CEB用水体积(m^3)
  18. t_ceb_s: float = 40 * 60.0 # CEB时长(s)
  19. phi_ceb: float = 1.0 # CEB去除比例(简化:完全恢复到TMP0)
  20. # —— 约束与收敛 ——
  21. dTMP: float = 0.0005 # 单次产水结束时,相对TMP0最大升幅(MPa)
  22. # —— 搜索范围(秒) ——
  23. L_min_s: float = 3600.0 # 过滤时长下限(s)
  24. L_max_s: float = 4200.0 # 过滤时长上限(s)
  25. t_bw_min_s: float = 40.0 # 物洗时长下限(s)
  26. t_bw_max_s: float = 60.0 # 物洗时长上限(s)
  27. # —— 物理反洗恢复函数参数 ——
  28. phi_bw_min: float = 0.7 # 物洗去除比例最小值
  29. phi_bw_max: float = 1.0 # 物洗去除比例最大值
  30. L_ref_s: float = 4000.0 # 过滤时长影响时间尺度
  31. tau_bw_s: float = 30.0 # 物洗时长影响时间尺度
  32. gamma_t: float = 1.0 # 物洗时长作用指数
  33. # —— 网格 ——
  34. L_step_s: float = 60.0 # 过滤时长步长(s)
  35. t_bw_step_s: float = 5.0 # 物洗时长步长(s)
  36. # 多目标加权及高TMP惩罚
  37. w_rec: float = 0.8 # 回收率权重
  38. w_rate: float = 0.2 # 净供水率权重
  39. w_headroom: float = 0.3 # 贴边惩罚权重
  40. r_headroom: float = 2.0 # 贴边惩罚幂次
  41. headroom_hardcap: float = 0.98 # 超过此比例直接视为不可取
  42. def _delta_tmp(p: UFParams, L_h: float) -> float:
  43. # 过滤时段TMP上升量
  44. return float(p.alpha * (p.q_UF ** p.belta) * L_h)
  45. def _v_bw_m3(p: UFParams, t_bw_s: float) -> float:
  46. # 物理反洗水耗
  47. return float(p.q_bw_m3ph * (float(t_bw_s) / 3600.0))
  48. def phi_bw_of(p: UFParams, L_s: float, t_bw_s: float) -> float:
  49. # 物洗去除比例:随过滤时长增长上界收缩,随物洗时长增长趋饱和
  50. L = max(float(L_s), 1.0)
  51. t = max(float(t_bw_s), 1e-6)
  52. upper_L = p.phi_bw_min + (p.phi_bw_max - p.phi_bw_min) * np.exp(- L / p.L_ref_s)
  53. time_gain = 1.0 - np.exp(- (t / p.tau_bw_s) ** p.gamma_t)
  54. phi = upper_L * time_gain
  55. return float(np.clip(phi, 0.0, 0.999))
  56. def simulate_one_supercycle(p: UFParams, L_s: float, t_bw_s: float):
  57. """
  58. 返回 (是否可行, 指标字典)
  59. - 支持动态CEB次数:48h固定间隔
  60. - 增加日均产水时间和吨水电耗
  61. """
  62. L_h = float(L_s) / 3600.0 # 小周期过滤时间(h)
  63. tmp = p.TMP0
  64. max_tmp_during_filtration = tmp
  65. max_residual_increase = 0.0
  66. # 小周期总时长(h)
  67. t_small_cycle_h = (L_s + t_bw_s) / 3600.0
  68. # 计算超级周期内CEB次数
  69. k_bw_per_ceb = int(np.floor(p.T_ceb_interval_h / t_small_cycle_h))
  70. if k_bw_per_ceb < 1:
  71. k_bw_per_ceb = 1 # 至少一个小周期
  72. # ton水电耗查表
  73. energy_lookup = {
  74. 3600: 0.1034, 3660: 0.1031, 3720: 0.1029, 3780: 0.1026,
  75. 3840: 0.1023, 3900: 0.1021, 3960: 0.1019, 4020: 0.1017,
  76. 4080: 0.1015, 4140: 0.1012, 4200: 0.1011
  77. }
  78. for _ in range(k_bw_per_ceb):
  79. tmp_run_start = tmp
  80. # 过滤阶段TMP增长
  81. dtmp = _delta_tmp(p, L_h)
  82. tmp_peak = tmp_run_start + dtmp
  83. # 约束1:峰值不得超过硬上限
  84. if tmp_peak > p.TMP_max + 1e-12:
  85. return False, {"reason": "TMP_max violated during filtration", "TMP_peak": tmp_peak}
  86. if tmp_peak > max_tmp_during_filtration:
  87. max_tmp_during_filtration = tmp_peak
  88. # 物理反洗
  89. phi = phi_bw_of(p, L_s, t_bw_s)
  90. tmp_after_bw = tmp_peak - phi * (tmp_peak - tmp_run_start)
  91. # 约束2:单次残余增量控制
  92. residual_inc = tmp_after_bw - tmp_run_start
  93. if residual_inc > p.dTMP + 1e-12:
  94. return False, {
  95. "reason": "residual TMP increase after BW exceeded dTMP",
  96. "residual_increase": residual_inc,
  97. "limit_dTMP": p.dTMP
  98. }
  99. if residual_inc > max_residual_increase:
  100. max_residual_increase = residual_inc
  101. tmp = tmp_after_bw
  102. # CEB
  103. tmp_after_ceb = p.TMP0
  104. # 体积与回收率
  105. V_feed_super = k_bw_per_ceb * p.q_UF * L_h
  106. V_loss_super = k_bw_per_ceb * _v_bw_m3(p, t_bw_s) + p.v_ceb_m3
  107. V_net = max(0.0, V_feed_super - V_loss_super)
  108. recovery = max(0.0, V_net / max(V_feed_super, 1e-12))
  109. # 时间与净供水率
  110. T_super_h = k_bw_per_ceb * (L_s + t_bw_s) / 3600.0 + p.t_ceb_s / 3600.0
  111. net_delivery_rate_m3ph = V_net / max(T_super_h, 1e-12)
  112. # 贴边比例与硬限
  113. headroom_ratio = max_tmp_during_filtration / max(p.TMP_max, 1e-12)
  114. if headroom_ratio > p.headroom_hardcap + 1e-12:
  115. return False, {"reason": "headroom hardcap exceeded", "headroom_ratio": headroom_ratio}
  116. # —— 新增指标 1:日均产水时间(h/d) ——
  117. daily_prod_time_h = k_bw_per_ceb * L_h / T_super_h * 24.0
  118. # —— 新增指标 2:吨水电耗(kWh/m³) ——
  119. closest_L = min(energy_lookup.keys(), key=lambda x: abs(x - L_s))
  120. ton_water_energy = energy_lookup[closest_L]
  121. info = {
  122. "recovery": recovery,
  123. "V_feed_super_m3": V_feed_super,
  124. "V_loss_super_m3": V_loss_super,
  125. "V_net_super_m3": V_net,
  126. "supercycle_time_h": T_super_h,
  127. "net_delivery_rate_m3ph": net_delivery_rate_m3ph,
  128. "max_TMP_during_filtration": max_tmp_during_filtration,
  129. "max_residual_increase_per_run": max_residual_increase,
  130. "phi_bw_effective": phi,
  131. "TMP_after_ceb": tmp_after_ceb,
  132. "headroom_ratio": headroom_ratio,
  133. "daily_prod_time_h": daily_prod_time_h,
  134. "ton_water_energy_kWh_per_m3": ton_water_energy,
  135. "k_bw_per_ceb": k_bw_per_ceb
  136. }
  137. return True, info
  138. def _score(p: UFParams, rec: dict) -> float:
  139. """综合评分:越大越好。不同TMP0会改变max_TMP→改变惩罚→得到不同解。"""
  140. # 无量纲化净供水率
  141. rate_norm = rec["net_delivery_rate_m3ph"] / max(p.q_UF, 1e-12)
  142. headroom_penalty = (rec["max_TMP_during_filtration"] / max(p.TMP_max, 1e-12)) ** p.r_headroom
  143. return (p.w_rec * rec["recovery"]
  144. + p.w_rate * rate_norm
  145. - p.w_headroom * headroom_penalty)
  146. def optimize_2d(p: UFParams,
  147. L_min_s=None, L_max_s=None, L_step_s=None,
  148. t_bw_min_s=None, t_bw_max_s=None, t_bw_step_s=None):
  149. # 网格生成
  150. L_lo = p.L_min_s if L_min_s is None else float(L_min_s)
  151. L_hi = p.L_max_s if L_max_s is None else float(L_max_s)
  152. L_st = p.L_step_s if L_step_s is None else float(L_step_s)
  153. t_lo = p.t_bw_min_s if t_bw_min_s is None else float(t_bw_min_s)
  154. t_hi = p.t_bw_max_s if t_bw_max_s is None else float(t_bw_max_s)
  155. t_st = p.t_bw_step_s if t_bw_step_s is None else float(t_bw_step_s)
  156. L_vals = np.arange(L_lo, L_hi + 1e-9, L_st)
  157. t_vals = np.arange(t_lo, t_hi + 1e-9, t_st)
  158. best = None
  159. best_score = -np.inf
  160. for L_s in L_vals:
  161. for t_bw_s in t_vals:
  162. feasible, info = simulate_one_supercycle(p, L_s, t_bw_s)
  163. if not feasible:
  164. continue
  165. rec = {"L_s": float(L_s), "t_bw_s": float(t_bw_s)}
  166. rec.update(info)
  167. score = _score(p, rec)
  168. if score > best_score + 1e-14:
  169. best_score = score
  170. best = rec.copy()
  171. best["score"] = float(score)
  172. # 若分数相同,偏好回收率更高,再偏好净供水率更高
  173. elif abs(score - best_score) <= 1e-14:
  174. if (rec["recovery"] > best["recovery"] + 1e-12) or (
  175. abs(rec["recovery"] - best["recovery"]) <= 1e-12 and
  176. rec["net_delivery_rate_m3ph"] > best["net_delivery_rate_m3ph"] + 1e-12
  177. ):
  178. best = rec.copy()
  179. best["score"] = float(score)
  180. if best is None:
  181. return {"status": "no-feasible-solution"}
  182. best["status"] = "feasible"
  183. return best
  184. def run_uf_decision(TMP0: float = None) -> dict:
  185. if TMP0 is None:
  186. rng = np.random.default_rng()
  187. TMP0 = rng.uniform(0.03, 0.04) # 初始TMP随机
  188. params = UFParams(
  189. q_UF=360.0,
  190. TMP_max=0.05,
  191. alpha=1.2e-6,
  192. belta=1.0,
  193. q_bw_m3ph=1000.0,
  194. T_ceb_interval_h=48,
  195. v_ceb_m3=30.0,
  196. t_ceb_s=40*60.0,
  197. phi_ceb=1.0,
  198. dTMP=0.001,
  199. L_min_s=3600.0, L_max_s=4200.0, L_step_s=30.0,
  200. t_bw_min_s=90.0, t_bw_max_s=100.0, t_bw_step_s=2.0,
  201. phi_bw_min=0.70, phi_bw_max=1.00,
  202. L_ref_s=500.0, tau_bw_s=40.0, gamma_t=1.0,
  203. TMP0=TMP0,
  204. w_rec=0.7, w_rate=0.3, w_headroom=0.3, r_headroom=2.0, headroom_hardcap=0.9
  205. )
  206. result = optimize_2d(params)
  207. if result.get("status") == "feasible":
  208. return {
  209. "L_s": result["L_s"],
  210. "t_bw_s": result["t_bw_s"],
  211. "recovery": result["recovery"],
  212. "k_bw_per_ceb": result["k_bw_per_ceb"],
  213. "daily_prod_time_h": result["daily_prod_time_h"],
  214. "ton_water_energy_kWh_per_m3": result["ton_water_energy_kWh_per_m3"]
  215. }
  216. # 若没有可行解,返回最小过滤时间和默认值
  217. return {
  218. "L_s": params.L_min_s,
  219. "t_bw_s": params.t_bw_min_s,
  220. "recovery": 0.0,
  221. "k_bw_per_ceb": 1,
  222. "daily_prod_time_h": 0.0,
  223. "ton_water_energy_kWh_per_m3": 0.0
  224. }
  225. def generate_plc_instructions(current_L_s, current_t_bw_s, model_prev_L_s, model_prev_t_bw_s, model_L_s, model_t_bw_s):
  226. """
  227. 根据工厂当前值、模型上一轮决策值和模型当前轮决策值,生成PLC指令。
  228. 新增功能:
  229. 1. 处理None值情况:如果模型上一轮值为None,则使用工厂当前值;
  230. 如果工厂当前值也为None,则返回None并提示错误。
  231. """
  232. # 参数配置保持不变
  233. params = UFParams(
  234. L_min_s=3600.0, L_max_s=6000.0, L_step_s=60.0,
  235. t_bw_min_s=40.0, t_bw_max_s=60.0, t_bw_step_s=5.0,
  236. )
  237. # 参数解包
  238. L_step_s = params.L_step_s
  239. t_bw_step_s = params.t_bw_step_s
  240. L_min_s = params.L_min_s
  241. L_max_s = params.L_max_s
  242. t_bw_min_s = params.t_bw_min_s
  243. t_bw_max_s = params.t_bw_max_s
  244. adjustment_threshold = 1.0
  245. # 处理None值情况
  246. if model_prev_L_s is None:
  247. if current_L_s is None:
  248. print("错误: 过滤时长的工厂当前值和模型上一轮值均为None")
  249. return None, None
  250. else:
  251. # 使用工厂当前值作为基准
  252. effective_current_L = current_L_s
  253. source_L = "工厂当前值(模型上一轮值为None)"
  254. else:
  255. # 模型上一轮值不为None,继续检查工厂当前值
  256. if current_L_s is None:
  257. effective_current_L = model_prev_L_s
  258. source_L = "模型上一轮值(工厂当前值为None)"
  259. else:
  260. # 两个值都不为None,比较哪个更接近模型当前建议值
  261. current_to_model_diff = abs(current_L_s - model_L_s)
  262. prev_to_model_diff = abs(model_prev_L_s - model_L_s)
  263. if current_to_model_diff <= prev_to_model_diff:
  264. effective_current_L = current_L_s
  265. source_L = "工厂当前值"
  266. else:
  267. effective_current_L = model_prev_L_s
  268. source_L = "模型上一轮值"
  269. # 对反洗时长进行同样的处理
  270. if model_prev_t_bw_s is None:
  271. if current_t_bw_s is None:
  272. print("错误: 反洗时长的工厂当前值和模型上一轮值均为None")
  273. return None, None
  274. else:
  275. effective_current_t_bw = current_t_bw_s
  276. source_t_bw = "工厂当前值(模型上一轮值为None)"
  277. else:
  278. if current_t_bw_s is None:
  279. effective_current_t_bw = model_prev_t_bw_s
  280. source_t_bw = "模型上一轮值(工厂当前值为None)"
  281. else:
  282. current_to_model_t_bw_diff = abs(current_t_bw_s - model_t_bw_s)
  283. prev_to_model_t_bw_diff = abs(model_prev_t_bw_s - model_t_bw_s)
  284. if current_to_model_t_bw_diff <= prev_to_model_t_bw_diff:
  285. effective_current_t_bw = current_t_bw_s
  286. source_t_bw = "工厂当前值"
  287. else:
  288. effective_current_t_bw = model_prev_t_bw_s
  289. source_t_bw = "模型上一轮值"
  290. # 检测所有输入值是否在规定范围内(只对非None值进行检查)
  291. # 工厂当前值检查(警告)
  292. if current_L_s is not None and not (L_min_s <= current_L_s <= L_max_s):
  293. print(f"警告: 当前过滤时长 {current_L_s} 秒不在允许范围内 [{L_min_s}, {L_max_s}]")
  294. if current_t_bw_s is not None and not (t_bw_min_s <= current_t_bw_s <= t_bw_max_s):
  295. print(f"警告: 当前反洗时长 {current_t_bw_s} 秒不在允许范围内 [{t_bw_min_s}, {t_bw_max_s}]")
  296. # 模型上一轮决策值检查(警告)
  297. if model_prev_L_s is not None and not (L_min_s <= model_prev_L_s <= L_max_s):
  298. print(f"警告: 模型上一轮过滤时长 {model_prev_L_s} 秒不在允许范围内 [{L_min_s}, {L_max_s}]")
  299. if model_prev_t_bw_s is not None and not (t_bw_min_s <= model_prev_t_bw_s <= t_bw_max_s):
  300. print(f"警告: 模型上一轮反洗时长 {model_prev_t_bw_s} 秒不在允许范围内 [{t_bw_min_s}, {t_bw_max_s}]")
  301. # 模型当前轮决策值检查(错误)
  302. if model_L_s is None:
  303. raise ValueError("错误: 决策模型建议的过滤时长不能为None")
  304. elif not (L_min_s <= model_L_s <= L_max_s):
  305. raise ValueError(f"错误: 决策模型建议的过滤时长 {model_L_s} 秒不在允许范围内 [{L_min_s}, {L_max_s}]")
  306. if model_t_bw_s is None:
  307. raise ValueError("错误: 决策模型建议的反洗时长不能为None")
  308. elif not (t_bw_min_s <= model_t_bw_s <= t_bw_max_s):
  309. raise ValueError(f"错误: 决策模型建议的反洗时长 {model_t_bw_s} 秒不在允许范围内 [{t_bw_min_s}, {t_bw_max_s}]")
  310. print(f"过滤时长基准: {source_L}, 值: {effective_current_L}")
  311. print(f"反洗时长基准: {source_t_bw}, 值: {effective_current_t_bw}")
  312. # 使用选定的基准值进行计算调整
  313. L_diff = model_L_s - effective_current_L
  314. L_adjustment = 0
  315. if abs(L_diff) > adjustment_threshold * L_step_s:
  316. if L_diff > 0:
  317. L_adjustment = L_step_s
  318. else:
  319. L_adjustment = -L_step_s
  320. next_L_s = effective_current_L + L_adjustment
  321. t_bw_diff = model_t_bw_s - effective_current_t_bw
  322. t_bw_adjustment = 0
  323. if abs(t_bw_diff) > adjustment_threshold * t_bw_step_s:
  324. if t_bw_diff > 0:
  325. t_bw_adjustment = t_bw_step_s
  326. else:
  327. t_bw_adjustment = -t_bw_step_s
  328. next_t_bw_s = effective_current_t_bw + t_bw_adjustment
  329. return next_L_s, next_t_bw_s
  330. current_L_s = 3920
  331. current_t_bw_s = 98
  332. model_prev_L_s = None
  333. model_prev_t_bw_s = None
  334. model_L_s = 4160
  335. model_t_bw_s = 96
  336. next_L_s, next_t_bw_s = generate_plc_instructions(current_L_s, current_t_bw_s, model_prev_L_s, model_prev_t_bw_s, model_L_s, model_t_bw_s)
  337. print(f"next_L_s={next_L_s}, next_t_bw_s={next_t_bw_s}")