Files
DynamisLab/src/CCD_analysis/real_ccd/schema.py
T
Frank14fandCursor b144d62920 feat(ccd): freeze dynamic-increment analysis pipeline
Replace the legacy CCD workspace with acquisition, direct-dq, original and lagged CCD contracts so the DRL-versus-constant-mean mechanism is reproducible and fail-closed.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 16:41:00 +08:00

47 lines
6.5 KiB
Python

"""Strict immutable schema for real-case streaming CCD."""
from __future__ import annotations
from typing import Any,Mapping
import numpy as np
from CCD_analysis.acquisition.contracts import ACTION_IDENTITIES,CASES,canonical_json
from CCD_analysis.direct_dq.schema import canonical_array_sha256
RESULT_SCHEMA_ID='ccd-real-ccd-result/v1'
ARRAY_KEYS={"x_D","y_D","q_target_solver_fluid_mask","q_blk_solver_fluid_mask","q_ctl_solver_fluid_mask","analysis_fluid_mask","selected_timeline_indices","selected_acquisition_relative_lattice_steps","selected_q_ctl_absolute_lattice_steps","coordinate_weights","field_mean","action_mean","effective_actions","cross_correlation","left_functions","singular_values","physical_modes","coefficients","identifiable_mode_mask","residual_block_boundaries","weighted_relative_residuals","authoritative_mean_dq_ctl"}
CONFIG_KEYS={"schema_id","case_id","Q","tau","observable_count","channel_names","action_identities","action_units","flatten_order","weight_rule","center_snapshots","center_observables","standardization","whitening","chunk_size","accumulation_dtype","input_field_dtype","singular_block_rtol","singular_block_atol","memory","full_reconstructions_persisted"}
SUMMARY_KEYS={"schema_id","sample_count","spatial_dof_count","numerical_rank","null_tolerance","degenerate_singular_blocks","complete_block_boundaries","spectrum_label","mean_context","claim_boundary","passes","provenance_status"}
def validate_result(*,arrays:Mapping[str,Any],config:Mapping[str,Any],summary:Mapping[str,Any],input_hashes:Mapping[str,Any])->dict[str,np.ndarray]:
if set(config)!=CONFIG_KEYS or config.get('schema_id')!='ccd-real-ccd-config/v1' or config.get('case_id') not in CASES: raise ValueError('real-CCD config schema is not exact')
fixed=(config['Q']==1 and config['tau']==0 and config['observable_count']==3 and config['channel_names']==['front','upper','lower'] and config['action_identities']==list(ACTION_IDENTITIES) and config['action_units']=='native solver angular-velocity command units' and config['flatten_order']=='component-major ux then uy; C-order analysis-mask point order' and config['weight_rule']=='direct_dq.coordinate_weights(x_D)*coordinate_weights(y_D), repeated ux then uy; not area-normalized' and config['center_snapshots'] is True and config['center_observables'] is True and config['standardization'] is False and config['whitening'] is False and config['full_reconstructions_persisted'] is False and config['accumulation_dtype']=='float64')
if not fixed: raise ValueError('frozen real-CCD estimand/config contradicted')
mem=config['memory']
if not isinstance(mem,Mapping) or mem.get('decision')!='PASS' or mem.get('estimated_peak_ram_bytes',1)>mem.get('ram_budget_bytes',0) or mem.get('estimated_scratch_bytes',1)>mem.get('scratch_budget_bytes',0): raise ValueError('memory decision must pass explicit budgets')
frozen_summary = {
'spectrum_label': 'cross-correlation strength; not field energy, explained variance, or canonical coefficient',
'mean_context': 'mean effective actions and authoritative mean dq_ctl are outside CCD',
'claim_boundary': 'no CCD>POD, causal, mechanism, response-time, same-phase, independent-realization, uncertainty, or observable-prediction claim',
'provenance_status': 'VERIFIED_LIVE_INPUTS_REQUIRED_ON_LOAD',
}
if set(summary)!=SUMMARY_KEYS or summary.get('schema_id')!='ccd-real-ccd-summary/v1' or summary.get('passes')!=3 or any(summary.get(k) != v for k, v in frozen_summary.items()): raise ValueError('real-CCD summary schema/claims are not exact')
if set(arrays)!=ARRAY_KEYS: raise ValueError('real-CCD array inventory is not exact')
d={k:np.asarray(v) for k,v in arrays.items()}; n=summary['sample_count']; m=summary['spatial_dof_count']
if type(n) is not int or type(m) is not int or n<1 or m<1: raise ValueError('invalid result dimensions')
if d['selected_timeline_indices'].dtype!=np.int64 or d['selected_timeline_indices'].shape!=(n,) or np.any(np.diff(d['selected_timeline_indices'])<=0): raise ValueError('selected indices invalid')
for key in ('selected_acquisition_relative_lattice_steps','selected_q_ctl_absolute_lattice_steps'):
if d[key].dtype!=np.int64 or d[key].shape!=(n,) or np.any(np.diff(d[key])<=0): raise ValueError(f'{key} invalid')
mask=d['analysis_fluid_mask']; nx=d['x_D'].size; ny=d['y_D'].size
masks=[]
for role in ('q_target','q_blk','q_ctl'):
v=d[f'{role}_solver_fluid_mask']; masks.append(v)
if v.dtype!=np.bool_ or v.shape!=(nx,ny): raise ValueError('role mask invalid')
if mask.dtype!=np.bool_ or mask.shape!=(nx,ny) or not np.array_equal(mask,masks[0]&masks[1]&masks[2]) or m!=2*int(mask.sum()): raise ValueError('analysis mask/flatten dimension invalid')
shapes={"coordinate_weights":(m,),"field_mean":(m,),"action_mean":(3,),"effective_actions":(n,3),"cross_correlation":(3,m),"left_functions":(3,3),"singular_values":(3,),"physical_modes":(m,3),"coefficients":(3,n),"identifiable_mode_mask":(3,)}
for k,s in shapes.items():
if d[k].shape!=s or (d[k].dtype.kind in 'fc' and not np.isfinite(d[k]).all()): raise ValueError(f'{k} invalid')
if d['coordinate_weights'].dtype!=np.float64 or np.any(d['coordinate_weights']<=0) or d['field_mean'].dtype!=np.float64 or d['action_mean'].dtype!=np.float64 or d['effective_actions'].dtype!=np.float32: raise ValueError('canonical dtypes invalid')
if d['authoritative_mean_dq_ctl'].dtype!=np.float32 or d['authoritative_mean_dq_ctl'].shape!=(2,nx,ny) or not np.isfinite(d['authoritative_mean_dq_ctl']).all(): raise ValueError('authoritative mean dq_ctl invalid')
if d['residual_block_boundaries'].dtype!=np.int64 or d['weighted_relative_residuals'].dtype!=np.float64 or d['residual_block_boundaries'].shape!=d['weighted_relative_residuals'].shape or np.any(np.diff(d['residual_block_boundaries'])<=0): raise ValueError('block residual schema invalid')
if summary['complete_block_boundaries']!=d['residual_block_boundaries'].tolist() or summary['numerical_rank']!=int(d['identifiable_mode_mask'].sum()): raise ValueError('summary rank/block contradiction')
if set(input_hashes)!={'direct_dq','acquisitions','canonical_arrays'} or set(input_hashes['acquisitions'])!={'q_target','q_blk','q_ctl'} or set(input_hashes['canonical_arrays'])!=ARRAY_KEYS: raise ValueError('input/hash schema invalid')
if any(input_hashes['canonical_arrays'][k]!=canonical_array_sha256(v) for k,v in d.items()): raise ValueError('canonical array hash mismatch')
canonical_json(config); canonical_json(summary); canonical_json(input_hashes)
return d