Chronaxis用户手册English
本页目录

chronaxis.extensions

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""由宿主Python程序显式声明可信扩展,纯配置只引用其已注册名称。23逐点函数声明固定输入输出端口;样本归约与网格变换分别使用具有生命周期4的工厂。注册表冻结名称、版本、类型、单位和后端能力,以稳定摘要标识协议。5本模块不发现模块、不读取配置中的文件或导入路径,也不在注册时创建运行状态。6"""78from contextlib import contextmanager9from contextvars import ContextVar10from dataclasses import dataclass11from hashlib import sha25612import json13from types import MappingProxyType1415from .core.types import SignalDType as D16from .core.unit_registry import resolve_unit17from .core.validation import require_identifier, require_text18from .core.function_contracts import builtin_function_specs192021_BUDGET = ContextVar('chronaxis_host_extension_budget',default=None)22_BUILTINS = frozenset(builtin_function_specs()) | frozenset("""23duration integral select legacy_sum sample_mean24sample_minimum sample_maximum min_time max_time time_weighted_mean sample_count point_event_count25activity_count segment_count sample_variance sample_stddev sample_rms sample_median sample_quantile26time_variance time_stddev time_rms rolling_time_mean rolling_minimum rolling_maximum rolling_integral27adjacent_rate window_rate sample_fit_slope28condition_duration rising_edge falling_edge start_only window_delta debounce hysteresis start_end29""".split())303132@dataclass(frozen=True)33class ValueSpec:34    """一个固定类型与数值尺度的端口。BOOL使用单位1,枚举代码需在DSL中显式取出。"""3536    dtype: D37    unit: str3839    def __post_init__(self):40        """核对宿主声明的类型与尺度。参数: 无。返回: None。异常: ValueError为非法端口。"""41        if not isinstance(self.dtype,D) or self.dtype is D.ENUM or resolve_unit(self.unit) is None:42            raise ValueError('扩展端口须声明数值或BOOL类型及已登记单位')43        if self.dtype is D.BOOL and self.unit != '1':44            raise ValueError('扩展BOOL端口的单位必须为1')454647class _BackendSpec:48    """三类协议共享名称/版本与显式能力验证,不共享数值或状态回调。"""4950    def require_backend(self, backend):51        """取得请求后端自身的实现。参数: backend为reference或numpy。返回: 回调。异常: 缺实现报ValueError。"""52        if backend not in ('reference','numpy'):53            raise ValueError('扩展后端须为reference或numpy')54        implementation = self.reference if backend == 'reference' else self.numpy55        if implementation is None:56            raise ValueError(f'扩展 {self.name} {self.version} 不支持后端 {backend}')57        return implementation5859    def _check(self, inputs):60        """校验名称、版本、端口与声明的具体后端。参数: 固定输入tuple。返回: None。异常: 非法描述报错。"""61        require_identifier(self.name,'extension.name')62        require_text(self.version,'extension.version')63        if self.name in _BUILTINS:64            raise ValueError(f'扩展名称与内置能力冲突:{self.name}')65        if type(inputs) is not tuple or not inputs or any(type(p) is not ValueSpec for p in (*inputs,self.output)):66            raise TypeError('扩展需要非空固定输入及明确的ValueSpec输出')67        if self.reference is None and self.numpy is None or any(68                f is not None and not callable(f) for f in (self.reference,self.numpy)):69            raise ValueError('扩展至少需要一个可调用的后端实现')707172@dataclass(frozen=True)73class FunctionSpec(_BackendSpec):74    """纯逐点函数,impl(args,resources)返回同一形状的ScalarValue或NumericArray。7576    固定位置参数逐一按端口单位及dtype编译,任何必要输入无效都传递到结果;77    无历史,真实输入更新沿用普通表达式政策。78    """79    name: str80    version: str81    inputs: tuple[ValueSpec,...]82    output: ValueSpec83    reference: object = None84    numpy: object = None8586    def __post_init__(self):87        """参数: 无。返回: None。异常: 声明或实现不合法时拒绝注册。"""88        self._check(self.inputs)899091@dataclass(frozen=True)92class ReductionSpec(_BackendSpec):93    """标量样本归约协议;factory(resources)建立每范围独立的状态。9495    consume接收已选真实样本,finalize返回ScalarValue,close退还外部资源;96    mergeable=True时还必须提供merge(other)。cancel和close均为必需方法,分别处理取消及资源释放。97    """98    name: str99    version: str100    input: ValueSpec101    output: ValueSpec102    reference: object = None103    numpy: object = None104    state_bytes: int = 0105    mergeable: bool = False106107    def __post_init__(self):108        """参数: 无。返回: None。异常: 资源/合并能力或端口不合规时拒绝。"""109        self._check((self.input,))110        if type(self.state_bytes) is not int or self.state_bytes < 0 or type(self.mergeable) is not bool:111            raise ValueError('归约需要非负state_bytes和布尔mergeable')112        if self.input.dtype is D.BOOL or self.output.dtype is D.BOOL:113            raise ValueError('标量样本归约需要数值输入和输出')114115116@dataclass(frozen=True)117class TransformSpec(_BackendSpec):118    """有状态网格变换;factory(resources)为本次会话创建状态。119120    advance(time_ns,window_ns,values,weights_ns)仅接收本窗口有效的ZOH区间,121    返回实际网格时刻的ScalarValue。history_ns限制回看窗口的实际或周期122    计量宽度,interval_modes声明该实现能正确使用的时间权重。123    """124    name: str125    version: str126    input: ValueSpec127    output: ValueSpec128    reference: object = None129    numpy: object = None130    state_bytes: int = 0131    history_ns: int = 0132    interval_modes: tuple[str,...] = ('actual','fixed','dbc')133134    def __post_init__(self):135        """参数: 无。返回: None。异常: 非数值端口、非正历史或非法资源费时拒绝。"""136        self._check((self.input,))137        if (type(self.state_bytes) is not int or self.state_bytes < 0 or type(self.history_ns) is not int138                or not 0 < self.history_ns < 1 << 63 or self.input.dtype is D.BOOL or self.output.dtype is D.BOOL):139            raise ValueError('网格扩展需要数值端口、非负state_bytes及正int64历史时长')140        if (type(self.interval_modes) is not tuple or not self.interval_modes141                or any(m not in ('actual','fixed','dbc') for m in self.interval_modes)142                or len(set(self.interval_modes)) != len(self.interval_modes)):143            raise ValueError('时序扩展的interval_modes须明确列出互不重复的actual、fixed或dbc')144145146@dataclass(frozen=True,eq=False,init=False)147class ExtensionRegistry:148    """不可变的宿主能力集合;同次运行必须提供与编译时相同的注册对象。"""149    functions: object150    reductions: object151    transforms: object152    identity: str153154    def __init__(self,functions=(),reductions=(),transforms=()):155        """登记三种互斥的typed描述。参数: 三类描述符tuple。返回: 新注册表。异常: 重名或模型错误。"""156        records, names = [], set()157        for kind,entries,typ in (('functions',functions,FunctionSpec),('reductions',reductions,ReductionSpec),158                                 ('transforms',transforms,TransformSpec)):159            if type(entries) is not tuple or any(type(spec) is not typ for spec in entries):160                raise TypeError(f'{kind}需要对应描述符的tuple')161            by_name = {}162            for spec in entries:163                if spec.name in names:164                    raise ValueError(f'重复的扩展名称:{spec.name}')165                by_name[spec.name] = spec166                names.add(spec.name)167                ports = spec.inputs if kind == 'functions' else (spec.input,)168                records.append((kind,spec.name,spec.version,[(p.dtype.value,p.unit) for p in ports],169                                (spec.output.dtype.value,spec.output.unit),spec.reference is not None,170                                spec.numpy is not None,getattr(spec,'state_bytes',0),getattr(spec,'mergeable',False),171                                getattr(spec,'history_ns',0),sorted(getattr(spec,'interval_modes',()))))172            object.__setattr__(self,kind,MappingProxyType(by_name))173        # 身份取声明和实现版本,跨进程不使用函数地址;换实现的宿主须递增版本。174        payload = json.dumps(['chronaxis-host-v1',sorted(records)],ensure_ascii=False,separators=(',',':'))175        object.__setattr__(self,'identity',sha256(payload.encode('utf-8')).hexdigest())176177178@contextmanager179def using_extension_budget(budget):180    """让当前调用栈的逐点扩展使用实际运行账本,嵌套及异常后恢复原上下文。181182    参数: budget为本次ResourceBudget。返回: 上下文管理器。异常: 回调/执行异常原样传播。183    """184    token = _BUDGET.set(budget)185    try:186        yield187    finally:188        _BUDGET.reset(token)189
Chronaxis 用户手册 · 离线阅读