chronaxis.execution.record_delta_condition
源码基线:8904c2f744951857659abd40aaa3535d57faf53c
1"""在当前窗口内保留必要数值片段,输出窗口变化量的三值条件。23实际模式在旧边界移出窗口时继续求值;固定/DBC只在选定驱动到达时移动窗口,4其他更新只处理失效。原实际坐标和TTL不改变,变化量仍交原独立两后端核计算。5"""6from collections import deque7from dataclasses import replace8from fractions import Fraction9from ..core.numeric import ScalarValue10from ..core.types import SignalDType as D,QualityFlag as Q,Truth as T11from ..core.window_delta import DeltaSpan12from ..core.window_delta_reference import window_delta_reference13from ..core.window_delta_numpy import window_delta_numpy14from ..results.errors import ResultClosedError15from ..results.spool import _reserve_owned_ram,_release_owned_ram,_cleanup_owner161718class RecordDeltaCondition:19 """拥有当前窗口片段和有限驱动时刻;不创建整段历史或对照时间轴。"""2021 def __init__(self,budget,spec,dtype,backend,step_ns):22 """预付有限状态,动态窗口按实际容量收费。参数: 预算/配置/数值类型/后端/周期。返回: owner。异常: 原构造错。"""23 self._budget,self._spec,self._dtype,self._step=budget,spec,dtype,step_ns24 self._held_cost=self._fixed_cost=self._span_cost=self._clock_cost=self._work_cost=025 self._spans,self._clocks=deque(),deque()26 self._held_number=self._held_right=None27 self._held_deadline=None28 self._point=self._right=ScalarValue(None,D.BOOL,False,Q.MISSING)29 self._last_time=None30 self.next_deadline=None31 self._closed=self._failed=False32 try:33 _reserve_owned_ram(self,65536,'_fixed_cost')34 self._kernel=window_delta_reference if backend=='reference' else window_delta_numpy35 except BaseException as error:36 _cleanup_owner(self,error,'record_delta_cleanup_owner','窗口变化条件构造')37 raise3839 @staticmethod40 def _condition(result):41 """转换既有数值核的三值结果,不把未知变False。参数: 结果。返回: ScalarValue。异常: 原类型错。"""42 valid=result.condition is not T.UNKNOWN43 return ScalarValue(result.condition is T.TRUE if valid else None,D.BOOL,valid,result.quality)4445 def _append(self,time_ns,point,right):46 """只保存值或质量真正变化的边界,查询末点临时构造。4748 参数: 实际时刻、点值/右侧值。返回: None。异常: 预算/时间回退。49 """50 value=point.value if point.valid else None51 pq=Q.NONE if point.valid else point.quality52 cq=Q.NONE if right.valid else right.quality53 if self._spans:54 last=self._spans[-1]55 if last.start_ns==time_ns:56 self._spans[-1]=DeltaSpan(time_ns,time_ns,value,cq,pq,right.partial,point.partial)57 return58 self._spans[-1]=replace(last,end_ns=time_ns)59 if last.value==value and last.quality==cq==pq and last.partial==right.partial==point.partial:60 return61 _reserve_owned_ram(self,2048,'_span_cost')62 self._spans.append(DeltaSpan(time_ns,time_ns,value,cq,pq,right.partial,point.partial))6364 def _left(self,time_ns):65 """只根据当前窗口需要的驱动记录确定左端。参数: 实际时刻。返回: 左端或不足时None。异常: 无。"""66 if self._step is None:67 return time_ns-self._spec.window_ns68 count=len(self._clocks)69 offset=Fraction(self._spec.window_ns,self._step)70 target=Fraction(count-1)-offset71 if target<0:72 return None73 index=target.numerator//target.denominator74 if target==index:75 return self._clocks[index]76 return round(Fraction(self._clocks[index])+(target-index)*(self._clocks[index+1]-self._clocks[index]))7778 def update(self,time_ns,point,right,expires_at_ns,*,driver=False):79 """推进真实值和当前窗口,返回点/右侧条件;不输出变化值副本。8081 参数: 实际时刻、求值点/右侧值、实际TTL截止及是否选定驱动。82 返回: 两个BOOL ScalarValue。异常: 倒序、预算或原数值错误;失败只能清理。83 """84 if self._closed or self._failed:85 raise ResultClosedError('窗口变化条件已关闭或失败')86 spans=point_result=right_result=None87 try:88 if self._last_time is not None and time_ns<self._last_time:89 raise ValueError('窗口变化时间回退')90 self._last_time=time_ns91 if self._step is not None:92 if driver:93 if not self._clocks or self._clocks[-1]!=time_ns:94 _reserve_owned_ram(self,128,'_clock_cost')95 self._clocks.append(time_ns)96 limit=(self._spec.window_ns+self._step-1)//self._step+197 while len(self._clocks)>limit:98 self._clocks.popleft()99 _release_owned_ram(self,128,'_clock_cost')100 self._held_number,self._held_right,self._held_deadline=point,right,expires_at_ns101 else:102 unknown=ScalarValue(None,self._dtype,False,Q.MISSING)103 if self._held_number is None:104 point=right=unknown105 else:106 expired=self._held_deadline is not None and time_ns>=self._held_deadline107 if expired:108 point=right=ScalarValue(None,self._dtype,False,Q.STALE)109 else:110 point=self._held_number if point.valid else point111 right=self._held_right if right.valid else right112 if not point.valid or not right.valid:113 self._held_number,self._held_right=point,right114 self.next_deadline=(self._held_deadline if self._held_deadline is not None and self._held_deadline>time_ns else None)115 self._append(time_ns,point,right)116 if self._step is not None and not driver:117 if not point.valid:118 self._point=ScalarValue(None,D.BOOL,False,point.quality)119 if not right.valid:120 self._right=ScalarValue(None,D.BOOL,False,right.quality)121 return self._point,self._right122 left=self._left(time_ns)123 if left is None or left>=time_ns:124 self._point=self._right=ScalarValue(None,D.BOOL,False,Q.MISSING)125 return self._point,self._right126 while len(self._spans)>1 and self._spans[0].end_ns<=left and self._spans[0].start_ns<left:127 self._spans.popleft()128 _release_owned_ram(self,2048,'_span_cost')129 _reserve_owned_ram(self,65536+4096*(len(self._spans)+1),'_work_cost')130 tail=DeltaSpan(time_ns,time_ns,point.value if point.valid else None,131 Q.NONE if right.valid else right.quality,Q.NONE if point.valid else point.quality,right.partial,point.partial)132 spans=tuple(self._spans)133 spans=spans if spans[-1].start_ns==time_ns else spans+(tail,)134 spec=self._spec if self._step is None else replace(self._spec,window_ns=time_ns-left)135 point_result=self._kernel(spec,spans,time_ns,self._dtype)136 right_result=point_result137 if tail.point_quality!=tail.quality:138 right_result=self._kernel(spec,spans[:-1]+(replace(tail,point_quality=tail.quality),),time_ns,self._dtype)139 self._point,self._right=self._condition(point_result),self._condition(right_result)140 # 净变化只依赖端点数值,要求单调时中间实际观测值也参与判断。141 history_partial=any((s.point_partial if s.start_ns==left else s.partial)142 for s in spans if s.start_ns<=left<s.end_ns)143 if spec.monotonic:144 history_partial|=any(s.point_partial for s in spans if left<s.start_ns<time_ns)145 if self._point.valid and (history_partial or point.partial):146 self._point=replace(self._point,partial=True)147 if self._right.valid and (history_partial or right.partial):148 self._right=replace(self._right,partial=True)149 if self._step is None:150 candidates=[span.start_ns+self._spec.window_ns for span in self._spans151 if time_ns<span.start_ns+self._spec.window_ns<(1<<63)]152 if expires_at_ns is not None and expires_at_ns>time_ns:153 candidates.append(expires_at_ns)154 self.next_deadline=min(candidates) if candidates else None155 return self._point,self._right156 except BaseException:157 self._failed=True158 raise159 finally:160 spans=point_result=right_result=None161 _release_owned_ram(self,self._work_cost,'_work_cost')162163 @property164 def closed(self):165 """返回资源关闭状态。参数: 无。返回: bool。异常: 无。"""166 return self._closed167168 def close(self):169 """释放有界片段与驱动记录,不写文件。参数: 无。返回: None。异常: 原预算关闭错,可重试。"""170 if self._closed:171 return172 self._spans.clear();self._clocks.clear()173 self._held_number=self._held_right=None174 for slot in ('_span_cost','_clock_cost','_work_cost','_fixed_cost'):175 _release_owned_ram(self,getattr(self,slot),slot)176 self._closed=True177