MATLAB 因果采样保持:用历史匹配和数据年龄防止未来信息泄漏

把两路日志放到同一张表里,有两种不同任务:一种是在公共时间网格上估计信号,另一种是回答"在这个查询时刻之前,最后一条记录是什么,现在还能不能用"。本文处理第二种,不重做公共网格插值。

已有文章《MATLAB 多传感器时间对齐 用 interp1 建立公共时间网格并限制外推》可以与本文对照阅读:这里把任务收窄为事件时间上的 latest-before/as-of 历史匹配,不要求重建采样点之间的连续信号。旧文用 previous 与 next 找到查询两侧的采样时刻,再用两者间距限制线性插值跨长缺口;本文检查的是查询时刻减去最近历史记录时刻。两种时间差对应不同的可用性规则,不能把"附近将有新样本"当作当前已经有新数据。

例如一路传感器更新很快,另一路更新较慢。主通道每到一个时间戳,都希望带上慢速通道的最近记录,再计算差值、画趋势或送进报警逻辑。

这里有两个容易被连续曲线掩盖的问题。第一,补进去的数值是否用到了查询时刻之后的样本?第二,即使它确实来自过去,这条数据是否已经太旧?设备在最后一次正常读数之后断流,单纯向前保持仍会给出一个看起来正常的数字。

本文把快速通道的时间戳作为主时间轴 tq,只处理慢速通道的时间戳 ts 和数值 xs。规则很明确:先选择不晚于查询时刻的最新样本,再检查该样本的年龄和有效性。 函数同时返回值、来源索引、数据年龄及状态,避免下游只看见一个 NaN,却不知道是从未收到、已经过期,还是新样本本身无效。

这里的"因果"只约束事件时间,不保证记录在查询时已经到达;严格在线回放还需要按到达或可获得时间筛选,具体边界见第 8 节。

本次实测环境与验证范围: MATLAB 25.2.0.2998904 (R2025b),PCWIN64。2026-10-22 08:06:58--08:07:49 UTC 完成原生验证,退出码为 0;该时间段包含完整验证流程,不是算法性能基准。

  • 独立测试通过 21,890 个合法输入案例、42 个非法输入案例、31 项性质/范围检查,包括未来样本追加不变量和代表性 ULP 边界。
  • 四份实测 .m 文件与待测源码哈希匹配,源码未改;PNG、FIG、CSV、MAT 均已实际生成并回读,独立运行与图像复核已完成。
  • Code Analyzer 对核心函数与演示无诊断;运行入口和独立测试脚本另有 5 条维护建议,已记录,不能将本次结果概括为"全包零诊断"。

1 先固定输入与输出契约

本例处理已经处于同一时钟基准下的事件时间。时间单位统一为秒,并使用相对于同一个原点的非负数值。

输入 约束 这样约束的原因
ts 实数 double 向量,有限、非负、严格递增 每个源时间唯一,扫描指针可以只向前移动
xs 与 ts 等长的实数 double 向量;允许 NaN,拒绝正负 Inf NaN 明确表示一次无效测量
tq 实数 double 向量,有限、非负、非递减,允许重复 支持多个记录共享查询时刻,但不在内部重排记录
maxAge 有限、非负的实数 double 标量 给出这一路传感器允许保持的最大数据年龄

向量可以是行向量或列向量,也接受空输入;输出统一为列向量。空源数据和只有一个源样本的情况都有明确行为,不需要凑够两个点。

isfinite 用于识别有限数,NaN 和正负无穷都不属于有限值。本实现对时间要求有限,对测量值则单独允许 NaN,因此没有用一次"删除所有非有限行"来清洗全部输入。MathWorks:isfinite

输出含义如下:

输出 含义
idx 选中的源样本索引;没有历史样本时为 0,不能拿它直接索引 xs
age 查询时刻减去选中样本的时刻;没有历史时为 NaN
missing 存在历史样本,且最新历史样本的值为 NaN
stale 存在历史样本,且 age > maxAge
valid 有历史、值有效、未过期时为真
y 仅在 valid 为真时输出样本值,否则为 NaN

这里的 missing 特指"选中的测量值缺失",不包括"从未有过历史样本"。后者由 idx == 0 单独表示。没有历史时,missing、stale 和 valid 都为假,因此不能仅用 ~missing & ~stale 在函数外重建有效状态,还必须检查 idx > 0。

2 把选择与判定写成两个步骤

对第 k 个查询时刻,先定义候选集合:

H k = { i ∣ t s ( i ) ≤ t q ( k ) } . H_k=\{i\mid t_s(i)\leq t_q(k)\}. Hk={i∣ts(i)≤tq(k)}.

如果集合为空,返回 idx(k)=0、age(k)=NaN、y(k)=NaN。否则,因为源时间严格递增,最新历史样本就是

j k = max ⁡ H k , a k = t q ( k ) − t s ( j k ) . j_k=\max H_k,\qquad a_k=t_q(k)-t_s(j_k). jk=maxHk,ak=tq(k)−ts(jk).

最终输出为

y ( k ) = { x s ( j k ) , x s ( j k ) 有限且 a k ≤ τ , N a N , 其他情况 , τ = maxAge . y(k)=\begin{cases} x_s(j_k), & x_s(j_k)\text{ 有限且 }a_k\leq\tau,\\ \mathrm{NaN}, & \text{其他情况}, \end{cases} \qquad \tau=\texttt{maxAge}. y(k)={xs(jk),NaN,xs(jk) 有限且 ak≤τ,其他情况,τ=maxAge.

选择 j_k 时只看时间,不先过滤 NaN。如果新样本报告无效,它就应当使当前保持值失效。若先删除这一行,程序可能悄悄退回更早的正常值,把"刚刚收到的无效测量"变成"继续正常输出"。

这是一种明确的工程策略:最新记录负责描述当前数据状态,不能因为它不好看就绕过它。 如果业务确实需要"最近一次有效测量",应另建接口和名称,并同时暴露它距离当前时刻的年龄,不能与本文接口混用。

3 手算十个查询点

设源数据为:

matlab 复制代码
ts = [1; 3; 7; 8];
xs = [10; 30; NaN; 80];
tq = [0; 1; 2; 3; 5; 6; 7; 8; 10; 11];
maxAge = 2;

下面的表先按上述定义逐行手算,再由本次原生 MATLAB 断言逐项核对,六个输出字段均与手算一致。T、F 分别表示真、假。推导给出预期,原生断言则检查实现是否满足预期。

tq / s idx age / s missing stale valid y 状态
0 0 NaN F F F NaN NO_HISTORY
1 1 0 F F T 10 VALID
2 1 1 F F T 10 VALID
3 2 0 F F T 30 VALID
5 2 2 F F T 30 VALID
6 2 3 F T F NaN STALE
7 3 0 T F F NaN MISSING
8 4 0 F F T 80 VALID
10 4 2 F F T 80 VALID
11 4 3 F T F NaN STALE

这组输入的手算计数为 6 个有效查询、1 个无历史查询、2 个过期查询、1 个测量缺失查询,本次原生运行得到相同计数。

几个边界值得单独看:

  • tq=5 和 tq=10 的年龄恰好为 2 秒,按 age <= maxAge 接受。
  • tq=7 选中的是时间 7 秒的 NaN,不能回退到时间 3 秒的数值 30。
  • tq=10 已超过最后一个源时间 8 秒,但最后样本仍在允许年龄内,因此可以输出 80。
  • tq=0 没有源样本可选。此时既不计算一个虚构年龄,也不借用第一个未来样本。

missing 与 stale 是两个独立条件,并不互斥。例如单点源数据 ts=7、xs=NaN,在 tq=10、maxAge=2 时,最新样本既缺失又过期,两项都应为真。演示里的 STALE_MISSING 只是便于查看的组合标签,不替代这两个布尔量。

4 为什么 nearest 和 linear 不能直接替代

只看两个样本 (1,10)、(3,30),查询时刻为 2.6 秒:

方法 解析结果 此查询使用的数据
linear 26 1 秒与 3 秒的样本
nearest 30 3 秒的样本
previous 10 1 秒的样本

线性结果可以直接算出:

10 + 2.6 − 1 3 − 1 ( 30 − 10 ) = 26. 10+\frac{2.6-1}{3-1}(30-10)=26. 10+3−12.6−1(30−10)=26.

3 秒的样本晚于 2.6 秒,因此前两种结果都用了查询时刻之后的信息。它们可以用于允许双侧信息的离线插值,但不满足本文"只取历史"的选择规则。interp1 的这些方法定义和 previous 至少需要两个样本的要求,可查阅 MathWorks:interp1(R2025b)。

interp1(...,'previous') 可以作为至少两个源点、源时间范围内 的历史选择交叉检查,但它不会替我们执行这里的数据年龄策略。它默认在源时间范围外返回 NaN;本文函数则允许在右侧继续保持,直到最后样本过期。以手算表的 tq=10 为例,两者的边界契约就不同。本文还支持只有一个源样本,不能把该情况直接交给 interp1 对比。MathWorks:interp1 的外推与方法要求

交叉检查时要先限定共同适用范围,而不是发现右端结果不同,就给其中一个实现判错。

5 一个只向前移动的指针就够了

核心循环保留指针 j,初始为 0。处理查询 tq(k) 时,只要下一个源时间仍满足 ts(j+1) <= tq(k),就继续向前推进。

循环退出后恰好有两种情形:

  1. j=0,说明源数据为空,或第一个源样本晚于查询,均没有历史;
  2. j>0,当前样本不晚于查询,而下一个样本不存在或晚于查询,因此 j 正是最新历史样本。

为什么下一次查询不用从头找?因为 tq 非递减。已经不晚于当前查询的源样本,也不会晚于下一次查询;指针无需后退。重复查询时刻同样成立。

令源点数为 N、查询点数为 M。外层执行 M 次,指针在整个过程中最多前进 N 次,核心扫描时间复杂度为 O(N+M) 。六个输出按查询长度分配,为 O(M) ;扫描本身只增加常数个指针和标量状态,即 O(1) 额外状态。输入校验使用 diff 与逻辑临时数组,其临时空间可达 O(N+M),因此不能把整个函数的额外内存宣称为严格 O(1)。

追加未来样本不能改写已有查询

这个选择规则还带来一个可以独立验证的不变量:固定原查询序列,只追加严格晚于所有查询时刻的源样本,并继续满足源时间严格递增,原查询的全部输出应保持不变。

证明很直接。新追加的时间均不满足任一原查询的 ts <= tq 条件,所有候选集合 H_k 都没有变化;选中索引、年龄、缺失标记、过期标记以及最终值也就不变。新增样本是正常值还是 NaN,都不应影响这些过去查询。

以十行手算输入为例,可在源数据末尾追加时间 12 秒、数值 120,仍只查询原来的 0 到 11 秒,再比较六个输出。含 NaN 的输出应使用能按位置比较 NaN 的方式检查。本次独立测试已通过"严格追加未来样本不改变原查询输出"的性质检查;这里的 12 秒示例用于解释不变量,不另充当一条运行记录。

条件中的"晚于所有查询"不能删掉。如果新增的是旧事件时间的迟到记录,它可能进入原查询的候选集合,离线结果就可能改变;这已经涉及迟到数据策略,不属于上述不变量。

6 完整核心函数

保存为 asof_age.m。输入检查会直接拒绝乱序、重复源时间、无穷测量值和不符合类型的输入,不在内部隐藏排序或去重。

matlab 复制代码
function [y, age, idx, valid, missing, stale] = asof_age(ts, xs, tq, maxAge)
%ASOF_AGE Latest sample at/before query time, gated by age and finite value.
% All time inputs are nonnegative relative seconds in real double vectors.
% ts must be strictly increasing; tq may repeat but must not decrease.
% xs may contain NaN (invalid measurement), but not Inf. Empty vectors work.
% All outputs are columns. idx=0 means no historical sample; never index xs(0).
% A newer NaN invalidates the hold: this function does not seek older good data.
% This is event-time alignment, not an arrival-time availability guarantee.

checkVector(ts, 'ts');
checkVector(xs, 'xs');
checkVector(tq, 'tq');
ts = ts(:); xs = xs(:); tq = tq(:);
if numel(ts) ~= numel(xs)
    error('Asof:Length', 'ts and xs must have the same number of elements.');
end
if any(~isfinite(ts)) || any(ts < 0) || any(diff(ts) <= 0)
    error('Asof:SourceTime', 'ts must be finite, nonnegative and strictly increasing.');
end
if any(~isfinite(tq)) || any(tq < 0) || any(diff(tq) < 0)
    error('Asof:QueryTime', 'tq must be finite, nonnegative and nondecreasing.');
end
if any(isinf(xs))
    error('Asof:Value', 'xs may contain NaN but not Inf.');
end
if ~(isa(maxAge, 'double') && isreal(maxAge) && isscalar(maxAge) ...
        && isfinite(maxAge) && maxAge >= 0)
    error('Asof:MaxAge', 'maxAge must be a finite nonnegative double scalar.');
end

nq = numel(tq); ns = numel(ts);
y = nan(nq, 1); age = nan(nq, 1); idx = zeros(nq, 1);
valid = false(nq, 1); missing = false(nq, 1); stale = false(nq, 1);
j = 0;
for k = 1:nq
    while j < ns && ts(j + 1) <= tq(k)
        j = j + 1;
    end
    if j == 0
        continue
    end
    idx(k) = j;
    age(k) = tq(k) - ts(j);
    missing(k) = isnan(xs(j));
    stale(k) = age(k) > maxAge;
    valid(k) = ~missing(k) && ~stale(k);
    if valid(k)
        y(k) = xs(j);
    end
end
end

function checkVector(a, name)
if ~(isa(a, 'double') && isreal(a) && ismatrix(a) ...
        && (isvector(a) || isempty(a)))
    error('Asof:Type', '%s must be a real double vector or empty array.', name);
end
end

7 完整演示与原生图生成

将下面文件保存为 asof_age_demo.m,与核心函数放在同一目录。演示包含手算期望断言、插值对照、三幅子图及 CSV/MAT 导出。代码采用基础 MATLAB 功能,本次已在 R2025b 原生环境执行通过。

三个绘图区分别用于观察:被接受的历史值与被拒绝的过期保持值、每个查询点的数据年龄、2.6 秒处三种插值方法的信息来源。图中的阶梯线只帮助定位源记录;有效查询点和状态输出才是接口判定结果。

图 1 本次 MATLAB 原生导出的三联图。上图区分接受值与过期保持值,中图显示数据年龄,底图比较 2.6 秒处三种插值方法;图像已完成独立复核。

PNG 使用 exportgraphics 从 MATLAB 图形对象导出,并设置分辨率;同时保留可重新打开的 FIG。关于图形对象导出和分辨率参数,见 MathWorks:exportgraphics。

matlab 复制代码
function r = asof_age_demo(outDir)
%ASOF_AGE_DEMO Reproducible base-MATLAB example; no toolbox is required.
% Running this function generates native MATLAB figures, CSV and MAT files.
if nargin < 1
    outDir = fullfile(fileparts(mfilename('fullpath')), 'results');
end
if ~exist(outDir, 'dir'), mkdir(outDir); end

ts = [1; 3; 7; 8];
xs = [10; 30; NaN; 80];
tq = [0; 1; 2; 3; 5; 6; 7; 8; 10; 11];
maxAge = 2;
[y, age, idx, valid, missing, stale] = asof_age(ts, xs, tq, maxAge);
expectedIdx = [0; 1; 1; 2; 2; 2; 3; 4; 4; 4];
expectedAge = [NaN; 0; 1; 0; 2; 3; 0; 0; 2; 3];
expectedY = [NaN; 10; 10; 30; 30; NaN; NaN; 80; 80; NaN];
assert(isequal(idx, expectedIdx), 'Demo:Index', 'Hand-derived index mismatch.');
assert(isequaln(age, expectedAge), 'Demo:Age', 'Hand-derived age mismatch.');
assert(isequaln(y, expectedY), 'Demo:Value', 'Hand-derived value mismatch.');
assert(nnz(valid) == 6 && nnz(idx == 0) == 1 && nnz(stale) == 2 ...
    && nnz(missing) == 1, 'Demo:Counts', 'State counts mismatch.');

status = repmat("VALID", size(tq));
status(idx == 0) = "NO_HISTORY";
status(stale) = "STALE";
status(missing) = "MISSING";
status(stale & missing) = "STALE_MISSING";
resultTable = table(tq, idx, age, missing, stale, valid, y, status);
disp(resultTable);
writetable(resultTable, fullfile(outDir, 'asof_hand_case.csv'));

% A deliberately tiny example reveals interpolation's information boundary.
smallT = [1; 3]; smallX = [10; 30]; q = 2.6;
linearValue = interp1(smallT, smallX, q, 'linear');
nearestValue = interp1(smallT, smallX, q, 'nearest');
previousValue = interp1(smallT, smallX, q, 'previous');
assert(abs(linearValue - 26) < 1e-12 && nearestValue == 30 ...
    && previousValue == 10, 'Demo:Interp', 'Interpolation contrast mismatch.');

f = figure('Visible', 'off', 'Color', 'w', 'Position', [80 80 1120 900]);
tl = tiledlayout(f, 3, 1, 'TileSpacing', 'compact', 'Padding', 'compact');
ax1 = nexttile(tl);
stairs(ax1, ts, xs, '-s', 'LineWidth', 1.5, 'DisplayName', 'Recorded samples');
hold(ax1, 'on');
plot(ax1, tq(valid), y(valid), 'o', 'MarkerSize', 8, ...
    'LineWidth', 1.5, 'DisplayName', 'Accepted query value');
bad = stale & ~missing & idx > 0;
plot(ax1, tq(bad), xs(idx(bad)), 'rx', 'MarkerSize', 10, ...
    'LineWidth', 1.8, 'DisplayName', 'Rejected stale hold');
xline(ax1, 7, ':k', 'NaN sample at 7 s', 'HandleVisibility', 'off');
grid(ax1, 'on'); xlim(ax1, [0 11.3]); ylim(ax1, [0 95]);
ylabel(ax1, 'Sensor value (a.u.)');
title(ax1, 'Latest historical sample is not always an acceptable value');
legend(ax1, 'Location', 'northwest');

ax2 = nexttile(tl);
stem(ax2, tq, age, 'filled', 'LineWidth', 1.2, 'DisplayName', 'Selected sample age');
hold(ax2, 'on');
yline(ax2, maxAge, '--r', 'Max age = 2 s', 'DisplayName', 'Expiry threshold');
grid(ax2, 'on'); xlim(ax2, [0 11.3]); ylim(ax2, [-0.2 3.8]);
ylabel(ax2, 'Age (s)');
title(ax2, 'Exact threshold is accepted; age is undefined without history');

ax3 = nexttile(tl);
plot(ax3, smallT, smallX, 'ks', 'MarkerFaceColor', 'k', 'DisplayName', 'Recorded samples');
hold(ax3, 'on');
plot(ax3, smallT, smallX, ':k', 'HandleVisibility', 'off');
plot(ax3, q, linearValue, 'bd', 'MarkerSize', 9, 'DisplayName', 'Linear = 26');
plot(ax3, q, nearestValue, 'r^', 'MarkerSize', 9, 'DisplayName', 'Nearest = 30');
plot(ax3, q, previousValue, 'go', 'MarkerSize', 9, 'LineWidth', 1.5, 'DisplayName', 'Previous = 10');
xline(ax3, q, '--', 'Query at 2.6 s', 'HandleVisibility', 'off');
grid(ax3, 'on'); xlim(ax3, [0.8 3.2]); ylim(ax3, [5 35]);
xlabel(ax3, 'Relative time (s)'); ylabel(ax3, 'Sensor value (a.u.)');
title(ax3, 'The sample at 3 s is future information at query time');
legend(ax3, 'Location', 'northwest');
exportgraphics(f, fullfile(outDir, 'asof_age_alignment.png'), 'Resolution', 160);
savefig(f, fullfile(outDir, 'asof_age_alignment.fig'));
close(f);

r = struct('matlabVersion', version, 'release', version('-release'), ...
    'computer', computer, 'validCount', nnz(valid), ...
    'noHistoryCount', nnz(idx == 0), 'staleCount', nnz(stale), ...
    'missingCount', nnz(missing), 'linearValue', linearValue, ...
    'nearestValue', nearestValue, 'previousValue', previousValue);
save(fullfile(outDir, 'asof_age_evidence.mat'), ...
    'r', 'ts', 'xs', 'tq', 'maxAge', 'resultTable');
fprintf('MATLAB %s (%s), %s\n', r.matlabVersion, r.release, r.computer);
fprintf('Demo completed: valid=%d, no_history=%d, stale=%d, missing=%d\n', ...
    r.validCount, r.noHistoryCount, r.staleCount, r.missingCount);
end

在 MATLAB 当前文件夹切换到两个 .m 文件所在目录后执行:

matlab 复制代码
outDir = fullfile(pwd, 'results');
r = asof_age_demo(outDir);

也可以在系统终端中先进入该目录,再使用 MATLAB 批处理方式:

text 复制代码
matlab -batch "r = asof_age_demo(fullfile(pwd,'results'));"

本次已实际生成并回读的文件类型如下。 表中使用上方复现命令的 results 目录;正式验证入口为每次运行创建独立子目录,避免覆盖或混入旧证据。

文件 内容
results/asof_hand_case.csv 十行查询的索引、年龄、状态与输出
results/asof_age_alignment.png MATLAB 原生三联图
results/asof_age_alignment.fig 可重新打开检查的 MATLAB 图形
results/asof_age_evidence.mat 输入、结果表及实际运行环境信息

演示只有在断言及后续导出流程成功后才会打印完成信息。本次 PNG 已完成独立目视复核,FIG 已重新打开并核对三个坐标轴,CSV/MAT 已回读核对。读者复现时,仍应检查图例、坐标轴、缺失点和过期点,再把 CSV 与手算表逐行对照。MAT 文件里的 version、release 和 computer 应来自各自当次运行,不应复制其他日期的环境记录。

本文的手算演示不能替代独立技术复核。本次另行执行的独立参考算法对每个查询重新扫描源时间,并选择符合条件的最大时间,不复用核心函数的单指针扫描。它已与核心函数逐字段比较,实际检查了空输入、单点、重复或乱序输入的处理,以及年龄阈值和代表性浮点数边界。第 8 节另外讨论的绝对纳秒时间预处理和线上到达策略属于接入建议,不是已经部署实测的流水线;这些测试通过不表示可以省略真实项目自身的数据契约与误差分析。

8 接入真实日志之前检查这些边界

空输入与单点输入

空源数据配非空查询时,每个查询都没有历史:idx=0、age=NaN、y=NaN。空查询返回零行列向量。单点源数据则在该时刻之前无历史、该时刻及之后按年龄和测量值判定。

maxAge=0 也有定义:只有年龄按当前数值表示恰好为零,且样本值有效时,才允许输出。

重复时间不能用 unique 悄悄决定

本函数拒绝重复的源时间戳。两个记录时间相同但数值不同,应保留先到、后到、质量更高的一个,还是按业务聚合?这些是采集协议或数据治理规则,函数没有足够信息替使用者决定。

因此不要在调用前随手加一句 unique,就以为问题已经解决。应先确定重复记录的含义、保留规则以及质量标记,再形成严格递增的源序列。源时间乱序同样应在上游明确处理;本函数报错,不会暗中排序。查询时间允许重复,但不允许倒退。

数据年龄使用直接的 double 比较

本实现直接计算 age = tq - ts(idx),随后判断 age > maxAge。因此实际算出的 double 年龄恰好等于阈值时接受 ,大于阈值时拒绝,没有自动增加 eps 或其他容差。

十进制小数在二进制浮点数中的表示及减法舍入可能影响边界。若业务允许时钟量化误差,应先定义量化单位或明确的容差规则,而不是临时扩大阈值来让测试通过。

复核时应另外选择可解释的代表性数值,检查阈值处以及邻近可表示数。例如以 ts=0、maxAge=2 为例,查询 2-eps(2)/2、2、2+eps(2),解析预期为有效、有效、过期;本次原生独立测试已逐项核对通过,也覆盖了非零源时间的边界案例。接入真实数据时,还应按实际采样量级检查减法后的年龄,不能只测整数秒。

大整数时间戳应先减原点再转 double

如果原始时间是绝对纳秒计数,直接转换为 double 可能把相邻时间舍入成同一个数,破坏顺序或年龄精度。应在原始精确整数表示中,为两路数据选取同一个原点,确认差值范围与减法安全,再把相对差值转换为 double 并换算为秒。

不要先把巨大的绝对时间转成 double,之后才减原点,并认为丢失的低位能恢复。还应检查相对跨度与所需时间分辨率是否匹配;"相对秒"并不自动保证任何量级都足够精确。

事件时间上的历史不等于在线已经收到

本文的 ts <= tq 是事件时间条件。如果传感器在 1 秒采样,但消息到 4 秒才到达,离线日志可以把它视为 2 秒查询的历史样本,真实在线程序在 2 秒却还拿不到它。

因此本函数适合同一时钟基准下的离线或事件时间对齐。若要复现在线行为,还需要记录到达时间,并明确迟到数据、乱序缓冲和决策截止时刻的处理规则。这里没有解决通信传输延迟、迟到补发、设备时钟偏移或时钟漂移,也没有重建传感器在两个采样点之间的真实物理轨迹。

9 让无效状态继续传到下游

对齐函数返回 NaN 后,不宜立即用零替代。零可能本来就是合法测量值,这样会把"不知道"改成一个确定读数;同样,未经业务定义再次向前填充,也会绕过前面设置的过期规则。

接入差值、融合或报警时,至少同时传递 y 和 valid。需要定位问题时,再保留 idx、age、missing、stale,区分启动阶段没有历史、采集到无效值和数据长时间未更新。

如果后续要接报警状态机,相关主题可以继续阅读《MATLAB 工程实验:滞回加持续时间报警,把 UNKNOWN 留在状态机里》。本文先在数据入口保留无效状态:把测量是否可用说清楚,再让后续算法依据状态作出决定,而不是在对齐时就把未知抹平。

你的慢速通道一旦收到新的 NaN,下游应立即停止使用旧值,还是进入一个单独标记、限时保持的降级状态;这个期限具体由哪项工程约束决定?

相关推荐
思麟呀2 小时前
BTree的原理与 C++ 完整实现
c语言·数据结构·c++·算法·1024程序员节
黄俊懿2 小时前
【架构师从入门到进阶】第七章:缓存和存储的优化思路——第一节:缓存概述
数据结构·数据库·分布式·缓存·架构·系统架构·1024程序员节
Tsuki_tl2 小时前
【优选算法 — 双指针】经典例题题解+算法总结
算法·蓝桥杯·力扣·刷题·双指针·1024程序员节·优选算法
晒太羊的猫3 小时前
hot100 堆
数据结构
拓恒技术3 小时前
内河航道无人机智能巡检系统与算法应用
人工智能·深度学习·算法·无人机·智慧城市
啵啵啵鱼3 小时前
用 Claude Code 重构遗留系统:从评估到落地的完整实践
数据结构·1024程序员节
如何原谅奋力过但无声3 小时前
【LeetCode hot100】代码随想录未cover部分
数据结构·算法·leetcode
INGNIGHT3 小时前
377.组合总和 Ⅳ(背包)
c++·算法·1024程序员节
H.莓飛4 小时前
【数据结构】排序_OJ题(二):划分与选择
linux·服务器·c语言·开发语言·数据结构·visual studio