Chronaxis用户手册English
本页目录

chronaxis.core.quantiles

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""保存精确样本分位数的有限预算状态,结束时定位两个顺序统计值。23q为0或1时只维护原dtype的极值。其余q保留被选中的真实样本,增长前向4本次预算付款,排序临时空间另行预付并释放。标量及数组后端各自完成排序5和线性插值;本对象只共享寿命、秩、预算协议,不读取来源或生成临时文件。6"""78from fractions import Fraction9from math import isfinite10from .types import QualityFlag as Q, SignalDType as D111213SAMPLE_QUANTILES = ('sample_quantile', 'sample_median')141516def quantile_probability(operation, q):17    """校验操作与概率的固定组合,返回精确的配置概率。1819    参数: operation为sample_quantile/sample_median,q为显式数值或None。20    返回: Fraction;中位数固定为1/2,其他概率按配置值的十进制表示解释。21    异常: ValueError为多余、缺失、非有限、越界或布尔概率。22    """23    if operation == 'sample_median':24        if q is not None:25            raise ValueError('sample_median固定使用q=0.5,请省略q')26        return Fraction(1, 2)27    if (operation != 'sample_quantile' or type(q) not in (int, float) or not 0 <= q <= 128            or (type(q) is float and not isfinite(q))):29        raise ValueError('sample_quantile需要有限数值q,范围为0到1')30    return Fraction(str(q))313233class ExactQuantileBuffer:34    """保存单个统计范围的已选样本或端点极值;只持有自己预付的空间。"""3536    def __init__(self, operation, q, dtype, backend, budget=None):37        """配置确定的概率、输入dtype和真实后端,创建空存储。3839        参数: operation/q见概率检查,dtype为数值声明,backend为reference或numpy,40            budget为共享ResourceBudget;独立调用省略时使用项目默认配额。41        返回: 新缓冲。异常: 非法参数在读取样本前失败。42        """43        from ..results.budget import ResourceBudget44        if backend not in ('reference','numpy') or dtype not in (D.INT64,D.UINT64,D.FLOAT64):45            raise ValueError('精确分位数需要数值dtype及已支持后端')46        self._q = quantile_probability(operation, q)47        self._dtype, self._backend = dtype, backend48        self._budget = ResourceBudget() if budget is None else budget49        self._held_cost = self._buffer_cost = self._sort_cost = self._size = 050        self._chunks = [[]] if backend == 'reference' else []51        self._extreme = None52        self._failed = self._closed = self._closing = False5354    def _reserve(self, amount, slot):55        """在保存样本或创建排序副本之前申请空间,保留具体预算错误。5657        参数: amount为本次额外字节,slot为持有本笔费用的字段。58        返回: None。异常: BudgetExceededError或取消原样传播,close可重试清理。59        """60        from ..results.budget import _reserve_owned_ram61        from ..results.errors import BudgetExceededError62        try:63            _reserve_owned_ram(self, amount, slot)64        except BudgetExceededError as error:65            error.add_note('精确样本分位数的样本存储或排序空间超过可用RAM')66            raise6768    def _append_memory_bytes(self, count):69        """给分块调用者计算下一批保留样本的新增费用,不提前占用预算。7071        参数: count为内部已确定的非负样本数,也可为本批可能选中数量的上界。72        返回: 新增字节数;空批和只保留极值的q端点为0。73        异常: 无;实际追加仍通过_reserve检查预算及取消。74        """75        # 与append使用同一个费用来源,避免分块预估与真正付款不一致。76        if not count or self._q in (0, 1):77            return 078        if self._backend == 'reference':79            return (0 if self._size else 256) + count * 6480        return 256 + count * 88182    def append(self, values, mask=None):83        """累加一批真正选中的有效样本;空批直接返回。8485        参数: reference的values为已选原生数值序列;numpy的values为原dtype数组,86            mask为同长布尔选择列。输入所有权属于调用方。87        返回: None。异常: 值/掩码不符、预算不足、关闭后调用或分配失败。88        """89        if self._failed or self._closed or self._closing:90            from ..results.errors import ResultClosedError91            raise ResultClosedError('精确分位数缓冲已关闭或失败')92        try:93            if self._backend == 'reference':94                if mask is not None:95                    raise ValueError('标量分位数仅接已选样本')96                count = len(values)97            else:98                import numpy as np99                if mask is None or mask.dtype != np.bool_ or values.shape != mask.shape:100                    raise ValueError('数组分位数需要同长布尔掩码')101                count = int(np.count_nonzero(mask))102            if not count:103                return104            if self._q in (0, 1):105                # 端点不需要历史。where+原dtype上限/下限使数组极值不用复制所选列。106                if self._backend == 'reference':107                    candidate = (min if self._q == 0 else max)(values)108                else:109                    from .numeric import INTEGER_BOUNDS110                    lower, upper = INTEGER_BOUNDS.get(self._dtype, (-float('inf'), float('inf')))111                    candidate = (np.min(values, where=mask, initial=upper) if self._q == 0 else112                                 np.max(values, where=mask, initial=lower)).item()113                choose = min if self._q == 0 else max114                self._extreme = candidate if self._extreme is None else choose(self._extreme,candidate)115            else:116                # 预付块头/索引槽、标量对象保留费或数组nbytes,然后取得独占副本。117                if self._backend == 'reference':118                    self._reserve(self._append_memory_bytes(count), '_buffer_cost')119                    self._chunks[0].extend(values)120                else:121                    self._reserve(self._append_memory_bytes(count), '_buffer_cost')122                    self._chunks.append(values[mask])123            self._size += count124        except BaseException:125            self._failed = True126            raise127128    def finish(self):129        """读取截至当前批次的分位数快照,不按块分别求分位数再合并。130131        参数: 无。返回: (原生数值或None,质量)。空集合返回None及NONE,最终132            空/部分状态由具有完整观测范围的计算器判定。133        异常: 已关闭/失败时拒绝,排序额度不足或分配失败原样传播。134        """135        from ..results.budget import _release_owned_ram136        if self._failed or self._closed or self._closing:137            from ..results.errors import ResultClosedError138            raise ResultClosedError('精确分位数缓冲已关闭或失败')139        if not self._size or self._q in (0, 1):140            return self._extreme, Q.NONE141        if self._size == 1:142            value = self._chunks[0][0]143            return (value if self._backend == 'reference' else value.item()), Q.NONE144        failure = None145        try:146            # Python排序预付指针列表及排序缓冲。NumPy单一连续副本原地堆排序;147            # 固定项还覆盖列表/数组头与两个端点精确插值所需的临时数值。148            workspace = (32 * self._size if self._backend == 'reference' else149                         8 * self._size + 16 * len(self._chunks))150            self._reserve(8192 + workspace, '_sort_cost')151            rank = (self._size - 1) * self._q152            index, remainder = divmod(rank.numerator, rank.denominator)153            if self._backend == 'reference':154                from .quantile_reference import ordered_reference155                return ordered_reference(self._chunks,index,remainder,rank.denominator)156            from .quantile_numpy import ordered_numpy157            return ordered_numpy(self._chunks,index,remainder,rank.denominator)158        except BaseException as error:159            failure = error160            self._failed = True161            raise162        finally:163            try:164                _release_owned_ram(self,self._sort_cost,'_sort_cost')165            except BaseException as cleanup:166                # 退款失败不能遮住先发生的排序/申请错误;实际费用槽由预算助手更新。167                self._failed = True168                original = cleanup if failure is None else failure169                if self._held_cost:170                    original.quantile_cleanup_owner = self171                if failure is None:172                    raise173                failure.add_note('分位数排序空间释放失败: ' + type(cleanup).__name__)174175    def close(self):176        """放弃样本并退回本实例的全部收费,释放被取消时可再次调用。177178        参数: 无。返回: None。异常: 原预算释放异常传播,不改变其他owner的费用。179        """180        from ..results.budget import _release_owned_ram181        if self._closed:182            return183        self._closing = True184        self._chunks.clear()185        self._extreme = None186        try:187            _release_owned_ram(self,self._sort_cost,'_sort_cost')188            _release_owned_ram(self,self._buffer_cost,'_buffer_cost')189        finally:190            self._closed = self._held_cost == 0191192193def compute_quantile_series(series, spec, sample_kernel, budget):194    """把有时间轴的单块内存输入送入对应后端的精确样本归约器。195196    参数: series为持有值、真实更新及覆盖;spec为quantile/median;sample_kernel197        为该后端的归约器类;budget为空时使用默认配额。198    返回: 包含观测范围、诊断及精确秩结果的CalculationResult。199    异常: 配置、预算或计算器失败传播;正常/异常退出均释放临时所有权。200    """201    from .calculations import make_calculation_result202    from ..results.budget import _cleanup_owner203    reducer = sample_kernel(spec,series.unit,series.values[0].dtype,quantile_budget=budget)204    try:205        facts = reducer.push(series)206        number = reducer.finish()207        actual = facts.actual_window208        span = 0 if actual is None else actual.end_ns - actual.start_ns209        result = make_calculation_result(spec, series, number.value, number.unit, actual,210            facts.value_valid_duration_ns, facts.condition_known_duration_ns, facts.selected_duration_ns,211            facts.selected_value_unknown_duration_ns, facts.valid_sample_count, facts.value_unknown_ranges,212            facts.condition_unknown_ranges, facts.quality | number.quality,213            all_false=actual == spec.window and span > 0 and facts.false_duration_ns == span,214            calculation_failed=number.calculation_failed)215        # 快照已独立,不把排序缓冲带出低层便捷入口。216        reducer.close()217        return result218    except BaseException as error:219        _cleanup_owner(reducer._quantile,error,'quantile_cleanup_owner','内存分位数')220        raise221
Chronaxis 用户手册 · 离线阅读