From c9984b11128480f61e203adc8b9170f2109aa488 Mon Sep 17 00:00:00 2001 From: louissun Date: Thu, 20 Aug 2026 01:36:35 +0800 Subject: [PATCH] =?UTF-8?q?open=5Floop.py=E6=96=87=E4=BB=B6=E7=9A=84?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E6=95=B0=E6=8D=AE=E9=83=A8=E5=88=86=E5=86=8D?= =?UTF-8?q?=E6=96=B0=E5=8A=A0=E4=B8=A4=E5=88=97=EF=BC=8C=E4=B8=80=E5=88=97?= =?UTF-8?q?=E6=98=AF"P=5Fabs=5Fratio"=EF=BC=8C=E5=AE=9A=E4=B9=89=E4=B8=BA?= =?UTF-8?q?=E9=98=80=E5=90=8E=E7=BB=9D=E5=AF=B9=E5=8E=8B=E5=8A=9B=E6=AF=94?= =?UTF-8?q?=E9=98=80=E5=89=8D=E7=BB=9D=E5=AF=B9=E5=8E=8B=E5=8A=9B=E7=9A=84?= =?UTF-8?q?=EF=BC=8C=E7=BB=9D=E5=AF=B9=E5=8E=8B=E5=8A=9B=E4=B8=BA=E4=BC=A0?= =?UTF-8?q?=E6=84=9F=E5=99=A8=E8=AF=BB=E6=95=B0=E5=8A=A0=E5=A4=A7=E6=B0=94?= =?UTF-8?q?=E5=8E=8B=E5=80=BC=E3=80=82=E5=8F=A6=E4=B8=80=E5=88=97=E6=98=AF?= =?UTF-8?q?"is=5Fratio=5Fsmaller=5Fthan=5F0.528"=EF=BC=8C=E5=88=A4?= =?UTF-8?q?=E6=96=AD=E8=BF=99=E4=B8=AA=E6=AF=94=E5=80=BC=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E5=B0=8F=E4=BA=8E0.528=EF=BC=88=E4=B8=B4=E7=95=8C=E5=8E=8B?= =?UTF-8?q?=E5=8A=9B=E6=AF=94=EF=BC=89=EF=BC=8C=E6=98=AF=E7=9A=84=E8=AF=9D?= =?UTF-8?q?=E5=B0=B1=E5=A1=ABY=EF=BC=8C=E4=B8=8D=E6=98=AF=E5=B0=B1?= =?UTF-8?q?=E5=A1=ABN.=20=E7=84=B6=E5=90=8E=E5=86=8D=E6=96=B0=E7=94=BB?= =?UTF-8?q?=E4=B8=80=E4=B8=AA=E5=9B=BE=EF=BC=8C=E5=B0=B1=E6=98=AF"P=5Fabs?= =?UTF-8?q?=5Fratio"=E9=9A=8F=E6=97=B6=E9=97=B4=E5=8F=98=E5=8C=96=E7=9A=84?= =?UTF-8?q?=E5=9B=BE=EF=BC=8C=E5=B9=B6=E4=B8=94=E6=A0=87=E5=87=BA0.528?= =?UTF-8?q?=E4=BB=A3=E8=A1=A8=E7=9A=84=E9=82=A3=E6=9D=A1=E7=9B=B4=E7=BA=BF?= =?UTF-8?q?=E3=80=82=E8=BF=99=E4=B8=AA=E5=9B=BE=E4=B8=8E=E5=B7=B2=E6=9C=89?= =?UTF-8?q?=E7=9A=84=E4=B8=A4=E5=B9=85=E5=9B=BE=E6=94=BE=E5=9C=A8=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E7=AA=97=E5=8F=A3=E4=B8=AD=E7=BB=98=E5=88=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- open_loop.py | 65 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/open_loop.py b/open_loop.py index d624983..ae3138d 100644 --- a/open_loop.py +++ b/open_loop.py @@ -6,8 +6,8 @@ 2. 逐个开度:写入电机行程 → 以固定周期采集控流阀前后 4 个传感器 (前压力、前流量、后压力、后流量)→ 等待“控流阀后流量”进入稳态后 切换到下一个开度; -3. 全程逐周期写 CSV,结束后在单个窗口内输出两张子图(前后压力、前后流量), - 保存到工程目录下的 open_loop_data/。 +3. 全程逐周期写 CSV,结束后在单个窗口内输出三张子图(前后压力、前后流量、 + 绝对压力比),保存到工程目录下的 open_loop_data/。 示例: python open_loop.py @@ -71,6 +71,11 @@ PLOT_DPI = config.PLOT_DPI ANNOTATE_STEPS = True # 曲线上标注开度步 +# 绝对压力 = 表压读数 + 大气压;临界压力比用于判断是否出现壅塞(choked)流动。 +ATMOSPHERIC_PRESSURE_KPA = 101.325 # 标准大气压(kPa) +CRITICAL_PRESSURE_RATIO = 0.528 # 临界压力比(阀后绝对压力 / 阀前绝对压力) + + CSV_FIELDS = ( "time_s", "step_index", @@ -80,6 +85,8 @@ CSV_FIELDS = ( "flow_after_slm", "pressure_before_kpa", "pressure_after_kpa", + "P_abs_ratio", + "is_ratio_smaller_than_0.528", ) @@ -140,7 +147,7 @@ class SteadyStateDetector: # --------------------------------------------------------------------------- class OpenLoopRecorder: - """逐周期写 CSV,结束时输出两张子图(前后压力、前后流量)。""" + """逐周期写 CSV,结束时输出三张子图(前后压力、前后流量、绝对压力比)。""" def __init__(self, output_directory, *, plot_dpi=160): output_dir = Path(output_directory) @@ -176,6 +183,18 @@ class OpenLoopRecorder: if self._closed: raise RuntimeError("记录器已关闭") + p_abs_ratio = self._absolute_pressure_ratio( + pressure_before_kpa, pressure_after_kpa + ) + if p_abs_ratio is None: + p_abs_ratio_text = "" + is_smaller_text = "" + else: + p_abs_ratio_text = f"{p_abs_ratio:.6f}" + is_smaller_text = ( + "Y" if p_abs_ratio < CRITICAL_PRESSURE_RATIO else "N" + ) + self._writer.writerow( { "time_s": self._fmt(time_s), @@ -186,6 +205,8 @@ class OpenLoopRecorder: "flow_after_slm": self._fmt(flow_after_slm), "pressure_before_kpa": self._fmt(pressure_before_kpa), "pressure_after_kpa": self._fmt(pressure_after_kpa), + "P_abs_ratio": p_abs_ratio_text, + "is_ratio_smaller_than_0.528": is_smaller_text, } ) self._file.flush() @@ -195,6 +216,21 @@ class OpenLoopRecorder: self.steps.append((float(time_s), float(opening_pct))) self._last_step_index = step_index + @staticmethod + def _absolute_pressure_ratio(pressure_before_kpa, pressure_after_kpa): + """阀后绝对压力 / 阀前绝对压力;读数缺失或分母非法时返回 None。""" + if pressure_before_kpa is None or pressure_after_kpa is None: + return None + try: + before_abs = float(pressure_before_kpa) + ATMOSPHERIC_PRESSURE_KPA + after_abs = float(pressure_after_kpa) + ATMOSPHERIC_PRESSURE_KPA + except (TypeError, ValueError): + return None + if before_abs <= 0: + return None + ratio = after_abs / before_abs + return ratio if math.isfinite(ratio) else None + def finalize(self): """关闭 CSV 并生成 PNG;无采样点时只保留 CSV。""" self.close() @@ -221,6 +257,7 @@ class OpenLoopRecorder: flow_after = [] pressure_before = [] pressure_after = [] + p_abs_ratio = [] with self.csv_path.open("r", newline="", encoding="utf-8-sig") as file: for row in csv.DictReader(file): @@ -233,11 +270,12 @@ class OpenLoopRecorder: pressure_after.append( self._float_or_nan(row["pressure_after_kpa"]) ) + p_abs_ratio.append(self._float_or_nan(row["P_abs_ratio"])) figure, axes = plt.subplots( - 2, + 3, 1, - figsize=(12, 9), + figsize=(12, 12), sharex=True, constrained_layout=True, ) @@ -264,10 +302,25 @@ class OpenLoopRecorder: "Flow after valve", "#6A1B9A", ) axes[1].set_ylabel("Flow (SLM)") - axes[1].set_xlabel("Time (s)") axes[1].set_title("Flow across control valve") axes[1].legend(loc="best") + self._plot_series( + axes[2], time_s, p_abs_ratio, + "P_abs_ratio (after/before)", "#EF6C00", + ) + axes[2].axhline( + CRITICAL_PRESSURE_RATIO, + color="#B71C1C", + linestyle="--", + linewidth=1.2, + label=f"Critical ratio {CRITICAL_PRESSURE_RATIO:.3f}", + ) + axes[2].set_ylabel("P_abs_ratio") + axes[2].set_xlabel("Time (s)") + axes[2].set_title("Absolute pressure ratio across control valve") + axes[2].legend(loc="best") + if ANNOTATE_STEPS and self.steps: y_top = axes[1].get_ylim()[1] for step_time, opening in self.steps: