Chronaxis用户手册English
本页目录

chronaxis._control_models

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""三条内部控制通道的固定模型转换;协议只允许这里显式列出的构造器。"""2from collections.abc import Mapping3from math import isfinite4from pathlib import Path56_SIGNAL = ('name source field dtype unit valid_for_ns input_unit scale offset choices valid_range '7           'invalid_values sample_policy same_time_policy semantic_id semantic_contract_version '8           'mapping_version sign_convention measurement_location input_sign_convention direction_factor '9           'config_version input_dtype code_dtype capture_code').split()10_ASC = 'path parser_version encoding time_reference timezone_offset_minutes offset_ns'.split()11_CSV_TIME = 'format unit offset_ns timezone_offset_minutes rollover_min_drop_ns'.split()12_COLUMN = 'column fixed value_map strip'.split()13_LAYOUT = 'fields encoding delimiter has_header skip_rows layout_version'.split()14_RULE = 'channel frame_id continuous is_extended is_fd protected same_time_policy'.split()15_RESULT = ('job_id status plan_path plan_identity dataset_id entity_id session_id input_identity run_id '16           'output_dir manifest_path error_type error_message error_chain elapsed_seconds memory_budget_bytes worker_pid').split()17_OPTIONS = set(('classify_unselected cycle_fields metadata_fields integrity trace retain_source_time selected_ids '18                'time_frame_ids emit_frame_ids blf_time_scope legacy_blf_decode').split())192021def native(value, depth=0):22    """复制控制值为有限JSON树。2324    参数: value原生值或已冻结映射,depth从0累计且最多320层。25    返回: 独立list/dict及原生标量。26    异常: 类型、深度或非有限数错误。27    """28    if depth > 320:29        raise ValueError('控制数据嵌套超过320层')30    if value is None or type(value) in (str, bool, int):31        return value32    if type(value) is float:33        if not isfinite(value):34            raise ValueError('控制数据需要有限数值')35        return value36    if isinstance(value, Mapping):37        if any(type(key) is not str for key in value):38            raise ValueError('控制映射键需要文本')39        return {key: native(item, depth+1) for key,item in value.items()}40    if type(value) in (list, tuple):41        return [native(item, depth+1) for item in value]42    raise TypeError('控制数据含未声明类型')434445def keys(data, names):46    """核对模型的完整字段集合。4748    参数: data字典,names允许且必需的字段名序列。49    返回: 原字典。50    异常: 缺项、多项或非字典时ValueError。51    """52    if type(data) is not dict or set(data) != set(names):53        raise ValueError('控制模型字段不符:' + ','.join(names))54    return data555657def extract(value, names):58    """摘取固定模型的明确字段。5960    参数: value已知模型,names调用点写定的字段名序列。61    返回: 字段名到原值的dict。62    异常: 缺少已声明属性时AttributeError。63    """64    return {name: getattr(value, name) for name in names}656667def _scalar(value):68    """编码信号无效值标记。6970    参数: value原生标量,float允许NaN/Inf。71    返回: 二元素列表,分别为固定种类及无损原值。72    异常: 未支持类型时ValueError。73    """74    if type(value) is float:75        return ['float', value.hex()]76    if value is None or type(value) in (str, bool, int):77        return ['value', value]78    raise ValueError('信号无效值类型不可传输')798081def _restore_scalar(data):82    """恢复信号无效值标记。8384    参数: data固定二元素列表。85    返回: 原生标量,保留float与整数区别。86    异常: 种类、数量或浮点文本非法时ValueError。87    """88    if type(data) is not list or len(data) != 2:89        raise ValueError('信号无效值编码非法')90    if data[0] == 'float' and type(data[1]) is str:91        return float.fromhex(data[1])92    if data[0] == 'value' and (data[1] is None or type(data[1]) in (str,bool,int)):93        return data[1]94    raise ValueError('信号无效值编码非法')959697def pack_signal(signal):98    """展开已冻结信号声明。99100    参数: signal为SignalDefinition。101    返回: JSON原生字段字典。102    异常: 模型类型或字段值非法。103    """104    from .core.binding import SignalDefinition105    if type(signal) is not SignalDefinition:106        raise TypeError('CAN控制信号需要SignalDefinition')107    value = extract(signal, _SIGNAL)108    for name in ('dtype', 'input_dtype', 'code_dtype'):109        value[name] = value[name].value if value[name] is not None else None110    value['invalid_values'] = [_scalar(item) for item in value['invalid_values']]111    return native(value)112113114def restore_signal(value):115    """恢复明确的信号声明。116117    参数: value必须包含_SIGNAL全部字段。118    返回: SignalDefinition,其派生单位状态由构造重新核对。119    异常: 字段、dtype或模型约束错误。120    """121    from .core.binding import SignalDefinition122    from .core.types import SignalDType123    value = dict(keys(value, _SIGNAL))124    for name in ('dtype', 'input_dtype', 'code_dtype'):125        value[name] = SignalDType(value[name]) if value[name] is not None else None126    value['choices'] = tuple(tuple(item) for item in value['choices'])127    value['valid_range'] = tuple(value['valid_range']) if value['valid_range'] is not None else None128    value['invalid_values'] = tuple(_restore_scalar(item) for item in value['invalid_values'])129    return SignalDefinition(**value)130131132def pack_input(item):133    """展开一个CAN文件输入。134135    参数: item为ASCSpec、BLFSpec或CANCSVSpec。136    返回: 固定种类及字段字典的二元素列表。137    异常: 未知输入类型时TypeError。138    """139    from .sources.asc import ASCSpec140    from .sources.blf import BLFSpec141    from .sources.raw_csv import CANCSVSpec142    if type(item) is ASCSpec:143        value = extract(item, _ASC)144        value['path'] = str(value['path'])145        return ['asc', native(value)]146    if type(item) is BLFSpec:147        return ['blf', dict(path=str(item.path), offset_ns=item.offset_ns)]148    if type(item) is CANCSVSpec:149        layout = extract(item.layout, _LAYOUT)150        layout['fields'] = {name: extract(mapping, _COLUMN) for name,mapping in item.layout.fields.items()}151        return ['csv', native(dict(path=str(item.path), layout=layout,152            time=extract(item.time,_CSV_TIME), id_base=item.id_base, length_base=item.length_base,153            channel_origin=item.channel_origin, payload_style=item.payload_style))]154    raise TypeError('CAN控制输入类型未声明')155156157def restore_input(data):158    """恢复一个CAN文件输入。159160    参数: data为固定种类及字段字典。161    返回: ASCSpec、BLFSpec或CANCSVSpec,不读取输入。162    异常: 种类、字段或原模型约束错误。163    """164    from .sources.asc import ASCSpec165    from .sources.blf import BLFSpec166    from .sources.raw_csv import CANCSVSpec, CSVTimeSpec167    from .sources.raw_csv_layout import CANCSVLayout, ColumnMapping168    if type(data) is not list or len(data) != 2:169        raise ValueError('CAN输入声明结构非法')170    kind, value = data171    if kind == 'asc':172        value = dict(keys(value, _ASC))173        value['path'] = Path(value['path'])174        return ASCSpec(**value)175    if kind == 'blf':176        keys(value, ('path','offset_ns'))177        return BLFSpec(Path(value['path']),value['offset_ns'])178    if kind == 'csv':179        value = dict(keys(value, ('path','layout','time','id_base','length_base','channel_origin','payload_style')))180        layout = dict(keys(value['layout'], _LAYOUT))181        if type(layout['fields']) is not dict:182            raise ValueError('CAN CSV列映射需要对象')183        layout['fields'] = {name:ColumnMapping(**keys(mapping,_COLUMN)) for name,mapping in layout['fields'].items()}184        value.update(path=Path(value['path']), layout=CANCSVLayout(**layout),185                     time=CSVTimeSpec(**keys(value['time'],_CSV_TIME)))186        return CANCSVSpec(**value)187    raise ValueError('CAN输入类型未知')188189190def pack_source(source):191    """展开单次CAN来源。192193    参数: source为CANTextSpec。194    返回: 原生字段字典,路径按现有文本保留。195    异常: 类型或嵌套模型错误。196    """197    from .sources.text_can_spec import CANTextSpec198    if type(source) is not CANTextSpec:199        raise TypeError('CAN控制来源需要CANTextSpec')200    return dict(name=source.name,inputs=[pack_input(item) for item in source.inputs],201        dbc_path=str(source.dbc_path), channel=source.channel,202        timebase=dict(timebase_id=source.timebase.timebase_id,time_type=source.timebase.time_type.value,203                      origin=source.timebase.origin),204        decode_policy=source.decode_policy,input_check=source.input_check,file_merge=source.file_merge)205206207def restore_source(value):208    """恢复单文件CAN来源。209210    参数: value为固定来源字段字典,inputs必须恰好一份。211    返回: CANTextSpec,不打开DBC或日志。212    异常: 字段、时间类型或输入数量错误。213    """214    from .sources.text_can_spec import CANTextSpec215    from .core.models import TimebaseSpec216    from .core.types import TimeType217    value = dict(keys(value, ('name','inputs','dbc_path','channel','timebase','decode_policy','input_check','file_merge')))218    base = keys(value['timebase'], ('timebase_id','time_type','origin'))219    if type(value['inputs']) is not list or len(value['inputs']) != 1:220        raise ValueError('CAN工作进程需要恰好一个输入')221    value.update(inputs=tuple(restore_input(item) for item in value['inputs']),222        dbc_path=Path(value['dbc_path']),223        timebase=TimebaseSpec(base['timebase_id'],TimeType(base['time_type']),base['origin']))224    return CANTextSpec(**value)225226227def pack_options(options):228    """展开CAN读取关键字。229230    参数: options仅包含_OPTIONS白名单。231    返回: 原生字典,周期定位显式展开,ID集合变列表。232    异常: 回调、未知关键字或未支持值类型错误。233    """234    from .legacy.records import field_to_dict235    if type(options) is not dict or not set(options) <= _OPTIONS:236        raise ValueError('CAN控制选项不在固定白名单')237    result = dict(options)238    for name in ('cycle_fields','metadata_fields'):239        if name in result:240            result[name] = [field_to_dict(value) for value in result[name]]241    for name in ('selected_ids','time_frame_ids','emit_frame_ids'):242        if result.get(name) is not None:243            result[name] = list(result[name])244    return native(result)245246247def restore_options(options):248    """恢复CAN读取关键字。249250    参数: options为白名单原生字典。251    返回: 含固定LegacyCANField元组及ID元组的dict。252    异常: 未知选项或非法定位模型错误。253    """254    from .legacy.records import field_from_dict255    if type(options) is not dict or not set(options) <= _OPTIONS:256        raise ValueError('CAN控制选项不在固定白名单')257    result = dict(options)258    for name in ('cycle_fields','metadata_fields'):259        if name in result:260            result[name] = tuple(field_from_dict(value) for value in result[name])261    for name in ('selected_ids','time_frame_ids','emit_frame_ids'):262        if result.get(name) is not None:263            result[name] = tuple(result[name])264    return result265266267def pack_frame_context(value):268    """展开所需帧重建声明。269270    参数: value为None或规则元组、周期开关、完整性开关。271    返回: None或固定三元素列表。272    异常: 非原生字段错误。273    """274    if value is None:275        return None276    rules, cycles, integrity = value277    return [native([extract(rule,_RULE) for rule in rules]), cycles, integrity]278279280def restore_frame_context(value):281    """恢复固定帧重建上下文。282283    参数: value为None或三元素列表。284    返回: None或FrameRebuildRule元组与两个bool。285    异常: 字段、开关或规则校验错误。286    """287    from .results.frame_patch import FrameRebuildRule288    if value is None:289        return None290    if type(value) is not list or len(value) != 3 or type(value[1]) is not bool or type(value[2]) is not bool:291        raise ValueError('CAN帧重建上下文非法')292    rules = []293    for row in value[0]:294        row = dict(keys(row,_RULE))295        row['continuous'], row['protected'] = tuple(row['continuous']), tuple(row['protected'])296        rules.append(FrameRebuildRule(**row))297    return tuple(rules), value[1], value[2]298299300def pack_result(result):301    """展开无活动资源的批量结果。302303    参数: result为recovery为None的BatchJobResult。304    返回: 固定原生结果字典。305    异常: 未知类型或仍持恢复句柄时TypeError。306    """307    from .batch.models import BatchJobResult308    if type(result) is not BatchJobResult or result.recovery is not None:309        raise TypeError('批量结果只能发送无活动恢复对象的事实')310    value = extract(result,_RESULT)311    for name in ('output_dir','manifest_path'):312        value[name] = str(value[name]) if value[name] is not None else None313    return native(value)314315316def restore_result(value):317    """恢复批量任务结果事实。318319    参数: value包含_RESULT全部字段。320    返回: BatchJobResult,指定路径字段恢复为Path,恢复对象仍为空。321    异常: 字段或结果模型约束错误。322    """323    from .batch.models import BatchJobResult324    value = dict(keys(value,_RESULT))325    for name in ('output_dir','manifest_path'):326        value[name] = Path(value[name]) if value[name] is not None else None327    value['error_chain'] = tuple(value['error_chain'])328    return BatchJobResult(**value)329330331def pack_message(family, message):332    """展开固定控制命令的参数。333334    参数: family为can/fast/batch,message为现有命令tuple。335    返回: JSON原生参数列表。336    异常: 未支持对象或固定模型错误。337    """338    operation, *arguments = message339    if family == 'can' and operation == 'file':340        source, signals, options, limit, context = arguments341        arguments = [pack_source(source),[pack_signal(item) for item in signals],342                     pack_options(options),limit,pack_frame_context(context)]343    elif family == 'batch' and operation == 'result':344        result, pending = arguments345        arguments = [pack_result(result),pending]346    elif family == 'batch' and operation == 'fast_result':347        # fast工作结果是已知字段dict,Path只在这两个固定结果位置允许。348        arguments[0] = dict(arguments[0])349        for name in ('output_dir','manifest_path'):350            value = arguments[0].get(name)351            if isinstance(value,Path):352                arguments[0][name] = str(value)353    return native(arguments)354355356def restore_message(family, operation, arguments):357    """恢复固定命令参数。358359    参数: family通道、operation命令、arguments原生参数列表。360    返回: 现有消费者使用的tuple,仅恢复写定模型。361    异常: 数量、类型或模型校验错误。362    """363    if type(arguments) is not list:364        raise ValueError('控制消息参数需要列表')365    if family == 'can' and operation == 'file':366        if len(arguments) != 5:367            raise ValueError('CAN文件命令参数数量不符')368        source, signals, options, limit, context = arguments369        if type(signals) is not list or len(signals) > 4096:370            raise ValueError('CAN信号声明数量非法')371        if type(limit) is not int or limit <= 0:372            raise ValueError('CAN工作预算非法')373        arguments = [restore_source(source),tuple(restore_signal(item) for item in signals),374                     restore_options(options),limit,restore_frame_context(context)]375    elif family == 'batch' and operation == 'result':376        if len(arguments) != 2 or type(arguments[1]) is not bool:377            raise ValueError('批量结果参数非法')378        arguments = [restore_result(arguments[0]),arguments[1]]379    elif family == 'batch' and operation == 'fast_result':380        if len(arguments) not in (2,3) or type(arguments[0]) is not dict or type(arguments[1]) is not bool:381            raise ValueError('fast批量结果参数非法')382        result = dict(arguments[0])383        for name in ('output_dir','manifest_path'):384            if result.get(name) is not None:385                result[name] = Path(result[name])386        if 'error_chain' in result:387            result['error_chain'] = tuple(result['error_chain'])388        arguments[0] = result389    elif family == 'can' and operation in ('next','close') and arguments:390        raise ValueError('CAN无参数命令包含多余参数')391    return (operation, *arguments)392393394395_FAST_KEYS = set(('path dbc channel offset_ns asc_options domain_ns table_format file_merge process_count '396                 'memory_budget_bytes spool_quota_bytes metrics inputs _dbc_digest _profile').split())397_METRIC_KEYS = set(('name frame_id is_extended is_fd expr unit signals parameters direction output_scale program').split())398399400def _object(value, required, allowed):401    """核对有限对象字段。402403    参数: value字典,required必需字段,allowed全部允许字段。404    返回: None,不改输入。405    异常: 缺项、未知字段或类型错误。406    """407    if type(value) is not dict or not set(required)<=set(value) or not set(value)<=set(allowed):408        raise ValueError('控制对象字段不符')409410411def _program(value, depth=0):412    """核对已有数值指令形状。413414    参数: value固定指令dict,depth从0累计且最多128。415    返回: None,不重新编译文本表达式。416    异常: 未知操作、dtype、参数或深度错误。417    """418    if depth>128:419        raise ValueError('fast控制指令深度超过128')420    _object(value,('op','dtype'),('op','dtype','name','value','args','scale','offset'))421    operation=value['op']422    if value['dtype'] not in ('int64','uint64','float64'):423        raise ValueError('fast控制指令数值类型非法')424    if operation=='name':425        keys(value,('op','dtype','name'))426        if type(value['name']) is not str:raise ValueError('fast指令名称非法')427    elif operation=='const':428        keys(value,('op','dtype','value'))429        if type(value['value']) not in (int,float):raise ValueError('fast指令常量非法')430    elif operation in ('add','sub','mul','div','abs','neg','to_float','unit_convert'):431        names=('op','dtype','args','scale','offset') if operation=='unit_convert' else ('op','dtype','args')432        keys(value,names)433        expected=2 if operation in ('add','sub','mul','div') else 1434        if type(value['args']) is not list or len(value['args'])!=expected:435            raise ValueError('fast指令参数数量非法')436        if operation=='unit_convert' and any(type(value[k]) not in (int,float) for k in ('scale','offset')):437            raise ValueError('fast指令换算系数非法')438        for argument in value['args']:_program(argument,depth+1)439    else:440        raise ValueError('fast控制指令未知')441442443def check_fast_spec(value):444    """核对有限fast文件声明。445446    参数: value原生spec字典。447    返回: None,不加载数据库或运行公式。448    异常: 未知字段、缺项或固定指令错误。449    """450    _object(value,('path','dbc','channel','metrics'),_FAST_KEYS)451    if type(value['path']) is not str or type(value['dbc']) is not str or type(value['channel']) is not int:452        raise ValueError('fast路径或通道类型非法')453    if type(value['metrics']) not in (tuple,list) or not value['metrics']:454        raise ValueError('fast需要非空计算项列表')455    for item in value['metrics']:456        _object(item,('name','frame_id','expr','unit','signals'),_METRIC_KEYS)457        if any(type(item[key]) is not str for key in ('name','expr','unit')) or type(item['frame_id']) is not int:458            raise ValueError('fast计算项字段类型非法')459        if type(item['signals']) is not dict:460            raise ValueError('fast信号需要对象')461        for signal in item['signals'].values():462            _object(signal,('signal','dtype'),('signal','dtype','input_dtype','scale','offset'))463            if type(signal['signal']) is not str or signal['dtype'] not in ('int64','uint64','float64'):464                raise ValueError('fast信号字段非法')465        if 'parameters' in item and (type(item['parameters']) is not dict466                or any(type(v) not in (int,float) for v in item['parameters'].values())):467            raise ValueError('fast参数需数值映射')468        if item.get('direction') is not None:469            keys(item['direction'],('signal','bucket'))470        if 'program' in item:_program(item['program'])471    if 'inputs' in value:472        if type(value['inputs']) is not list:raise ValueError('fast输入需列表')473        for item in value['inputs']:474            _object(item,('path',),_FAST_KEYS-{'inputs'})475    if 'asc_options' in value:476        _object(value['asc_options'],(),set(_ASC)-{'path'})477478479480def _check_fast_rows(rows):481    """检查快速批量总量所用的固定五字段结果。482483    参数: rows为未请求/任务失败时的None,或当前累计器产出的行列表。484    返回: None;不改数值、名称和顺序,也不限制合法任务的计算项数量。485    异常: 未知字段、类型或结果状态错误。486    """487    if rows is None:488        return489    if type(rows) is not list:490        raise ValueError('fast批量累计结果需要列表或None')491    for row in rows:492        keys(row,('name','unit','value','status','reason'))493        if type(row['name']) is not str or type(row['unit']) is not str:494            raise ValueError('fast累计名称及单位需要文本')495        value=row['value']496        if value is not None and (type(value) not in (int,float) or not isfinite(value)):497            raise ValueError('fast累计数值需要有限数或None')498        if row['status'] not in ('complete','indeterminate') or row['reason'] not in (499                None,'no_target_frames','no_selected_direction'):500            raise ValueError('fast累计状态或原因非法')501502503def check_message_fields(family, operation, arguments):504    """核对大消息的固定字段。505506    参数: family通道、operation命令、arguments参数序列。507    返回: None,用户config仍交原AnalysisPlan验证。508    异常: 固定字段、计数、结果状态或指令错误。509    """510    if family=='fast' and operation in ('file','program_file'):511        check_fast_spec(arguments[1])512    elif family=='fast' and operation=='eof':513        facts,clocks=arguments[2],arguments[3]514        keys(facts,('raw','targets','valid','selected','kinds','compatible'))515        if type(facts['raw']) is not int or facts['raw']<0:516            raise ValueError('fast原帧计数非法')517        counts=[]518        for key in ('targets','valid','selected','kinds','compatible'):519            if type(facts[key]) not in (list,tuple) or any(type(v) is not int or v<0 for v in facts[key]):520                raise ValueError('fast计数或身份列表非法')521            if key!='compatible':counts.append(len(facts[key]))522        if len(set(counts))>1:523            raise ValueError('fast各计算项计数长度不符')524        allowed={'compute_seconds','credit_wait_seconds','send_seconds','write_seconds',525                 'read_seconds','decode_seconds','formula_seconds'}526        if not set(clocks)<=allowed or any(type(v) not in (int,float) or not isfinite(v) or v<0 for v in clocks.values()):527            raise ValueError('fast计时字段非法')528    elif family=='batch' and operation in ('run','fast'):529        packet=arguments[0]530        if operation=='run':531            names=('job_id','path') if 'path' in packet else ('job_id','config','base_dir','identity')532            keys(packet,names)533            for key in set(names)-{'config'}:534                if type(packet[key]) is not str:raise ValueError('batch任务文本字段非法')535            if 'config' in packet and type(packet['config']) is not dict:536                raise ValueError('batch内存配置需要对象')537        else:538            keys(packet,('job_id','path','spec','total'))539            if type(packet['job_id']) is not str or packet['path'] is not None and type(packet['path']) is not str or type(packet['total']) is not bool:540                raise ValueError('fast批量任务字段非法')541            check_fast_spec(packet['spec'])542    elif family=='batch' and operation in ('result','fast_result'):543        if operation=='fast_result' and len(arguments)==3:544            _check_fast_rows(arguments[2])545        row=arguments[0]546        _object(row,('job_id','status'),_RESULT)547        if row['status'] not in ('completed','failed','cancelled'):548            raise ValueError('batch结果状态非法')549        for key in set(_RESULT)-{'error_chain','elapsed_seconds','memory_budget_bytes','worker_pid'}:550            if row.get(key) is not None and type(row[key]) is not str:551                raise ValueError('batch结果文本字段非法')552        if 'error_chain' in row and (type(row['error_chain']) is not list or any(type(v) is not str for v in row['error_chain'])):553            raise ValueError('batch错误链需要文本列表')554        if 'elapsed_seconds' in row and (type(row['elapsed_seconds']) not in (int,float) or not isfinite(row['elapsed_seconds']) or row['elapsed_seconds']<0):555            raise ValueError('batch结果耗时非法')556        for key in ('memory_budget_bytes','worker_pid'):557            if row.get(key) is not None and (type(row[key]) is not int or row[key]<1):558                raise ValueError('batch结果计数非法')559
Chronaxis 用户手册 · 离线阅读