MATLAB 工程实验:不等间隔流量积分,先把缺口与覆盖率说清楚

前文离线尖峰标记:局部中位数与 MAD,为什么必须保留替换掩码强调不要丢掉数据处理痕迹;积分还需要另一张账:每个相邻时间区间是否有资格贡献面积。本篇保留原时间线和拒绝区间,不把删点或补点后的曲线直接当成完整流量证据。

流量仪每条记录都有时间戳,但记录间隔不一定相同。把整列流量直接相加再乘一个"平均周期",很容易得到一个精确到很多位、含义却不清楚的累计量。更隐蔽的问题是:即使正确使用时间坐标,软件仍可能把数秒通信缺口两端连成一个梯形,随后把这块面积一起显示为累计量。

本实验只解决一个工程问题:在原始不等间隔时间线上,逐区间判断是否允许使用梯形模型,同时输出分段面积、已接纳面积账本和时间覆盖率。这里不做重采样,也不通过插值制造缺口内的新证据。数据由代码直接生成,不需要设备、CSV 输入文件或额外工具箱。

1. 输入契约与量纲

t 是严格递增的相对时间,单位 s;q 是同长度的流量序列,单位 L/s。因此每段面积的单位是 (L/s) × s = L。如设备上报 L/min,必须先除以 60;毫秒时间戳须先转秒,不能只修改图轴文字。

代码接受实数 double 行向量或列向量,内部统一成列向量;长度为 2~100000。时间必须有限且 abs(t)<=1e9,不能重复或倒序。有限流量幅值不超过 1e6;NaN 和 Inf 表示无效观测。maxGap 必须是 double 标量,为 (0,1e6] 秒,区间长度等于阈值时接纳,大于阈值时拒绝。这些数值界限是本示例的输入范围,并非仪器规格。

时间差必须逐项检查。程序不自动排序、不删除坏点后重新连接,也不把无效值替换成零流量。负流量允许存在,面积是有符号的净累计量。

2. 先判区间,再算面积

令 dt_i=t_(i+1)-t_i,区间接纳标志为

g i = finite ⁡ ( q i ) ∧ finite ⁡ ( q i + 1 ) ∧ ( 0 < d t i ≤ G ) . g_i=\operatorname{finite}(q_i)\land\operatorname{finite}(q_{i+1})\land(0<dt_i\leq G). gi=finite(qi)∧finite(qi+1)∧(0<dti≤G).

仅对 g_i=true 的区间计算

A i = d t i ( q i 2 + q i + 1 2 ) . A_i=dt_i\left(\frac{q_i}{2}+\frac{q_{i+1}}{2}\right). Ai=dti(2qi+2qi+1).

连续的接纳区间组成一段。孤立有效点没有持续时间,不能单独贡献面积。全部接纳面积及时间覆盖率分别为

A a c c e p t e d = ∑ g i = 1 A i , C = ∑ g i = 1 d t i t N − t 1 . A_{\rm accepted}=\sum_{g_i=1}A_i,\qquad C=\frac{\sum_{g_i=1}dt_i}{t_N-t_1}. Aaccepted=gi=1∑Ai,C=tN−t1∑gi=1dti.

coverage 是时间占比,不是有效样本数占比。AcceptedLedger_L 累加已接纳面积,在拒绝区间不新增已知面积;它的平坦部分不表示真实流量为零 。CompletePrefix_L 在第一个拒绝区间之后一直为 NaN,表示从记录起点到当前时刻的完整累计量已无法仅凭这些数据给出。起点的累计零只是积分基准,即使起点观测无效,也不表示该观测有效。

MATLAB 的 trapz(t,q) 使用给定坐标,trapz(q) 默认单位间隔;cumtrapz(t,q) 与 cumtrapz(q) 也有对应区别。这是 API 的坐标语义,缺口政策需要应用自己定义。trapz 官方文档、cumtrapz 官方文档

3. 可手算例子与两个反例

取 t=[0,0.5,1.25,2,5,5.5,6.25,7],观测值为 q=2*t+1,缺口阈值为 1 s。2~5 s 的区间被拒绝,其余区间接纳。线性函数在接纳段的梯形积分恰好等于解析积分:

接纳段 手算 面积
0~2 s [t^2+t]_0^2 6 L
5~7 s [t^2+t]_5^7 26 L
合计 6+26 32 L

覆盖时间是 2+2=4 s,总跨度 7 s,coverage=4/7≈57.142857%。

第一个故意错误做法是 trapz(q):结果 55,使用的是样本索引的单位间距,不能解释为本时间轴上的 55 L。第二个错误在解释层面:trapz(t,q) 得到 56 L,却把它称为"有数据支持的完整累计量"。这个数并非一定算错;若缺口内确实继续线性变化,真实总量就是 56 L。但在同样的两端观测之间,完全可以存在底宽 3 s、高 4 L/s 的三角脉冲,多出的面积为 6 L,真实总量变为 62 L。两条轨迹具有相同采样值,因此完整总量不能从这组样本唯一确定。

再将 5.5 s 的观测置为 NaN,它左右两个区间都要拒绝,不能删除这一行再连到 6.25 s。结果变为 16.6875 L、覆盖时间 2.75 s、coverage=39.285714%。

4. 完整可运行源码

将下面全部内容保存为 gap_trapezoid_demo.m,在该目录执行 gap_trapezoid_demo。输出默认进入同目录的 results;也可传入一个独立输出目录。函数末尾包含本地算法函数,无须另外下载辅助文件。

matlab 复制代码
function r=gap_trapezoid_demo(outDir)
% Rate samples in L/s, timestamps in s. Each file includes its local helper.
if nargin<1,outDir=fullfile(fileparts(mfilename('fullpath')),'results');end
if ~exist(outDir,'dir'),mkdir(outDir);end
t=[0;0.5;1.25;2;5;5.5;6.25;7];q=2*t+1;maxGap=1;
a=qualified_trapezoid(t,q,maxGap);
plain=trapz(t,q);wrongUnit=trapz(q);
% Same observed endpoints permit a hidden triangular pulse in the gap.
hiddenPulseArea=0.5*3*4;
assert(abs(a.acceptedArea-32)<1e-12 && abs(a.coverage-4/7)<1e-12,'INT:Hand','Hand result differs.');
assert(isequal(a.segments.Area,[6;26]),'INT:Segments','Segment areas differ.');
assert(plain==56 && wrongUnit==55,'INT:Controls','Counterexample changed.');
bad=q;bad(6)=NaN;b=qualified_trapezoid(t,bad,maxGap);
assert(abs(b.acceptedArea-16.6875)<1e-12 && b.coveredTime==2.75,'INT:Missing','Missing point was bridged.');
samples=table(t,q,[false;a.acceptedEdge],a.acceptedLedger,a.completePrefix, ...
    'VariableNames',{'Time_s','Rate_L_per_s','IncomingEdgeAccepted','AcceptedLedger_L','CompletePrefix_L'});
intervals=table(t(1:end-1),t(2:end),diff(t),a.acceptedEdge,a.edgeArea, ...
    'VariableNames',{'Start_s','End_s','Duration_s','Accepted','Area_L'});
writetable(samples,fullfile(outDir,'gap_trapezoid_samples.csv'));
writetable(intervals,fullfile(outDir,'gap_trapezoid_intervals.csv'));
writetable(a.segments,fullfile(outDir,'gap_trapezoid_segments.csv'));
f=figure('Visible','off','Color','w','Position',[80 80 1100 800]);
subplot(2,2,1);h1=plot(t,q,'ko','MarkerFaceColor','k');hold on;
h2=plot([2 5],[5 11],'k--');tt=linspace(2,5,101)';
h3=plot(tt,2*tt+1+4*(1-abs(tt-3.5)/1.5),'r-','LineWidth',1.3);
for j=1:height(a.segments)
    ix=a.segments.StartIndex(j):a.segments.EndIndex(j);plot(t(ix),q(ix),'b-','HandleVisibility','off');
end
grid on;xlabel('Time (s)');ylabel('Flow (L/s)');
legend([h1 h2 h3],{'Observed','Unsupported bridge','Possible hidden pulse'},'Location','northwest');
title('Same samples, different unseen gap area');
subplot(2,2,2);plot(t,a.acceptedLedger,'bo-',t,a.completePrefix,'g-','LineWidth',1.3);grid on;
xlabel('Time (s)');ylabel('Area (L)');legend('Accepted-area ledger','Complete prefix only','Location','northwest');
title('Flat ledger across gap does not mean zero flow');
subplot(2,2,3);bar([a.coveredTime,a.totalSpan-a.coveredTime]);set(gca,'XTickLabel',{'Covered','Uncovered'});
ylabel('Duration (s)');grid on;title(sprintf('Time coverage = %.2f%%',100*a.coverage));
subplot(2,2,4);bar(a.segments.Area);set(gca,'XTickLabel',{'[0,2] s','[5,7] s'});
ylabel('Accepted area (L)');grid on;ylim([0 30]);title('Two separate supported segments');
print(f,fullfile(outDir,'gap_trapezoid.png'),'-dpng','-r150');close(f);
r=struct('acceptedArea_L',a.acceptedArea,'coveredTime_s',a.coveredTime,'coverage',a.coverage, ...
    'unsupportedBridge_L',plain,'wrongUnitSpacing',wrongUnit, ...
    'possibleTrueTotalA_L',plain,'possibleTrueTotalB_L',plain+hiddenPulseArea, ...
    'missingPointArea_L',b.acceptedArea,'missingPointCoverage',b.coverage);
save(fullfile(outDir,'gap_trapezoid.mat'),'r','t','q','maxGap','a','b','samples','intervals');
fprintf('INTEGRAL_PASS accepted=%.12g L coverage=%.12g bridge=%.12g possible_hidden=%.12g\n', ...
    a.acceptedArea,a.coverage,plain,plain+hiddenPulseArea);
end

function a=qualified_trapezoid(t,x,maxGap)
% Only adjacent valid endpoints and dt<=maxGap contribute. No gap interpolation.
validateattributes(t,{'double'},{'real','finite','vector','nonempty'});
validateattributes(x,{'double'},{'real','vector','nonempty'});
validateattributes(maxGap,{'double'},{'real','finite','scalar','positive','<=',1e6});
t=t(:);x=x(:);N=numel(t);
assert(N==numel(x)&&N>=2&&N<=1e5,'INT:Length','Equal lengths, 2..1e5 samples required.');
assert(all(abs(t)<=1e9),'INT:Range','Use relative seconds with magnitude <=1e9.');
dt=diff(t);assert(all(isfinite(dt)&dt>0),'INT:Time','Timestamps must strictly increase.');
finite=isfinite(x);
assert(all(abs(x(finite))<=1e6),'INT:Range','Finite rate magnitudes must be <=1e6.');
good=finite(1:end-1)&finite(2:end)&dt<=maxGap;
edge=nan(N-1,1);
edge(good)=dt(good).*(x(find(good))/2+x(find(good)+1)/2);
assert(all(isfinite(edge(good))),'INT:Numeric','Accepted interval area must be finite.');
ledger=[0;cumsum(replace_missing(edge))];
prefix=ledger;prefix([false;cumsum(~good)>0])=NaN;
starts=find(good&[true;~good(1:end-1)]);
ends=find(good&[~good(2:end);true])+1;
area=zeros(size(starts));duration=area;
for j=1:numel(starts)
    area(j)=sum(edge(starts(j):ends(j)-1));duration(j)=t(ends(j))-t(starts(j));
end
segments=table(starts,ends,t(starts),t(ends),duration,area, ...
    'VariableNames',{'StartIndex','EndIndex','Start_s','End_s','Duration_s','Area'});
span=t(end)-t(1);covered=sum(dt(good));
a=struct('acceptedEdge',good,'edgeArea',edge,'acceptedLedger',ledger, ...
    'completePrefix',prefix,'segments',segments,'acceptedArea',sum(edge(good)), ...
    'coveredTime',covered,'totalSpan',span,'coverage',covered/span);
end

function x=replace_missing(x)
% Bookkeeping only: skipped intervals add no known area, not assumed zero truth.
x(~isfinite(x))=0;
end

5. 本次 MATLAB 实测

2026-10-21 在 Windows 的 MATLAB 25.2.0.2998904 (R2025b),PCWIN64 上真实执行。总入口为 run_oct21.m,正式 v2 批处理退出码为 0。依赖分析只列出 MATLAB。不是 Octave 执行,也没有用 Python 代算结果。

项目 实测
两个接纳段面积 6、26 L
接纳面积与覆盖时间 32 L、4 s
coverage 0.5714285714285714
直接跨缺口的 trapz(t,q) 56 L
相同端点可容许的另一总量 62 L
加入一个无效点后 16.6875 L、coverage=0.39285714285714285

图左上红线是用于反证唯一性的可能轨迹,不是重建结果。右上蓝线是接纳面积账本,绿线只保留完整前缀。下方分别显示时间覆盖与两段面积。图片由本代码生成,已打开检查。

CSV 包括 gap_trapezoid_samples.csv、gap_trapezoid_intervals.csv、gap_trapezoid_segments.csv;MAT 保存原始数据、区间标志、两种累计序列与结果结构。被拒绝区间的 Area_L 为 NaN,不是 0。

独立测试用每个接纳段的 trapz 与解析原函数作参考,覆盖阈值等号、刚超过阈值、缺点两侧失效、全部无效、负面积、行列形状、重复/倒序时间及超范围输入。三篇合计 47 个命名检查全部通过 ,其中报警另枚举 1024 组序列。测试完整源码保存在作者归档实验包的 matlab/tests-v2/run_independent_checks.m,真实命令、退出码和输出在 execution 目录;不能把此测试数量解读为任意输入的正确性证明。

6. 工程接入与限制

接线可以按"流量传感器或 PLC → 采集卡/串口解析 → 带时间戳和质量位的记录 → 单位换算 → 本算法 → 分段累计量与覆盖率看板"组织。时间最好使用同一单调时钟相对起点的秒数,校时跳变应断开一批记录;输入质量位无效时保留时间行并将流量置为 NaN。阈值 G 应由采集周期、允许抖动和业务缺口政策共同确定。

需要复用算法时,可把文件末尾 qualified_trapezoid 与 replace_missing 一起提取到独立 qualified_trapezoid.m,在完成单位与质量处理后调用 a=qualified_trapezoid(t,q,G)。展示时将 a.acceptedArea、a.coverage、a.segments 同时向下游传递,不能只传一个面积数值。在线分块累计还需要保留上一块末样本,并对跨块区间执行同样检查;本示例不实现流式状态持久化。

通过数据质量条件仅表示允许采用该区间的梯形近似,不证明两个采样点之间真的线性,也不排除采样间的短脉冲。传感器偏置、量程饱和、时间戳误差、曲率造成的离散误差都需另行评估。代码的数值范围避免常见溢出,却不能消除双精度舍入或大正负面积相消;把数值多保留几位也不会增加物理可信度。

本文来源于新的 2026-10-21 代码与执行记录。官方页面于当日核对,当前网页标注 R2026b;实际测试版本始终是 R2025b。输入检查接口参考 validateattributes 官方文档。

本篇代码的执行凭据

以下是便于复用的命令模板,不是原测试机绝对路径。只复现本篇时,将上文完整源码保存到新的工作目录,将 MATLAB 可执行文件加入 PATH;把下例 你的新目录 换成实际路径。命令的标准输出与退出码应保存,以便复核。

powershell 复制代码
matlab -wait -sd "你的新目录" -batch "gap_trapezoid_demo"

本次实际命令、源快照与时间/退出码原文均保留在作者归档实验包的 matlab/execution,最终数值来自其中 20261021-082339-demos-v2 运行,并非模板命令的假定结果。

主运行退出码 0,独立检查退出码 0。作者归档中保留 matlab/execution/20261021-082339-demos-v2 与 matlab/execution/20261021-082407-tests-v2 下的 stdout.txt、run.json。这些是归档路径,不是本网页的下载链接;本篇不依赖该归档即可运行上文完整示例。

本篇完整 MATLAB 源码 SHA256:b5a0514114497b0c98fb9a30029892a0125aa433a0ca32e144f777a062b52d97。文内代码、final 源码与真实执行快照已逐字节核验一致。独立复核由不同于主作者的 AI 子代理完成,身份、发现、修复和范围记录保存在作者实验归档的 reviews;不是人工认证。

留一个工程问题

同一组样本允许缺口内额外出现 6 L 的三角脉冲。如果业务只允许发布一个"累计量"字段,你会怎样修改接口,让 32 L 接纳面积、4/7 覆盖率和未知缺口不被误读为完整总量?

相关推荐
Ai-_Man1 小时前
Meta Muse还是不可以导出左侧所有对话,只能点进去单条内容导出,方法不对吗?
开发语言·人工智能·ai·小程序·c#·1024程序员节
梓豪csd1 小时前
消息订阅中心:谁该收到哪条通知
java·开发语言·人工智能·低代码·自动化·1024程序员节
学逆向的2 小时前
C 枚举变量
c语言·开发语言·网络安全
Sakura_j_h_2 小时前
Python 在定时任务里输出 emoji,UnicodeEncodeError 从哪来
开发语言·字符编码·windows·python·定时任务
小成202303202657 小时前
C++~07补充
开发语言·c++
2401_8858850410 小时前
国际语音C#接口怎么写?.NET环境下实现国际语音验证码批量下发
开发语言·c#·.net
程序猿乐锅11 小时前
【从0-1学习Python | 第七篇】Python 面向对象基础
开发语言·笔记·后端·python·学习·1024程序员节
Wang's Blog11 小时前
Java 项目实战: 交友项目 - 查询圈子个人动态:查询我的动态需求分析
java·开发语言·交友