Chronaxis用户手册English
本页目录

chronaxis.legacy.sum

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""在预算内按原求值顺序累计调用方已经算好的旧口径贡献。23输入为LegacySumContribution的有限精确tuple,每项保留LegacyDecodedUpdate4身份并显式选择有限float贡献或跳过原因。输出为未舍入的冻结快照;本层不求5表达式、不再乘period/factor/dt,不把原quality推断成TTL、缓存或跳过政策。6legacy_policy必须由提供者明确填写,不证明完整复现ChrgInfo的旧行为。78累计器只保存一个来源的最后顺序、总数、计数及四种原因计数。每批先付新状态9和临时列费用,再独立核顺序及数值,本批另付的临时费用实际释放后才一次提交候选状态。10内部历史AnalysisRunner可明确预付一条的工作空间;本owner保持这笔费用直到close,11单条计算复用它,多条仅另付不足部分,不能借共享账本中其他owner的余额抵账。12参考路径逐项使用Python浮点加法;NumPy路径按需加载独立数组核,不共享参考13数值计算。输入及返回快照归调用方,本层估算不包含其已有内存或整个进程RSS。14本模块不读写文件、不调用旧reader/解码器;close只释放本owner的实际RAM槽。15"""1617# 冻结模型及候选复制只依赖标准库,数组依赖仅在明确选择且有贡献时加载。18from collections.abc import Mapping19from copy import copy20from dataclasses import dataclass21from enum import Enum22from math import isfinite23from types import MappingProxyType2425from ..core.validation import require_identifier, require_text26from ..results.budget import ResourceBudget, _cleanup_owner, _release_owned_ram, _reserve_owned_ram27from ..results.errors import ResultClosedError28from .updates import LegacyDecodedUpdate, LegacyUpdateOrder293031class LegacySkipReason(Enum):32    """提供者明确选择的一次跳过原因,不由累计器根据原值或质量推断。3334    四个固定成员覆盖输入缺项、条件不成立、求值错误和非有限输入。35    枚举无可变状态;原因仅解释该提供者为何未给出有限贡献,不补造旧缓存事实。36    """3738    MISSING_INPUT = "missing_input"39    CONDITION_FALSE = "condition_false"40    EVALUATION_ERROR = "evaluation_error"41    NONFINITE_INPUT = "nonfinite_input"424344# 原因域固定,内部计数tuple和公开冻结映射都保持此声明顺序及全部零项。45_SKIP_REASONS = tuple(LegacySkipReason)464748def _require_contribution(value: float | None, skip_reason: LegacySkipReason | None) -> None:49    """检查有限原生贡献与显式跳过原因互斥,不转换或修补数值。5051    参数: value为原生有限float或None;skip_reason为固定枚举或None。52    返回: None;合法数值和显式跳过均保持原样。53    异常: TypeError为类型不符;ValueError为非有限数值或贡献/原因关系矛盾。54    """55    # 先限制明确的原因域;None贡献只能表示提供者已选择跳过,不能省略原因。56    if skip_reason is not None and not isinstance(skip_reason, LegacySkipReason):57        raise TypeError("skip_reason必须为LegacySkipReason或None")58    if value is None:59        if skip_reason is None:60            raise ValueError("无贡献时必须显式提供skip_reason")61        return62    # int和bool即使可转float也不能隐藏提供者采用的数值政策。63    if type(value) is not float:64        raise TypeError("贡献必须为原生float或None")65    if not isfinite(value):66        raise ValueError("贡献必须为有限float;非有限输入应由提供者显式跳过")67    if skip_reason is not None:68        raise ValueError("有限贡献不能同时携带skip_reason")697071@dataclass(frozen=True)72class LegacySumContribution:73    """关联一次更新身份与提供者已求出的贡献,不保存新的信号缓存。7475    update保留原模型对象;value是有限原生float,或与明确skip_reason配对的76    None。即使update.quality非零,有限贡献也不由本层自动否决。该对象由调用方77    提供,不证明旧表达式、触发门控、缺失处理或真实日志逐次出口已经接通。78    """7980    update: LegacyDecodedUpdate  # 此次求值的原身份,引用保持不变。81    value: float | None  # 已计算一次的贡献,不再施加时间或单位倍率。82    skip_reason: LegacySkipReason | None = None  # 仅None贡献需要明确原因。8384    def __post_init__(self) -> None:85        """验证真实更新模型及互斥的贡献/跳过结构。8687        参数: 无,读取update、value和skip_reason。88        返回: None;不复制update或修改其原值、质量。89        异常: TypeError为非模型或数值/原因类型不符;ValueError为结构或有限性错误。90        """91        # 保留已验证的原更新引用,再独立核对贡献结构,不重造或改写求值身份。92        if not isinstance(self.update, LegacyDecodedUpdate):93            raise TypeError("update必须为LegacyDecodedUpdate")94        _require_contribution(self.value, self.skip_reason)959697@dataclass(frozen=True)98class LegacySumSnapshot:99    """保存固定大小的未舍入累计事实,明确区分没有贡献与有效零和。100101    evaluation_count包括跳过,contribution_count只数实际加法项;四键102    skip_counts复制冻结且始终含零项。没有求值和全部跳过分别以reason说明,103    只有曾接收有效贡献才给float总数。快照归调用方,可在累计器关闭后保留;104    不保存逐次列表,也不声称普通时间积分的覆盖率或旧整文件行为全等。105    """106107    session_id: str  # 原会话身份。108    source: str  # 单一来源身份。109    legacy_policy: str  # 提供者显式声明的贡献及跳过政策文本。110    value: float | None  # 未舍入总数;没有有效贡献时为None。111    evaluation_count: int  # 所有已接受求值,包括跳过。112    contribution_count: int  # 实际有限贡献数量,包括值为0的项。113    skipped_count: int  # 显式跳过的求值数量。114    skip_counts: Mapping[LegacySkipReason, int]  # 固定四键的独立冻结映射。115    reason: str | None  # no_evaluations/all_skipped;有贡献时为None。116117    def __post_init__(self) -> None:118        """核对有限计数关系与空结果原因,并冻结固定四键映射。119120        参数: 无,读取所有快照字段。121        返回: None;skip_counts转换成新的MappingProxyType,其他事实不改写。122        异常: TypeError为字段类型不符;ValueError为计数、原因域或结果关系矛盾;123            MemoryError为固定小映射分配失败,不返回部分快照。124        """125        # 身份和政策仍须明确,合法的数值不能掩盖缺失的来源或政策声明。126        require_identifier(self.session_id, "session_id")127        require_identifier(self.source, "source")128        require_text(self.legacy_policy, "legacy_policy")129        # 三种计数分别非负,且求值数必须由贡献和跳过完整闭合,不能遗漏已接受项。130        for count in (self.evaluation_count, self.contribution_count, self.skipped_count):131            if type(count) is not int:132                raise TypeError("快照计数必须为原生非布尔int")133            if count < 0:134                raise ValueError("快照计数必须非负")135        if self.evaluation_count != self.contribution_count + self.skipped_count:136            raise ValueError("求值数必须等于贡献数加跳过数")137        # 固定键域不能由可变Mapping实现注入额外原因,仍独立复制调用方dict。138        if type(self.skip_counts) not in (dict, MappingProxyType):139            raise TypeError("skip_counts需要dict或MappingProxyType")140        if set(self.skip_counts) != set(_SKIP_REASONS):141            raise ValueError("skip_counts必须恰好包含四个LegacySkipReason")142        copied = {}143        for reason in _SKIP_REASONS:144            count = self.skip_counts[reason]145            if type(count) is not int:146                raise TypeError("跳过原因计数必须为原生非布尔int")147            if count < 0:148                raise ValueError("跳过原因计数必须非负")149            copied[reason] = count150        if sum(copied.values()) != self.skipped_count:151            raise ValueError("四种原因计数之和必须等于skipped_count")152        # 用实际贡献数区分未求值、全跳过与有贡献,不用总数的真假判断是否为空。153        expected_reason = None if self.contribution_count else (154            "no_evaluations" if self.evaluation_count == 0 else "all_skipped"155        )156        if self.reason != expected_reason:157            raise ValueError("快照reason与实际贡献数量不一致")158        # 有贡献时零和也是有限结果;没有贡献时必须保留None,不能补成正常0。159        if self.contribution_count:160            if type(self.value) is not float:161                raise TypeError("有贡献的快照需要原生float总数")162            if not isfinite(self.value):163                raise ValueError("快照总数必须有限")164        elif self.value is not None:165            raise ValueError("没有有效贡献时value必须为None")166        object.__setattr__(self, "skip_counts", MappingProxyType(copied))167168169def _sum_reference(carry: float, values: tuple[float, ...]) -> float:170    """以独立Python浮点逐项左折叠,发现任一中间溢出立即拒绝本批。171172    参数: carry是此前有限总数;values是按原求值顺序选出的有限float tuple。173    返回: float;无舍入、补偿求和或块和重排的候选总数。174    异常: OverflowError为任一中间累计非有限;取消及内存异常原样传播。175    """176    candidate = carry177    try:178        # 每次加法承接前一项的真实浮点尾数;逐中间值检查,禁止靠后续抵消修复溢出。179        for value in values:180            candidate = candidate + value181            if not isfinite(candidate):182                raise OverflowError("legacy_sum中间累计非有限,整批拒绝")183        return candidate184    finally:185        # 异常traceback可能由调用方长期保留;不得经纯核参数保活本层选值tuple。186        values = None187        candidate = None188189190class LegacySumAccumulator:191    """拥有单来源有限累计状态和实际预算槽,不拥有调用方更新或共享账本。192193    _state一次保存顺序器、总数、贡献数及四原因计数。候选顺序器用copy.copy194    复制,共享的旧状态tuple不可变;包括skip的全部记录仍检查会话和原序。195    _fixed_cost覆盖本层有限元数据及候选重叠;_workspace_cost可预付单条工作区,196    _batch_cost只覆盖本批超过预付容量的临时费用。三者之和为_held_cost。197    费用清理未完成时拒绝新消费,close可按同owner重试。198    """199200    def __init__(self, budget: ResourceBudget, session_id: str, source: str, *,201                 legacy_policy: str, backend: str = "numpy", max_batch_rows: int = 64,202                 prepay_single: bool = False):203        """先安装责任槽、校验配置并预付费用,再建立空顺序及累计状态。204205        参数: budget为借用共享账本;session_id/source为固定身份;legacy_policy206            是必填非空政策;backend为reference或numpy;max_batch_rows为正原生int;207            prepay_single为原生bool,True时本owner构造预付4352字节单条工作区,208            供内部历史AnalysisRunner在共享余额用尽时继续已有单条计算,不缓存贡献;209            默认False保原批行为,多条调用仍须先预约实际所需空间的不足部分。210        返回: 新累计器;初始无求值、无贡献,不把初始加法carry当正常结果。211        异常: TypeError/ValueError为配置不符;预算及取消原错传播。构造清理持续212            失败时,原异常的legacy_sum_cleanup_owner保留真实未返回owner。213        """214        if not isinstance(budget, ResourceBudget):215            raise TypeError("budget必须为ResourceBudget")216        # 首次外部预算回调前安装全部实际费用和关闭槽,避免未返回对象失去责任。217        self._budget = budget218        self._held_cost = self._fixed_cost = self._workspace_cost = self._batch_cost = 0219        self._closed = self._closing = False220        self._state = None221        self._session_id = self._source = self._legacy_policy = None222        self._backend = None223        self._max_batch_rows = 0224        try:225            # 先检查调用配置,避免无效来源、政策或块上限进入已付费的累计状态。226            require_identifier(session_id, "session_id")227            require_identifier(source, "source")228            require_text(legacy_policy, "legacy_policy")229            if type(backend) is not str or backend not in ("reference", "numpy"):230                raise ValueError("backend仅支持reference或numpy")231            if type(max_batch_rows) is not int:232                raise TypeError("max_batch_rows必须为正原生int")233            if max_batch_rows <= 0:234                raise ValueError("max_batch_rows必须为正原生int")235            if type(prepay_single) is not bool:236                raise TypeError("prepay_single必须为原生bool")237            # 文本计费按最多UTF-32字符及候选/快照引用余量,固定开销覆盖有限状态。238            cost = 16384 + 8 * (len(session_id) + len(source) + len(legacy_policy))239            _reserve_owned_ram(self, cost, "_fixed_cost")240            # 预约前已安装独立责任槽,预约成功后取消也由本未返回owner清理。241            # 4352沿用单条原公式4096+256,不减原估算,也不替其他owner付款。242            if prepay_single:243                _reserve_owned_ram(self, 4352, "_workspace_cost")244            self._session_id, self._source, self._legacy_policy = session_id, source, legacy_policy245            self._backend, self._max_batch_rows = backend, max_batch_rows246            self._state = (LegacyUpdateOrder(session_id, source), 0.0, 0, (0, 0, 0, 0))247        except BaseException as exc:248            _cleanup_owner(self, exc, "legacy_sum_cleanup_owner", "legacy累计构造")249            raise250251    @property252    def closed(self) -> bool:253        """读取完全关闭事实;参数: 无;返回: bool;异常: 无。"""254        return self._closed255256    def _ensure_open(self) -> None:257        """拒绝关闭或有待恢复费用的owner,防止新批覆盖旧责任槽。258259        参数: 无。返回: None。异常: ResultClosedError为已关闭或正在清理。260        """261        if self._closed or self._closing:262            raise ResultClosedError("legacy累计器已关闭或正在清理")263264    def _candidate(self, contributions: tuple[LegacySumContribution, ...]) -> tuple:265        """在已预付空间内生成独立候选,返回时不保留逐次值临时列。266267        参数: contributions为本批借用tuple,长度已经检查。268        返回: tuple;新顺序器、候选总数、贡献数和四原因计数。269        异常: TypeError/ValueError为贡献或顺序错误;OverflowError为中间累计溢出;270            数组导入、分配或取消原样传播,原_state始终不被修改。271        """272        candidate_order = skips = selected = values = None273        try:274            # 只复制有限顺序状态,后续accept只改候选,保留整批失败时的原顺序事实。275            order, carry, contribution_count, previous_skips = self._state276            candidate_order = copy(order)277            skips = list(previous_skips)278            selected = []279            # 每项先核模型再推进候选,后续坏项不能让此前部分求值提前提交。280            for contribution in contributions:281                if not isinstance(contribution, LegacySumContribution):282                    raise TypeError("consume仅接受LegacySumContribution")283                # 即使整批全部跳过,原更新身份和求值顺序也必须完整推进及核对。284                candidate_order.accept(contribution.update)285                if contribution.value is None:286                    skips[_SKIP_REASONS.index(contribution.skip_reason)] += 1287                else:288                    selected.append(contribution.value)289            # 不添加跳过占位0;全skip保持原carry,尚无贡献时快照仍由计数判为None。290            if selected:291                values = tuple(selected)292                if self._backend == "reference":293                    carry = _sum_reference(carry, values)294                else:295                    from .sum_numpy import sum_numpy296                    carry = sum_numpy(carry, values)297            return candidate_order, carry, contribution_count + len(selected), tuple(skips)298        finally:299            # 先断开本层新建的候选、选值和原因容器;保留原输入和异常本身。300            # 否则失败traceback会在批费用释放后继续保活这些临时副本。301            candidate_order = skips = selected = values = None302303    def _release_failed_batch(self, primary: BaseException) -> None:304        """有限重试本批实际费用,清理副错只附注原异常,不关闭正常累计状态。305306        参数: primary为原校验、计算、预算或取消异常。307        返回: None;持续未释费用设置_closing,禁止覆盖该责任槽。308        异常: 清理错误只记notes;真正剩余费用由原异常携带的owner.close恢复。309        """310        # 至多重试两次已登记费用,不无限阻塞原错;剩余责任留给显式close恢复。311        for attempt in range(2):312            if not self._batch_cost:313                break314            try:315                _release_owned_ram(self, self._batch_cost, "_batch_cost")316            except BaseException as cleanup:317                primary.add_note(f"legacy批费用清理第{attempt + 1}次失败: {type(cleanup).__name__}")318        if self._batch_cost:319            self._closing = True320321    def consume(self, contributions: tuple[LegacySumContribution, ...]) -> None:322        """整批核验并累计,临时差额实际释放成功后才一次安装全部候选事实。323324        参数: contributions为精确tuple,长度不超过max_batch_rows;空tuple合法。325            已预付单条空间时仅预约超过该自有容量的差额;默认仍预约完整本批费用。326        返回: None;成功提交全部项,任何失败不推进原数值、计数或顺序。327        异常: TypeError/ValueError为类型、批上限、来源或顺序;OverflowError为累计328            非有限;ResultClosedError为关闭中。预算、导入、分配或取消原样传播并329            附legacy_sum_cleanup_owner。清理成功后普通失败可用合法后续批恢复。330        """331        self._ensure_open()332        candidate = None333        try:334            # 先限定真实有限容器和批上限;空批不建立候选,也不申请无用工作区。335            if type(contributions) is not tuple:336                raise TypeError("consume需要精确tuple")337            if len(contributions) > self._max_batch_rows:338                raise ValueError("本批超过max_batch_rows")339            if not contributions:340                return341            # 每项256覆盖选值list/tuple、seed/累计/有限性数组及验证临时量;固定342            # 4096覆盖顺序器和四计数候选。输入update.values不复制也不深遍历计费。343            extra = max(0, 4096 + len(contributions) * 256 - self._workspace_cost)344            if extra:345                _reserve_owned_ram(self, extra, "_batch_cost")346            candidate = self._candidate(contributions)347            # _candidate已退出,所有逐次临时列已丢弃;这里只保固定候选与旧状态重叠。348            _release_owned_ram(self, self._batch_cost, "_batch_cost")349        except BaseException as exc:350            # 释放预算前丢弃尚未提交的固定候选,异常frame不能继续替它保留引用。351            candidate = None352            self._release_failed_batch(exc)353            exc.legacy_sum_cleanup_owner = self354            raise355        # 提交前不再调用预算或数值代码;候选分配与临时费用收尾均已完成。356        self._state = candidate357358    def snapshot(self) -> LegacySumSnapshot:359        """返回调用方拥有的独立冻结快照,不舍入、不封口或证明来源EOF。360361        参数: 无。返回: LegacySumSnapshot;四原因键始终存在,计数为0也不省略。362        异常: ResultClosedError为关闭中;分配或取消原样传播并附真实恢复owner。363        """364        self._ensure_open()365        try:366            order, total, contribution_count, skips = self._state367            # 固定原因域每次都含全部零项,返回的小映射独立于内部计数tuple。368            counts = dict(zip(_SKIP_REASONS, skips))369            # 空结果按求值及贡献数分类,已有累计不会被后来全跳过的批次清空。370            reason = None if contribution_count else (371                "no_evaluations" if order.count == 0 else "all_skipped"372            )373            return LegacySumSnapshot(374                self._session_id, self._source, self._legacy_policy,375                total if contribution_count else None, order.count, contribution_count,376                order.count - contribution_count, counts, reason,377            )378        except BaseException as exc:379            exc.legacy_sum_cleanup_owner = self380            raise381382    def close(self) -> None:383        """释放本累计器实际费用,可在释放前后取消或持续失败后重试同一owner。384385        参数: 无。返回: None;只有所有自有槽实际归零才closed=True,重复close无操作。386        异常: 原预算或取消异常保持,附legacy_sum_cleanup_owner;半关闭时拒绝继续387            consume/snapshot,不释放调用方借款,不使已返回快照失效。388        """389        # 只有完全关闭才幂等返回,避免重复close再次触发预算回调或抵销借款。390        if self._closed:391            return392        self._closing = True393        # 本层无FD或子owner;先丢自有状态引用,释放后取消也不留下未计费的新状态。394        self._state = None395        try:396            _release_owned_ram(self, self._batch_cost, "_batch_cost")397            _release_owned_ram(self, self._workspace_cost, "_workspace_cost")398            _release_owned_ram(self, self._fixed_cost, "_fixed_cost")399        except BaseException as exc:400            exc.legacy_sum_cleanup_owner = self401            raise402        self._session_id = self._source = self._legacy_policy = None403        self._closed = True404
Chronaxis 用户手册 · 离线阅读