Files
vd-analysis/tools/plot_sens_alignment.py
wangfq db5e108620 fig(family): 六型号总览图 + PD132T↔PD136HA 档位对齐图;待实测清单 ⑧
- docs/img/family-overview.{svg,png}(tools/plot_family_overview.py)
  面板A 窗长/τ 条形;面板B 检测延迟 vs Δf/f(六型号按各自档0 判据);
  归一化延迟表(@1.1× / @2× / @10× 判据)+ 进入确认与口径脚注
  关键:@10×(强信号) 全部=1 窗 ⇒ 8.2/8.7/10.0/10.9/21.9/27.3ms;@1.1×(近阈值) 7~9 窗 ⇒ M1H 57ms ~ PD136HA 197ms
- docs/img/sens-alignment-pd132t-pd136ha.{svg,png}(tools/plot_sens_alignment.py)
  四档 进入/离开 双段条形 + 同档偏差(−46%~+69%) + 最近档映射 + 换型 4 步 + 两端档无对应警示
- 待实测清单新增 ⑧ 弱信号复测(0.10% 车:PD132T 档1 ≈76ms 应检出 / PD136HA 档1 应检不到)
- 文档接入:cross-model-comparison.md 新增「六型号总览图」节;comparison §5.4 嵌入对齐图;measurement doc §10 加指引
- README tools 段补 3 个绘图脚本;表格校验 0 异常(16 文件)
2026-09-11 11:27:58 +08:00

141 lines
7.1 KiB
Python
Raw Permalink 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.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""PD132T ↔ PD136HA 灵敏度档位对齐图(现场调档照着贴)—— 纯 stdlib 手写 SVG
横轴 = Δf/f(进入/离开阈值的相对频偏,%);每档画 进入(实心)+离开(浅色) 两段
偏差 = 同档位号一对一映射的 (H−T)/T;最佳匹配 = 最近的进入阈值
转 PNGchromium --headless=new --disable-gpu --no-sandbox \
--screenshot=docs/img/sens-alignment-pd132t-pd136ha.png --window-size=1500,880 \
file://$PWD/docs/img/sens-alignment-pd132t-pd136ha.svg
"""
W, H = 1500, 1060
FONT = "'Noto Serif CJK SC','Noto Sans CJK SC',sans-serif"
K = 65536.0
out = []
def esc(s):
return s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
def txt(x, y, s, size=14, fill='#222', anchor='start', weight='normal'):
out.append('<text x="%.1f" y="%.1f" font-size="%s" fill="%s" text-anchor="%s" font-weight="%s" font-family="%s">%s</text>'
% (x, y, size, fill, anchor, weight, FONT, esc(s)))
def line(x1, y1, x2, y2, stroke='#999', w=1.0, dash=None):
d = ' stroke-dasharray="%s"' % dash if dash else ''
out.append('<line x1="%.1f" y1="%.1f" x2="%.1f" y2="%.1f" stroke="%s" stroke-width="%s"%s/>' % (x1, y1, x2, y2, stroke, w, d))
def rect(x, y, w, h, fill='none', stroke='none', rx=2, sw=1.0, op=1.0):
out.append('<rect x="%.1f" y="%.1f" width="%.1f" height="%.1f" rx="%s" fill="%s" fill-opacity="%s" stroke="%s" stroke-width="%s"/>'
% (x, y, w, h, rx, fill, op, stroke, sw))
def note(x, y, w, h, title, lines_, color='#0b6'):
rect(x, y, w, h, '#fbfdff', color, rx=6, sw=1.2, op=0.95)
txt(x + 12, y + 24, title, 14.5, color, weight='bold')
for i, s in enumerate(lines_):
txt(x + 12, y + 48 + i * 21, s, 13, '#333')
T_IN = [300, 60, 32, 21] # PD132T 进入表(档0..3
T_OUT = [60, 32, 21, 14] # PD132T 离开表
H_IN = [162, 81, 54, 28] # PD136HA 进入表
H_OUT = [81, 54, 32, 18] # PD136HA 离开表
ct = lambda v: v / K * 100.0
out.append('<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" viewBox="0 0 %d %d">' % (W, H, W, H))
out.append('<rect width="%d" height="%d" fill="#ffffff"/>' % (W, H))
txt(30, 44, 'PD132T ↔ PD136HA 灵敏度档位对齐图(判据 = 表值/65536 的 Δf/f', 24, '#111', weight='bold')
txt(30, 72, '每档画两段:深色 = 离开阈值(回差下沿),浅色 = 进入阈值(回差上沿);横轴 0~0.5%(线性)', 13, '#555')
line(30, 86, W - 30, 86, '#ddd', 1.2)
# ================== 轴 ==================
AX0, AX1 = 380.0, 1230.0
VMAX = 0.50
ax = lambda v: AX0 + (v / VMAX) * (AX1 - AX0)
GY0, GGH = 118.0, 116.0
for g in [0.0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5]:
x = ax(g)
line(x, 112, x, 112 + 4 * GGH, '#ececec', 1.0)
txt(x, 112 + 4 * GGH + 22, ('%.2f' % g), 12, '#777', anchor='middle')
txt((AX0 + AX1) / 2, 112 + 4 * GGH + 46, 'Δf/f(%)—— 数值越大越钝(越不易触发)', 13, '#555', anchor='middle')
# ================== 四档 ==================
COLT, COLH = '#1565c0', '#e65100'
for i in range(4):
gy = GY0 + i * GGH
viT, voT = ct(T_IN[i]), ct(T_OUT[i])
viH, voH = ct(H_IN[i]), ct(H_OUT[i])
dev = (viH - viT) / viT * 100.0
# 分组背景
if i % 2 == 0:
rect(40, gy - 8, W - 80, GGH - 12, '#fafafa', rx=6, sw=0)
txt(60, gy + 52, '档 %d' % i, 17, '#111', weight='bold')
# --- PD132T 行 ---
yT = gy + 6
rect(AX0, yT, ax(voT) - AX0, 17, COLT, rx=2, op=0.30)
rect(AX0, yT, ax(viT) - AX0, 17, COLT, rx=2, op=0.85)
txt(AX0 - 10, yT + 14, 'PD132T', 13.5, COLT, anchor='end', weight='bold')
txt(ax(viT) + 8, yT + 14, '进入 %.4f%% 离开 %.4f%%' % (viT, voT), 12.5, COLT)
# --- PD136HA 行 ---
yH = gy + 52
rect(AX0, yH, ax(voH) - AX0, 17, COLH, rx=2, op=0.30)
rect(AX0, yH, ax(viH) - AX0, 17, COLH, rx=2, op=0.85)
txt(AX0 - 10, yH + 14, 'PD136HA', 13.5, COLH, anchor='end', weight='bold')
txt(ax(viH) + 8, yH + 14, '进入 %.4f%%' % viH, 12.5, COLH)
# 同档偏差标注 + 连接线
line(ax(viT), yT + 17, ax(viH), yH, '#999', 1.2, '4,3')
badge_col = '#c1121f' if abs(dev) >= 30 else ('#b8860b' if abs(dev) >= 15 else '#0b6')
txt(AX1 + 16, gy + 40, '同档偏差 %+.0f%%' % dev, 14, badge_col, weight='bold')
# 最佳匹配说明
txt(W - 30, GY0 + 4 * GGH + 10, '最佳匹配:档 1 → 档 2(−10.0%', 13, '#0b6', anchor='end')
txt(W - 30, GY0 + 4 * GGH + 32, '两端档 0 / 3 无对应档 ⇒ 必须复测', 13, '#c1121f', anchor='end')
line(AX1 + 4, 112, AX1 + 4, 112 + 4 * GGH, '#ccc', 1.0)
# ================== 对照表 ==================
TY = 660.0
txt(40, TY, '档位对照表(现场照着贴)', 17, '#111', weight='bold')
COLS = [(60, '档位'), (150, 'PD132T 进入'), (290, 'PD132T 离开'), (430, 'PD136HA 进入'), (570, 'PD136HA 离开'),
(710, '同档偏差'), (860, '等效替代(最近进入阈值)')]
for x, s in COLS:
txt(x, TY + 30, s, 13.5, '#555', weight='bold')
line(40, TY + 40, W - 40, TY + 40, '#ccc', 1.0)
for i in range(4):
y = TY + 66 + i * 36
viT, voT, viH, voH = ct(T_IN[i]), ct(T_OUT[i]), ct(H_IN[i]), ct(H_OUT[i])
dev = (viH - viT) / viT * 100.0
j = min(range(4), key=lambda k: abs(H_IN[k] - T_IN[i]))
dev2 = (ct(H_IN[j]) - viT) / viT * 100.0
txt(60, y, '档 %d' % i, 14, '#111', weight='bold')
txt(150, y, '%.4f %%' % viT, 13, '#1565c0')
txt(290, y, '%.4f %%' % voT, 13, '#777')
txt(430, y, '%.4f %%' % viH, 13, '#e65100')
txt(570, y, '%.4f %%' % voH, 13, '#777')
txt(710, y, '%+.1f %%' % dev, 13.5, ('#c1121f' if abs(dev) >= 30 else '#b8860b'), weight='bold')
txt(860, y, 'PD136HA 档 %d%+.1f%%' % (j, dev2), 13, '#0b6')
line(40, TY + 66 + 4 * 36 - 16, W - 40, TY + 66 + 4 * 36 - 16, '#ccc', 1.0)
# ================== 脚注 ==================
note(40, TY + 66 + 4 * 36 + 4, 700, 128, '换型动作(4 步)', [
'① 先算目标:原型号档位 → 百分比 = 表值 / 65536',
'② 在新型号表里找最接近的档(偏差 ≤15% 可接受;>30% 必须复测)',
'③ 用 §5.5 公式预估响应:延迟 = (n+1)×窗长(n 由 信号/判据 比决定)',
'④ 强信号车 + 弱信号车各复测 3 次(记录 判据越线→继电器 实测耗时)',
], '#c1121f')
note(760, TY + 66 + 4 * 36 + 4, 700, 128, '两端档位无对应(必须注意)', [
'档0(最钝):PD132T 0.4578% ↔ PD136HA 0.2472%(钝 46%',
' ⇒ 依赖「最钝档防误触发」的现场,换型后须重新评估误触发率',
'档3(最灵敏):PD132T 0.0320% ↔ PD136HA 0.0427%(钝 33%',
' ⇒ 依赖「最高灵敏」的现场,PD136HA 可能检不到弱信号车(§5.5 ⑧)',
], '#e65100')
OPEN = 'docs/img/sens-alignment-pd132t-pd136ha.svg'
out.append('</svg>')
open(OPEN, 'w', encoding='utf-8').write('\n'.join(out))
print('已写出 %s' % OPEN)
for i in range(4):
viT, viH = ct(T_IN[i]), ct(H_IN[i])
j = min(range(4), key=lambda k: abs(H_IN[k] - T_IN[i]))
print(' 档%d: PD132T %.4f%% ↔ PD136HA %.4f%%(同档 %+.1f%%| 最近档 %d%+.1f%%'
% (i, viT, viH, (viH - viT) / viT * 100, j, (ct(H_IN[j]) - viT) / viT * 100))