chronaxis.core.conditional_integral_terms_numpy
源码基线:8904c2f744951857659abd40aaa3535d57faf53c
1"""用真实NumPy列运算生成有限条件积分面积与各自的覆盖事实。23输入有限数值CalculationSeries、显式BOOL条件与原integral请求,输出只读一维float644面积、bool掩码及ConditionalIntegralFacts。先按整数ns裁剪,再按TRUE/有效掩码乘秒。5数值转换、面积、故障和覆盖归约独立于标量参考;元数据循环只恢复诊断Interval。6FALSE、UNKNOWN不做乘法,不把未知存储零视为正常结果,不提前归约或换单位。7不修改输入、不读来源;N/N-1临时列及输出由调用方有限workspace预约并消费。8"""910# 只共享非数值模型门槛;计算采用独立NumPy转换与真实ufunc/掩码。11from dataclasses import dataclass12import numpy as np13from .conditional_integral_terms import ConditionalIntegralFacts, require_conditional_integral_terms14from .calculations import merge_ranges15from .models import Interval16from .numeric_numpy import DTYPES, NumericArray, to_float_numpy17from .types import QualityFlag as Q181920@dataclass(frozen=True)21class NumpyConditionalIntegralTerms:22 """保存原序只读面积与明确掩码,不借用可写输入或隐式掩码数组。"""2324 areas: np.ndarray # float64形状(N-1,),未转换的输入单位乘秒。25 valid: np.ndarray # bool形状(N-1,),仅TRUE且正常面积可归约。26 facts: ConditionalIntegralFacts # 当前块双覆盖、样本与来源,不是全窗结果。2728 def __post_init__(self):29 """检查普通一维同长只读列、有限面积及冻结事实。3031 参数: 无。返回: None。异常: TypeError/ValueError为类型、形状、可写或占位错误。32 """33 # 拒绝自定义/隐藏掩码子类;纯输出不能依赖调用侧数组行为。34 if (type(self.areas) is not np.ndarray or type(self.valid) is not np.ndarray35 or self.areas.dtype != np.float64 or self.valid.dtype != bool36 or self.areas.ndim != 1 or self.areas.shape != self.valid.shape37 or not isinstance(self.facts, ConditionalIntegralFacts)):38 raise TypeError('数组条件积分项需要同长一维float64/bool及ConditionalIntegralFacts')39 if (self.areas.flags.writeable or self.valid.flags.writeable40 or not np.all(np.isfinite(self.areas)) or np.any(~self.valid & (self.areas != 0))):41 raise ValueError('数组条件积分项必须只读且有限,无效位置只能0存储')424344def compute_conditional_integral_terms_numpy(series, spec, *, step_ns=None, valid_until_ns=None,45 condition_valid_until_ns=None, retain_unknown_ranges=True):46 """按真实数组掩码生成面积,独立归约值、条件和选择覆盖。4748 参数: series为有限数值CalculationSeries及显式BOOL条件,spec为原integral请求。49 step_ns选择固定/DBC周期;两个until列给实际有效截止;范围明细可显式关闭。50 返回: NumpyConditionalIntegralTerms;只读面积/掩码形状N-1,临时列形状N或N-1。51 异常: 输入、单位、时间和分配错误传播;浮点故障写质量并使对应值/面积失效。52 """53 # 时间不先转float,实际交集为整数域;所有有限输入先过公共结构门槛。54 require_conditional_integral_terms(series, spec, step_ns=step_ns, valid_until_ns=valid_until_ns,55 condition_valid_until_ns=condition_valid_until_ns)56 if type(retain_unknown_ranges) is not bool:57 raise TypeError('未知范围保留选项需要bool')58 start = max(series.observation.start_ns, spec.window.start_ns)59 end = min(series.observation.end_ns, spec.window.end_ns)60 actual = Interval(start, end) if start <= end else None61 # N个真实typed值经独立数组精度核;无效0仅是受valid约束的存储。62 column = NumericArray(series.values[0].dtype,63 np.array([value.value if value.valid else 0 for value in series.values], dtype=DTYPES[series.values[0].dtype]),64 np.array([value.valid for value in series.values], dtype=bool),65 np.array([int(value.quality) for value in series.values], dtype=np.uint16))66 values = to_float_numpy(column)67 # BOOL列按已知和真值两个(N,)掩码处理,UNKNOWN占位False不进入FALSE覆盖。68 known = np.array([condition.valid for condition in series.conditions], dtype=bool)69 truth = np.array([condition.value if condition.valid else False for condition in series.conditions], dtype=bool)70 condition_quality = np.array([int(condition.quality) for condition in series.conditions], dtype=np.uint16)71 times = np.array(series.times_ns, dtype=np.int64)72 left, right = np.maximum(times[:-1], np.int64(start)), np.minimum(times[1:], np.int64(end))73 observed = right > left74 durations = np.zeros(len(left), dtype=np.int64)75 np.subtract(right, left, out=durations, where=observed)76 # 当前块副本可标记面积故障;只对TRUE有效正区间执行input_unit乘秒的ufunc。77 valid_values, quality_values = values.valid.copy(), values.quality.copy()78 value_right, condition_right = right, right79 value_durations, condition_durations = durations, durations80 # 两种有效性各有实际截止,不能用一个联合截止丢掉已知FALSE或值覆盖。81 for deadlines, is_value in ((valid_until_ns, True), (condition_valid_until_ns, False)):82 if deadlines is None:83 continue84 expiry = np.array([item if item is not None else (1 << 63) - 1 for item in deadlines], dtype=np.int64)85 has_expiry = np.array([item is not None for item in deadlines], dtype=bool)86 expired = has_expiry & (times >= expiry)87 clipped = np.maximum(left, np.minimum(right, expiry[:-1]))88 clipped_durations = np.zeros(len(left), dtype=np.int64)89 np.subtract(clipped, left, out=clipped_durations, where=observed)90 if is_value:91 valid_values[expired] = False92 quality_values[expired] |= int(Q.STALE)93 quality_values[:-1][observed & (clipped < right)] |= int(Q.STALE)94 value_right, value_durations = clipped, clipped_durations95 else:96 known[expired] = False97 condition_quality[expired] |= int(Q.STALE)98 condition_quality[:-1][observed & (clipped < right)] |= int(Q.STALE)99 condition_right, condition_durations = clipped, clipped_durations100 selected_durations = (durations if valid_until_ns is None and condition_valid_until_ns is None101 else np.minimum(value_durations, condition_durations))102 keep = observed & (selected_durations > 0) & valid_values[:-1] & known[:-1] & truth[:-1]103 areas = np.zeros(len(left), dtype=np.float64)104 seconds = selected_durations.astype(np.float64)105 if step_ns is None:106 seconds /= 1_000_000_000107 else:108 # 分母保留完整实际记录对,不能改用条件/有效期裁剪后的长度。109 seconds /= np.diff(times)110 seconds *= step_ns / 1_000_000_000111 with np.errstate(over='ignore', under='ignore', invalid='ignore'):112 np.multiply(values.values[:-1], seconds, out=areas, where=keep)113 # 非有限及非零下溢只标实际执行的TRUE项,并使该位置真实样本失效。114 nonfinite = keep & ~np.isfinite(areas)115 underflow = keep & (areas == 0) & (values.values[:-1] != 0)116 quality_values[:-1][nonfinite] |= int(Q.NONFINITE)117 quality_values[:-1][underflow] |= int(Q.PRECISION_LOSS)118 valid_values[:-1][nonfinite | underflow] = False119 keep = observed & (selected_durations > 0) & valid_values[:-1] & known[:-1] & truth[:-1]120 areas[~keep] = 0.0121 # 五种覆盖分别按(N-1,)掩码归约正整数ns,总和不超过已验证int64实际跨度。122 value_duration = int(np.sum(value_durations, where=observed & valid_values[:-1], dtype=np.int64))123 known_duration = int(np.sum(condition_durations, where=observed & known[:-1], dtype=np.int64))124 selected_duration = int(np.sum(selected_durations, where=keep, dtype=np.int64))125 selected_unknown_duration = int(np.sum(condition_durations, where=observed & known[:-1] & truth[:-1], dtype=np.int64)) - selected_duration126 false_duration = int(np.sum(condition_durations, where=observed & known[:-1] & ~truth[:-1], dtype=np.int64))127 # 样本只计真实原值更新;条件变化与右终点不成为数值样本。128 actual_updates = np.array(series.actual_value_updates, dtype=bool)129 actual_samples = actual_updates & (times >= start) & (times < end)130 sample_count = int(np.count_nonzero(actual_samples & valid_values & known & truth))131 # 诊断来自实际正区间或真实样本,不将窗口外末点质量传播入结果。132 diagnostic = np.array([int(flag) for flag in series.diagnostics], dtype=np.uint16) | quality_values | condition_quality133 relevant = actual_samples.copy()134 relevant[:-1] |= observed135 quality = Q(int(np.bitwise_or.reduce(diagnostic, where=relevant, initial=0)))136 # Python循环仅将有限未知掩码恢复精确Interval;数值和覆盖未调用参考核。137 value_unknown = (tuple(Interval(int(value_right[index] if valid_values[index] else left[index]), int(right[index]))138 for index in np.flatnonzero(observed & (~valid_values[:-1] | (value_right < right)))) if retain_unknown_ranges else ())139 condition_unknown = (tuple(Interval(int(condition_right[index] if known[index] else left[index]), int(right[index]))140 for index in np.flatnonzero(observed & (~known[:-1] | (condition_right < right)))) if retain_unknown_ranges else ())141 facts = ConditionalIntegralFacts(spec, actual, value_duration, known_duration, selected_duration,142 selected_unknown_duration, false_duration, sample_count, merge_ranges(value_unknown),143 merge_ranges(condition_unknown), quality, series.unit, series.binding_identity, series.source_refs,144 ranges_retained=retain_unknown_ranges)145 # 返回只读列供独立原序float归约;输出未申请独立owner,不能脱离已预约workspace留存。146 areas.setflags(write=False)147 keep.setflags(write=False)148 return NumpyConditionalIntegralTerms(areas, keep, facts)149