chronaxis.core.calculations_reference
源码基线:8904c2f744951857659abd40aaa3535d57faf53c
1"""独立计算保持值积分、时间统计、TRUE时长和真实样本统计。23compute_reference接收CalculationSeries和CalculationDefinition,先裁剪观测与请求窗口,4再独立计算面积、样本归约、覆盖时长及未知区间,输出CalculationResult。5积分使用零阶保持:左端值乘以相邻区间裁剪后的时长。先对整数纳秒端点6求差,再换算秒,不把较大的绝对时间先转成浮点数。7时间极值包含裁剪边界的保持值,time_weighted_mean按有效选择时长加权;duration8累计有效主BOOL为TRUE的区间。select_reference保留真实TRUE/UNKNOWN更新行。910样本统计只取actual_value_updates为True的真实数值更新,不包含窗口或11有效期到期边界,也不包括半开窗口末点。值有效与条件已知分别统计,12条件FALSE是已知未选中,UNKNOWN(未知)不能视作FALSE或补成零。1314本实现只使用标准库与核心模型,数值和覆盖不调用NumPy。可共享输入15检查、单位与结果分类,但归约独立实现。保留完整内存输入,不读写文件,16不修改原数据或持有跨任务状态;本模块不提供流式内存预算保证。17"""1819# 独立标量有限性/稳定求和,不调用数组归约。20from math import fsum, isfinite21# 共享模型、单位与条件始终为FALSE时的空选择规则,不共享数值计算。22from .calculations import CalculationSeries, CalculationDefinition, make_calculation_result, calculation_conversion23# 诊断区间与请求都保持精确纳秒。24from .models import Interval25# 独立参考定宽到float转换先检查精度。26from .numeric import ScalarValue, to_float_reference27# 未知/计算质量与正常零严格分开。28from .types import QualityFlag as Q, SignalDType as D, Truth29# 选择只共享冻结协议,标量筛选独立于NumPy掩码。30from .selection import SelectionRow, require_selection, partial_selection31from .record_intervals import clipped_record_seconds, require_record_step32from .calculation_output_reference import convert_result_reference33from .affine import affine_reference34from .moments import MOMENTS, TIME_MOMENTS, compute_moment_series35from .quantiles import SAMPLE_QUANTILES, compute_quantile_series363738def require_calculation(series, spec, *, step_ns=None):39 """验证实际指标输入模型,不执行任何面积或统计。4041 参数: series真实CalculationSeries,spec真实CalculationDefinition;step_ns可指定记录对周期,42 仅连续统计算子可用,输入必须是所选驱动真实更新。43 返回: None,模型内部完整轴和dtype已校验。44 异常: TypeError为无类型占位输入;ValueError为select标量执行或dtype不匹配。45 """46 # 后端共享typed协议,不复用被测数值。47 if not isinstance(series, CalculationSeries) or not isinstance(spec, CalculationDefinition):48 # 不猜dict内不完整状态。49 raise TypeError('指标执行需CalculationSeries和CalculationDefinition')50 # 行流具有独立返回类型,不能作为标量统计执行。51 if spec.operation == 'select':52 raise ValueError('select需选择行流入口')53 # 布尔只用于TRUE时长;数值统计不接受布尔或枚举编码。54 allowed = (D.BOOL,) if spec.operation == 'duration' else (D.INT64, D.UINT64, D.FLOAT64)55 if series.values[0].dtype not in allowed:56 raise ValueError('指标算子与输入dtype不匹配')57 if step_ns is not None and spec.operation not in ('integral', 'duration', 'time_weighted_mean', 'min_time', 'max_time', *TIME_MOMENTS):58 raise ValueError('样本统计不接受记录对周期')59 require_record_step(step_ns, series.actual_value_updates)606162def select_reference(series, spec):63 """按真实更新逐行返回TRUE与UNKNOWN选择结果,不累计输出列表。6465 参数: series完整内存CalculationSeries;spec为select请求。66 返回: SelectionRow迭代器;UNKNOWN保留诊断,FALSE排除,窗口右端不输出。67 异常: 开始迭代时传播模型/算子/单位错误。68 流程: 遍历完整时间轴,只处理请求与观测交集内的真实目标依赖更新。69 合成边界与条件独自更新不产生行;无效值保持原质量,不填正常零。70 """71 # 生成器检查发生在消费时;只共享输入门槛。72 require_selection(series, spec)73 conversion = calculation_conversion(spec, series.unit)74 start = max(spec.window.start_ns, series.observation.start_ns)75 end = min(spec.window.end_ns, series.observation.end_ns)76 # 输入已经完整物化,输出每次只创建当前行。77 for index, time in enumerate(series.times_ns):78 # 不插入保持值行、不读取EOF之后的数据。79 if not start <= time < end or not series.actual_value_updates[index]:80 continue81 # 未提供条件就是明确TRUE,未知条件单独诊断。82 condition = series.conditions[index] if series.conditions is not None else ScalarValue(True, D.BOOL)83 selection = Truth.UNKNOWN if not condition.valid else Truth.TRUE if condition.value else Truth.FALSE84 if selection is Truth.FALSE:85 continue86 # 原值保持dtype/质量/标签与绑定,UNKNOWN不是自动选择成功。87 value = series.values[index]88 # 当前输出只保留换算后的一份值;原样本及实际时间不改写。89 if conversion is not None and (conversion.factor != 1 or conversion.offset != 0):90 value = affine_reference(value, conversion.factor, conversion.offset, conversion.dtype(value.dtype))91 quality=series.diagnostics[index] | value.quality | condition.quality92 if series.values[index].partial or condition.partial:93 value,quality=partial_selection(value,quality,spec)94 yield SelectionRow(time, value, selection, quality,95 series.binding_identity, series.source_refs, series.unit if conversion is None else conversion.unit)969798def compute_reference(series, spec, *, step_ns=None, budget=None):99 """独立计算请求窗口内的积分、样本/时间统计或BOOL TRUE时长。100101 参数:102 series: 完整CalculationSeries;数值统计需同一数值dtype,duration需BOOL。103 spec: CalculationDefinition,给出左闭右开纳秒窗口、算子、输出单位和部分结果政策。104 step_ns: 可选固定/DBC周期,仅用于所选驱动的真实记录对;按交集比例105 计算面积、时长和均值权重,不改变实际坐标或输出第二套时间。106 budget为精确分位数的可选共享RAM账本,省略时采用项目默认配额。107 返回:108 CalculationResult。未换单位的整数极值可保持精确整数;积分和样本平均109 返回浮点值;时间平均先规范化时长权重,duration输出秒。无法核实时110 为None,并保留覆盖、来源与质量原因;select使用独立行流入口。111 异常:112 输入模型或单位配置错误直接传播TypeError或ValueError。113 非有限结果、下溢或转换丢精度作为质量原因返回,不吞配置错误。114 流程:115 裁剪窗口后逐区间计算保持面积与三值选择交集,再按真实更新归约样本,116 最后应用一次输出单位转换并分类结果。正常零与未知占位分别处理。117 临时值和诊断列表只用于本次计算,不修改调用者的CalculationSeries。118 """119 # 配置错误不能吞成未知数据。120 require_calculation(series, spec, step_ns=step_ns)121 if spec.operation in SAMPLE_QUANTILES:122 from .sample_reduction_reference import ReferenceSampleReduction123 return compute_quantile_series(series, spec, ReferenceSampleReduction, budget)124 if spec.operation in MOMENTS:125 from .sample_reduction_reference import ReferenceSampleReduction126 from .time_reduction_reference import ReferenceTimeReduction127 from .conditional_time_reduction_reference import ReferenceConditionalTimeReduction128 return compute_moment_series(series, spec, ReferenceSampleReduction, ReferenceTimeReduction,129 ReferenceConditionalTimeReduction, step_ns)130 # 仅显式已知维度转换,保留有符号面积。131 conversion = calculation_conversion(spec, series.unit)132 unit, factor = conversion.unit, conversion.factor133 # 先求真实观测与请求交集,不外推末点/EOF。134 start = max(spec.window.start_ns, series.observation.start_ns)135 # 半开窗口右端实际观测不足必须裁剪。136 end = min(spec.window.end_ns, series.observation.end_ns)137 # 完全无交集与零域分别由政策解释。138 actual = Interval(start, end) if start <= end else None139 # 整数输入进入float面积/mean前先严格验证,不影响精确min/max。140 values = [to_float_reference(value) if spec.operation in ('integral', 'sample_mean', 'time_weighted_mean') else value for value in series.values]141 # 全部覆盖时长使用Python精确整数,不按样本数量估算。142 value_duration, known_duration, selected_duration, selected_unknown = 0, 0, 0, 0143 # 明确FALSE时长用来证明充分观测空选择,不靠sum空数组。144 false_duration = 0145 # 真实未知区间按原因类型分别记录,不改变原输入。146 value_unknown, condition_unknown, areas = [], [], []147 # 时间统计保留每个正裁剪区间的左端保持值和整数纳秒时长。148 timed_values = []149 # duration必须至少有可核实的主布尔值与TRUE选择区间,不能靠外部UNKNOWN补零。150 duration_evidence = 0151 # 原诊断与计算原因完整OR,不将KNOWN逻辑结果改成UNKNOWN。152 quality = Q.NONE153 # 独立参考逐区间求裁剪/面积/三值交集。154 for index in range(len(series.times_ns)-1):155 # 先裁剪精确整数端点,不先转float绝对坐标。156 left, right = max(series.times_ns[index], start), min(series.times_ns[index+1], end)157 # 窗口外或零区间不产生面积/未知时长。158 if left >= right:159 # 边界point不是额外周期。160 continue161 # 先整数差再换算秒,保留大绝对时间的小间隔。162 duration = right - left163 # 当前ZOH值与当前选择独立有效性。164 value = values[index]165 # 无条件表示明确选择全部,而不是未知选择。166 condition = series.conditions[index] if series.conditions is not None else ScalarValue(True, D.BOOL)167 # 原依赖、数值与条件质量均可追溯。168 quality |= series.diagnostics[index] | value.quality | condition.quality169 # 正常积分面积先计算,非有限/下溢不变成正常0。170 area = None171 # 只计算真实选择的有效值,FALSE不制造所选值缺失。172 if spec.operation == 'integral' and value.valid and condition.valid and condition.value:173 # 相对dt转秒,默认ZOH保持当前左端值。174 seconds = (duration / 1_000_000_000 if step_ns is None else175 clipped_record_seconds(series.times_ns[index], series.times_ns[index + 1],176 spec.window, step_ns=step_ns))177 area = value.value * seconds178 # 有限输入乘积溢出和非零乘积下溢显式失效。179 failure = Q.NONFINITE if not isfinite(area) else Q.PRECISION_LOSS if area == 0 and value.value != 0 else Q.NONE180 # 不参与不可信面积的正常归约。181 if failure != Q.NONE:182 # 元数据值仍保留,只有计算输出置未知。183 value = ScalarValue(value.value, D.FLOAT64, False, failure)184 # 相同真实样本的积分可用性也更新。185 values[index] = value186 # 新原因不覆盖原诊断。187 quality |= failure188 # 值有效时长与条件真假独立统计。189 if value.valid:190 # 有效零值计入覆盖,区别于UNKNOWN。191 value_duration += duration192 else:193 # 不跨窗口外范围保存未知。194 value_unknown.append(Interval(left, right))195 # 条件UNKNOWN独立保存;FALSE属于已知条件覆盖。196 if condition.valid:197 # 真假都可判断,不把FALSE当缺失。198 known_duration += duration199 # 只有明确FALSE累计空选择证据。200 if not condition.value:201 # 不凭无选中结果推测FALSE。202 false_duration += duration203 # 真与有效值交集才算选中时长。204 elif value.valid:205 # FALSE主值仍证明已知零时长;外部条件未知不提供该证据。206 duration_evidence += duration207 # 所选面积和时长分别保留。208 selected_duration += duration if spec.operation != 'duration' or value.value else 0209 # 其他样本算子没有连续面积。210 if spec.operation == 'integral':211 # 正常零面积合法进入归约。212 areas.append(area)213 # min/max_time包含裁剪左边界保持值,time_weighted_mean使用同一有效选择集合。214 if spec.operation in ('min_time', 'max_time', 'time_weighted_mean'):215 weight = (clipped_record_seconds(series.times_ns[index], series.times_ns[index + 1],216 spec.window, step_ns=step_ns)217 if spec.operation == 'time_weighted_mean' and step_ns is not None else duration)218 timed_values.append((value.value, weight))219 elif spec.operation == 'duration' and value.value and step_ns is not None:220 areas.append(clipped_record_seconds(series.times_ns[index], series.times_ns[index + 1],221 spec.window, step_ns=step_ns))222 else:223 # 所选值未知时strict/report有必要覆盖不足。224 selected_unknown += duration225 else:226 # 条件未知不能当FALSE产生空选择零。227 condition_unknown.append(Interval(left, right))228 # 样本口径只计真实原依赖更新,不计TTL/裁剪合成边界。229 selected_samples = []230 sample_count = 0231 # 参考逐原完整时间组统计当前值/条件,不跨域复制末点。232 for index, time in enumerate(series.times_ns):233 # 半开实际窗口内真实更新,末点排除。234 if not start <= time < end or not series.actual_value_updates[index]:235 # 不把窗口左端保持值伪装成真实样本。236 continue237 # 未知样本保留在诊断,只有有效/TRUE进入样本统计。238 value = values[index]239 # 无选择条件全部真实有效样本可用。240 condition = series.conditions[index] if series.conditions is not None else ScalarValue(True, D.BOOL)241 # 完整实际样本的质量也纳入诊断。242 quality |= series.diagnostics[index] | value.quality | condition.quality243 # 不将无效原数据存储占位0当正常样本。244 if value.valid and condition.valid and condition.value and (spec.operation != 'duration' or value.value):245 # 仅恢复当前原生逻辑数值,未改原输入。246 sample_count += 1247 if spec.operation != 'sample_count':248 selected_samples.append(value.value)249 # 空归约保持None;仅明确all_false时按空选择规则处理。250 result, failed = None, False251 # 已选可核实面积或样本才计算正常值。252 try:253 # 面积与样本统计使用不同明确口径。254 if spec.operation == 'integral' and areas:255 # fsum减少标量累计舍入,但不替代NumPy归约。256 result = fsum(areas)257 # 精确整数极值无需浮点转换。258 elif spec.operation == 'sample_count':259 result = sample_count260 elif selected_samples and spec.operation == 'sample_minimum':261 # 窗口裁剪不增加边界合成样本。262 result = min(selected_samples)263 # 同样保留uint64大整数精度。264 elif selected_samples and spec.operation == 'sample_maximum':265 # min/max不按时间加权。266 result = max(selected_samples)267 # mean明确样本算术平均,不叫模糊mean。268 elif selected_samples and spec.operation == 'sample_mean':269 # 有效样本数量作分母,不能用时长替代。270 result = fsum(selected_samples) / len(selected_samples)271 # 时间极值只计正时长保持区间,保留大整数的原生精度。272 elif timed_values and spec.operation == 'min_time':273 result = min(value for value, _ in timed_values)274 elif timed_values and spec.operation == 'max_time':275 result = max(value for value, _ in timed_values)276 # 先规范化权重,避免平均值有限但值乘纳秒的中间面积溢出。277 elif timed_values and spec.operation == 'time_weighted_mean':278 denominator = selected_duration if step_ns is None else fsum(weight for _, weight in timed_values)279 terms = [value * (duration / denominator) for value, duration in timed_values]280 if any(not isfinite(term) for term in terms):281 failed, quality = True, quality | Q.NONFINITE282 elif any(term == 0 and value != 0 for term, (value, _) in zip(terms, timed_values)):283 failed, quality = True, quality | Q.PRECISION_LOSS284 else:285 result = fsum(terms)286 # 已知FALSE可核实为零;全未知没有候选值,不能返回正常零。287 elif spec.operation == 'duration' and duration_evidence > 0:288 result = selected_duration / 1_000_000_000 if step_ns is None else fsum(areas)289 # 只把明确浮点累计溢出转计算质量,不吞接口错误。290 except OverflowError:291 # 无法核实总值不报正常Inf。292 failed, quality = True, quality | Q.NONFINITE293 # 归约后应用一次完整仿射转换,并检查目标数值精度。294 if result is not None and not failed and spec.operation != 'sample_count':295 result, conversion_quality = convert_result_reference(result, series.values[0].dtype, conversion)296 failed = conversion_quality != Q.NONE297 quality |= conversion_quality298 # 完整全FALSE证明需要非零实际域,最后政策还检查请求充分覆盖。299 all_false = series.conditions is not None and actual is not None and end > start and false_duration == end-start300 # 共同政策仅包装各独立内核已计算的事实。301 return make_calculation_result(spec, series, result, unit, actual, value_duration, known_duration, selected_duration,302 selected_unknown, sample_count, value_unknown, condition_unknown, quality,303 all_false=all_false, calculation_failed=failed)304