Files
ReinLoopTest/ReinLoop/tool/data_analyze
T
2026-07-30 11:12:31 +08:00

285 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import pickle
import glob
def load_and_merge_pickle_chunks(folder_path, file_pattern="*.pkl"):
"""
从指定文件夹中读取所有匹配的分片文件,解包并合并成一个总的数据列表。
Args:
folder_path: 存放 .pkl 分片文件的文件夹路径
file_pattern: 文件匹配模式,默认匹配所有 .pkl 文件
"""
all_episodes = []
# 获取所有匹配的 pkl 文件路径,并按名称排序(确保 part1, part2 顺序或逻辑清晰)
search_path = os.path.join(folder_path, file_pattern)
file_list = sorted(glob.glob(search_path))
if not file_list:
print(f"❌ 未在路径 【{folder_path}】 下找到任何匹配 【{file_pattern}】 的文件!")
return []
print(f"📂 找到 {len(file_list)} 个数据分片文件,开始加载...")
for file_path in file_list:
try:
with open(file_path, 'rb') as f:
# 每个分片解包出来都是一个 list [ep1, ep2, ...]
chunk_data = pickle.load(f)
if isinstance(chunk_data, list):
all_episodes.extend(chunk_data)
print(f" ✅ 成功加载: {os.path.basename(file_path)} (包含 {len(chunk_data)} 个 Episode)")
else:
print(f" ⚠️ 警告: {os.path.basename(file_path)} 解析出的数据格式不是列表,跳过。")
except Exception as e:
print(f" ❌ 读取文件 {os.path.basename(file_path)} 失败: {e}")
print(f"整个序列加载完成,共合并了 {len(all_episodes)} 个 Episode。")
return all_episodes
def analyze_episodes_data(episode_data_raw):
"""
分析 Episode 数据,统计超调情况。
"""
total_episodes = len(episode_data_raw)
if total_episodes == 0:
print("没有数据可供分析。")
return
invalid_count = 0 # 最后一步误差绝对值 > 2 kPa 的无效 episode
invalid_high_flow = 0 # 无效 episode 中流量 > 200
invalid_low_flow = 0 # 无效 episode 中流量 < 100
all_steady_abs_errors = [] # 所有有效 episode 的稳态误差(绝对值)
no_overshoot_count = 0
no_overshoot_abs_errors = [] # 绝对值稳态误差
no_overshoot_raw_errors = [] # 带符号稳态误差(+ = 高于目标, - = 低于目标)
overshoot_lt_1_count = 0
overshoot_1_to_2_count = 0
overshoot_2_to_3_count = 0
overshoot_3_to_4_count = 0
overshoot_4_to_5_count = 0
overshoot_5_to_10_count = 0
overshoot_gt_10_count = 0
overshoots_5_to_10 = []
overshoots_gt_10 = []
for idx, ep in enumerate(episode_data_raw):
pressures = ep.get('pressures', [])
target_p = ep.get('target_pressure', 0.0)
if not pressures:
continue
# 最后一步误差绝对值 > 2 kPa → 无效 episode,跳过
errors = ep.get('errors', [])
if errors and abs(errors[-1]) > 2:
invalid_count += 1
q = ep.get('Q_in', 0)
if q > 200:
invalid_high_flow += 1
elif q < 100:
invalid_low_flow += 1
continue
initial_p = pressures[0]
# 所有有效 episode 的稳态误差(最后 30 步绝对值均值)
if errors:
last_n = errors[-30:] if len(errors) >= 30 else errors
all_steady_abs_errors.append(sum(abs(e) for e in last_n) / len(last_n))
is_step_up = target_p >= initial_p # 升压为 True,降压为 False
overshoot = 0.0
if is_step_up:
# 升压:最大值大于目标压力为超调
max_p = max(pressures)
if max_p > target_p:
overshoot = max_p - target_p
else:
# 降压:最小值小于目标压力为超调
min_p = min(pressures)
if min_p < target_p:
overshoot = target_p - min_p
# 统计区间
if overshoot == 0:
no_overshoot_count += 1
elif overshoot < 1.0:
overshoot_lt_1_count += 1
# 最后 30 步的平均误差作为稳态误差(分别记录绝对值和带符号值)
if len(errors) >= 30:
last_30 = errors[-30:]
elif errors:
last_30 = errors
else:
last_30 = []
if last_30:
no_overshoot_abs_errors.append(sum(abs(e) for e in last_30) / len(last_30))
no_overshoot_raw_errors.append(sum(last_30) / len(last_30))
elif 1.0 <= overshoot < 2.0:
overshoot_1_to_2_count += 1
elif 2.0 <= overshoot < 3.0:
overshoot_2_to_3_count += 1
elif 3.0 <= overshoot < 4.0:
overshoot_3_to_4_count += 1
elif 4.0 <= overshoot <= 5.0:
overshoot_4_to_5_count += 1
else:
item = {
"index": idx,
"direction": "升压" if is_step_up else "降压",
"initial_p": initial_p,
"target_p": target_p,
"overshoot_value": round(overshoot, 3),
"Q_in": ep.get("Q_in", 0),
}
if overshoot <= 10.0:
overshoot_5_to_10_count += 1
overshoots_5_to_10.append(item)
else:
overshoot_gt_10_count += 1
overshoots_gt_10.append(item)
# 打印报告
def _pct(n): return f"{n / total_episodes * 100:.1f}%"
print("\n" + "="*25 + " 离线数据分析 " + "="*25)
valid_episodes = total_episodes - invalid_count
print(f"合并后的总 Episode 数 : {total_episodes}")
print(f" - 无效 Episode(末步误差>2: {invalid_count} ({_pct(invalid_count)})")
if invalid_count > 0:
print(f" ├ 流量 > 200 L/min : {invalid_high_flow}")
print(f" └ 流量 < 100 L/min : {invalid_low_flow}")
print(f" - 有效 Episode 数 : {valid_episodes}")
print(f" - 未超调的 Episode 数 : {no_overshoot_count} ({_pct(no_overshoot_count)})")
print(f" - 超调 < 1 kPa : {overshoot_lt_1_count} ({_pct(overshoot_lt_1_count)})")
print(f" - 超调在 1 ~ 2 kPa 之间 : {overshoot_1_to_2_count} ({_pct(overshoot_1_to_2_count)})")
print(f" - 超调在 2 ~ 3 kPa 之间 : {overshoot_2_to_3_count} ({_pct(overshoot_2_to_3_count)})")
print(f" - 超调在 3 ~ 4 kPa 之间 : {overshoot_3_to_4_count} ({_pct(overshoot_3_to_4_count)})")
print(f" - 超调在 4 ~ 5 kPa 之间 : {overshoot_4_to_5_count} ({_pct(overshoot_4_to_5_count)})")
print(f" - 超调在 5 ~ 10 kPa 之间 : {overshoot_5_to_10_count} ({_pct(overshoot_5_to_10_count)})")
print(f" - 超调 > 10 kPa : {overshoot_gt_10_count} ({_pct(overshoot_gt_10_count)})")
print("=" * 68)
def _print_detail(title, items):
if items:
print(f"\n[⚠️ {title}]:")
for item in items:
print(f" * Episode [{item['index']}] ({item['direction']}): "
f"初始 {item['initial_p']:.2f} -> 目标 {item['target_p']:.2f} | "
f"超调量: {item['overshoot_value']:.2f} kPa | "
f"流量: {item['Q_in']:.1f} L/min")
_print_detail("超调在 5 ~ 10 kPa", overshoots_5_to_10)
_print_detail("超调大于 10 kPa", overshoots_gt_10)
if not overshoots_5_to_10 and not overshoots_gt_10:
print("\n🎉 极好!没有发现超调大于 5 kPa 的数据。")
# ---- 流量分布统计 ----
flow_bins = [
(0, 10), (10, 50), (50, 100), (100, 150),
(150, 200), (200, 250), (250, 300),
]
flow_counts = {f"{lo}~{hi}": 0 for lo, hi in flow_bins}
flow_counts["300+"] = 0
for ep in episode_data_raw:
q = ep.get('Q_in', 0)
placed = False
for lo, hi in flow_bins:
if lo <= q < hi:
flow_counts[f"{lo}~{hi}"] += 1
placed = True
break
if not placed:
flow_counts["300+"] += 1
print(f"\n📊 流量分布统计 (共 {total_episodes} 个 Episode):")
for lo, hi in flow_bins:
label = f"{lo}~{hi}"
print(f" {label:>10} L/min : {flow_counts[label]:>5} ({flow_counts[label]/total_episodes*100:5.1f}%)")
print(f" {'300+':>10} L/min : {flow_counts['300+']:>5} ({flow_counts['300+']/total_episodes*100:5.1f}%)")
if all_steady_abs_errors:
avg_all = sum(all_steady_abs_errors) / len(all_steady_abs_errors)
print(f"\n📊 所有有效 Episode 平均稳态误差(最后 30 步绝对值均值): {avg_all:.3f} kPa"
f" ({len(all_steady_abs_errors)} 个 Episode)")
if no_overshoot_abs_errors:
avg_abs = sum(no_overshoot_abs_errors) / len(no_overshoot_abs_errors)
avg_raw = sum(no_overshoot_raw_errors) / len(no_overshoot_raw_errors)
print(f"\n📊 超调01kpa Episode 平均稳态误差(最后 30 步):")
print(f" 绝对值均值 : {avg_abs:.3f} kPa")
print(f" 带符号均值 : {avg_raw:.3f} kPa ({'偏高于目标' if avg_raw > 0 else '偏低' if avg_raw < 0 else '无偏'})"
f" ({no_overshoot_count} 个 Episode)")
def print_episode_detail(episode_data_raw, index):
"""打印指定 episode 的完整数据"""
if index < 0 or index >= len(episode_data_raw):
print(f"❌ Episode 索引 {index} 超出范围 (0~{len(episode_data_raw)-1})")
return
ep = episode_data_raw[index]
print(f"\n{'='*60}")
print(f" Episode [{index}] 完整数据")
print(f"{'='*60}")
for key in ['Q_in', 'volume', 'target_pressure', 'mode']:
if key in ep:
print(f" {key}: {ep[key]}")
pressures = ep.get('pressures', [])
errors = ep.get('errors', [])
valve_openings = ep.get('valves', [])
print(f"\n 步数: {len(pressures)}")
if pressures:
print(f" 初始压力: {pressures[0]:.2f} kPa")
print(f" 最终压力: {pressures[-1]:.2f} kPa")
print(f" 目标压力: {ep.get('target_pressure', 'N/A')} kPa")
if errors:
print(f" 最终误差: {errors[-1]:.3f} kPa")
print(f"\n {'步':>4s} {'压力(kPa)':>10s} {'误差(kPa)':>10s} {'开度(%)':>8s}")
print(f" {'-'*36}")
n = len(pressures)
for i in range(n):
p = pressures[i]
e = errors[i] if i < len(errors) else float('nan')
vo = valve_openings[i] if i < len(valve_openings) else float('nan')
print(f" {i:4d} {p:10.2f} {e:10.3f} {vo:8.2f}")
print(f"{'='*60}\n")
# --- 执行离线分析 ---
if __name__ == "__main__":
# 💡 数据存放文件夹路径
DATA_FOLDER = "/Users/menglingrui/Documents/DominatedConvergence/cloud_down_file/永久/data_8L"
# 1. 读取并合并分片
merged_data = load_and_merge_pickle_chunks(DATA_FOLDER, file_pattern="*part*.pkl")
# 2. 执行分析
if merged_data:
analyze_episodes_data(merged_data)
# 3. 找出无效 episode(末步误差绝对值 > 2 kPa),打印前 3 个的完整数据
# invalid_indices = []
# for idx, ep in enumerate(merged_data):
# errors = ep.get('errors', [])
# if errors and abs(errors[-1]) > 2:
# invalid_indices.append(idx)
# if len(invalid_indices) >= 3:
# break
# if invalid_indices:
# print(f"\n找到 {len(invalid_indices)} 个无效 Episode,索引: {invalid_indices}")
# for idx in invalid_indices:
# print_episode_detail(merged_data, idx)
# else:
# print("\n未找到无效 Episode")
print_episode_detail(merged_data, 2500)