2026-07-27 08:35:44 +00:00
#!/usr/bin/env python3
""" Generate concise loop context from GPU RANS verifier reports. """
from __future__ import annotations
import argparse
import json
import math
from datetime import datetime , timezone
from pathlib import Path
from typing import Any , Mapping
ROOT = Path ( __file__ ) . resolve ( ) . parents [ 1 ]
DEFAULT_CONTEXT = ROOT / " .loop/diagnostic-context.md "
DEFAULT_BASELINE = ROOT / " .loop/diagnostic-baseline.json "
REPORT_GLOBS = (
" tmp/**/verifier_report.json " ,
" tmp/**/report.json " ,
)
TMP_REPORT_GLOBS = (
" *gpu*/report.json " ,
" *gpu*/verifier_report.json " ,
" worker_gpu_rans_solver*/report.json " ,
" judge_gpu_rans_solver*/report.json " ,
" *gpu*rans*/report.json " ,
" *gpu*rans*/verifier_report.json " ,
)
def load_json ( path : Path ) - > Any | None :
try :
return json . loads ( path . read_text ( ) )
except Exception :
return None
def is_verifier_report ( value : Any ) - > bool :
if not isinstance ( value , Mapping ) :
return False
2026-07-29 09:37:03 +00:00
return any ( key in value for key in ( " verifier_evidence " , " first_divergence_summary " , " artifact_comparisons " , " differential_trace " ) )
2026-07-27 08:35:44 +00:00
def is_gpu_report ( value : Mapping [ str , Any ] , path : Path ) - > bool :
backend = value . get ( " backend " ) if isinstance ( value . get ( " backend " ) , Mapping ) else { }
requested = str ( backend . get ( " requested " ) or " " ) . lower ( )
selected = str ( backend . get ( " selected " ) or " " ) . lower ( )
return requested == " gpu " or selected == " gpu " or " gpu " in path . parent . name . lower ( )
def discover_latest_report ( root : Path ) - > tuple [ Path | None , Mapping [ str , Any ] | None ] :
candidates : list [ Path ] = [ ]
for pattern in REPORT_GLOBS :
candidates . extend ( root . glob ( pattern ) )
tmp_root = Path ( " /tmp " )
if tmp_root . exists ( ) :
for pattern in TMP_REPORT_GLOBS :
candidates . extend ( tmp_root . glob ( pattern ) )
unique = sorted ( { path . resolve ( ) for path in candidates if path . is_file ( ) } , key = lambda path : path . stat ( ) . st_mtime , reverse = True )
reports : list [ tuple [ Path , Mapping [ str , Any ] ] ] = [ ]
for path in unique :
data = load_json ( path )
if is_verifier_report ( data ) :
reports . append ( ( path , data ) ) # type: ignore[arg-type]
if not reports :
return None , None
gpu_reports = [ ( path , data ) for path , data in reports if is_gpu_report ( data , path ) ]
return ( gpu_reports or reports ) [ 0 ]
def get_path ( value : Mapping [ str , Any ] , path : str ) - > Any :
cursor : Any = value
for part in path . split ( " . " ) :
if not isinstance ( cursor , Mapping ) :
return None
cursor = cursor . get ( part )
return cursor
def finite_number ( value : Any ) - > float | None :
try :
number = float ( value )
except ( TypeError , ValueError ) :
return None
return number if math . isfinite ( number ) else None
def fmt ( value : Any ) - > str :
number = finite_number ( value )
if number is None :
if value is True :
return " true "
if value is False :
return " false "
if value is None :
return " - "
return str ( value )
if number == 0.0 :
return " 0 "
if abs ( number ) > = 1e4 or abs ( number ) < 1e-3 :
return f " { number : .3e } "
return f " { number : .6g } "
def status_word ( value : Any ) - > str :
if value is True :
return " passed "
if value is False :
return " failed "
return str ( value or " unknown " )
def first_nested_key ( value : Any , target : str , path : str = " " ) - > tuple [ str , Mapping [ str , Any ] ] | None :
if isinstance ( value , Mapping ) :
for key , item in value . items ( ) :
next_path = f " { path } . { key } " if path else str ( key )
if key == target and isinstance ( item , Mapping ) :
return next_path , item
found = first_nested_key ( item , target , next_path )
if found is not None :
return found
elif isinstance ( value , list ) :
for index , item in enumerate ( value ) :
found = first_nested_key ( item , target , f " { path } [ { index } ] " )
if found is not None :
return found
return None
def artifact_checks ( report : Mapping [ str , Any ] ) - > list [ dict [ str , Any ] ] :
out : list [ dict [ str , Any ] ] = [ ]
comparisons = report . get ( " artifact_comparisons " , { } ) if isinstance ( report . get ( " artifact_comparisons " ) , Mapping ) else { }
for family in ( " pressure_inputs " , " matrix_operator " , " solver " ) :
family_report = comparisons . get ( family ) if isinstance ( comparisons . get ( family ) , Mapping ) else { }
for check in family_report . get ( " checks " , [ ] ) if isinstance ( family_report . get ( " checks " ) , list ) else [ ] :
if not isinstance ( check , Mapping ) :
continue
largest = check . get ( " largest_difference " ) if isinstance ( check . get ( " largest_difference " ) , Mapping ) else { }
location = largest . get ( " location " ) if isinstance ( largest . get ( " location " ) , Mapping ) else { }
out . append (
{
" family " : family ,
" name " : check . get ( " name " ) ,
" allclose " : check . get ( " allclose " ) ,
" reason " : check . get ( " reason " ) ,
" max_abs " : check . get ( " max_abs " ) ,
" mean_abs " : check . get ( " mean_abs " ) ,
" rms_abs " : check . get ( " rms_abs " ) ,
" location " : location ,
}
)
out . sort ( key = lambda item : ( item . get ( " allclose " ) is True , item [ " family " ] , str ( item . get ( " name " ) ) ) )
return out
def field_checks ( report : Mapping [ str , Any ] ) - > list [ dict [ str , Any ] ] :
out : list [ dict [ str , Any ] ] = [ ]
modes = report . get ( " modes " , { } ) if isinstance ( report . get ( " modes " ) , Mapping ) else { }
for mode_name in ( " run_one " , " split " ) :
mode = modes . get ( mode_name ) if isinstance ( modes . get ( mode_name ) , Mapping ) else { }
comparisons = mode . get ( " comparisons " , { } ) if isinstance ( mode . get ( " comparisons " ) , Mapping ) else { }
for field , data in comparisons . items ( ) :
if not isinstance ( data , Mapping ) :
continue
out . append (
{
" mode " : mode_name ,
" field " : field ,
" allclose " : data . get ( " allclose " ) ,
" max_abs " : data . get ( " max_abs " ) ,
" mean_abs " : data . get ( " mean_abs " ) ,
" rms_abs " : data . get ( " rms_abs " ) ,
" location " : data . get ( " location " ) ,
}
)
out . sort ( key = lambda item : ( item . get ( " allclose " ) is True , item [ " mode " ] , str ( item . get ( " field " ) ) ) )
return out
2026-07-29 09:37:03 +00:00
def differential_trace_checks ( report : Mapping [ str , Any ] ) - > list [ dict [ str , Any ] ] :
trace = report . get ( " differential_trace " , { } ) if isinstance ( report . get ( " differential_trace " ) , Mapping ) else { }
checks = trace . get ( " checkpoints " , [ ] ) if isinstance ( trace . get ( " checkpoints " ) , list ) else [ ]
out : list [ dict [ str , Any ] ] = [ ]
for check in checks :
if not isinstance ( check , Mapping ) :
continue
largest = check . get ( " largest_difference " ) if isinstance ( check . get ( " largest_difference " ) , Mapping ) else { }
location = largest . get ( " location " ) if isinstance ( largest . get ( " location " ) , Mapping ) else { }
metadata = check . get ( " metadata " ) if isinstance ( check . get ( " metadata " ) , Mapping ) else { }
diff = check . get ( " difference_stats " ) if isinstance ( check . get ( " difference_stats " ) , Mapping ) else { }
out . append (
{
" name " : check . get ( " name " ) ,
" status " : check . get ( " status " ) ,
" reason " : check . get ( " reason " ) ,
" family " : metadata . get ( " family " ) ,
" lifecycle_phase " : metadata . get ( " lifecycle_phase " ) ,
" substitution_supported " : metadata . get ( " substitution_supported " ) ,
" max_abs " : diff . get ( " max_abs " ) ,
" mean_abs " : diff . get ( " mean_abs " ) ,
" rms_abs " : diff . get ( " rms_abs " ) ,
" location " : location ,
}
)
out . sort ( key = lambda item : ( item . get ( " status " ) == " passed " , str ( item . get ( " name " ) ) ) )
return out
2026-07-27 08:35:44 +00:00
def extract_metrics ( report : Mapping [ str , Any ] ) - > dict [ str , Any ] :
metrics : dict [ str , Any ] = {
" status.passed " : report . get ( " status " ) == " passed " ,
" verifier.passed " : get_path ( report , " verifier_evidence.passed " ) is True ,
}
first = report . get ( " first_divergence_summary " ) if isinstance ( report . get ( " first_divergence_summary " ) , Mapping ) else { }
if first :
metrics [ " first.target " ] = first . get ( " first_target " )
metrics [ " first.family " ] = first . get ( " artifact_family " )
metrics [ " first.stage_group " ] = first . get ( " stage_group " )
2026-07-29 09:37:03 +00:00
trace = report . get ( " differential_trace " ) if isinstance ( report . get ( " differential_trace " ) , Mapping ) else { }
if trace :
first_trace = trace . get ( " first_divergence " ) if isinstance ( trace . get ( " first_divergence " ) , Mapping ) else { }
metrics [ " trace.status " ] = trace . get ( " status " )
metrics [ " trace.first_checkpoint " ] = first_trace . get ( " name " ) if first_trace else None
metrics [ " trace.failed_count " ] = finite_number ( trace . get ( " failed_count " ) ) or 0
metrics [ " trace.missing_count " ] = finite_number ( trace . get ( " missing_count " ) ) or 0
if first_trace :
for key in ( " max_abs " , " mean_abs " , " rms_abs " ) :
number = finite_number ( first_trace . get ( key ) )
if number is not None :
metrics [ f " trace.first. { key } " ] = number
2026-07-27 08:35:44 +00:00
for check in artifact_checks ( report ) :
base = f " artifact. { check [ ' family ' ] } . { check [ ' name ' ] } "
metrics [ f " { base } .allclose " ] = check . get ( " allclose " ) is True
for key in ( " max_abs " , " mean_abs " , " rms_abs " ) :
number = finite_number ( check . get ( key ) )
if number is not None :
metrics [ f " { base } . { key } " ] = number
for check in field_checks ( report ) :
base = f " field. { check [ ' mode ' ] } . { check [ ' field ' ] } "
metrics [ f " { base } .allclose " ] = check . get ( " allclose " ) is True
for key in ( " max_abs " , " mean_abs " , " rms_abs " ) :
number = finite_number ( check . get ( key ) )
if number is not None :
metrics [ f " { base } . { key } " ] = number
preconditioner = first_nested_key ( report , " preconditioner_diagnostic " )
if preconditioner is not None :
_ , data = preconditioner
for key in (
" gpu_dilu_vs_reference_delta_l2 " ,
" diagonal_vs_reference_delta_l2 " ,
) :
number = finite_number ( data . get ( key ) )
if number is not None :
metrics [ f " preconditioner. { key } " ] = number
for path , metric_name in (
( " gpu_dilu_vs_openfoam_reference.max_abs " , " preconditioner.gpu_dilu_vs_reference.max_abs " ) ,
( " gpu_dilu_vs_openfoam_reference.rms_abs " , " preconditioner.gpu_dilu_vs_reference.rms_abs " ) ,
( " diagonal_vs_openfoam_reference.max_abs " , " preconditioner.diagonal_vs_reference.max_abs " ) ,
( " diagonal_vs_openfoam_reference.rms_abs " , " preconditioner.diagonal_vs_reference.rms_abs " ) ,
) :
number = finite_number ( get_path ( data , path ) )
if number is not None :
metrics [ metric_name ] = number
return metrics
def classify_delta ( current : Mapping [ str , Any ] , baseline : Mapping [ str , Any ] | None ) - > list [ dict [ str , Any ] ] :
if not baseline :
return [ { " metric " : name , " status " : " newly_available " , " current " : value , " baseline " : None } for name , value in sorted ( current . items ( ) ) ]
out : list [ dict [ str , Any ] ] = [ ]
previous = baseline . get ( " metrics " , { } ) if isinstance ( baseline . get ( " metrics " ) , Mapping ) else { }
for name , value in sorted ( current . items ( ) ) :
old = previous . get ( name )
status = " newly_available "
if old is not None :
if isinstance ( value , bool ) and isinstance ( old , bool ) :
if value == old :
status = " unchanged "
elif value and not old :
status = " improved "
else :
status = " regressed "
else :
now_num = finite_number ( value )
old_num = finite_number ( old )
if now_num is not None and old_num is not None :
tolerance = max ( 1e-15 , abs ( old_num ) * 1e-9 )
if abs ( now_num - old_num ) < = tolerance :
status = " unchanged "
elif now_num < old_num :
status = " improved "
else :
status = " regressed "
else :
status = " unchanged " if value == old else " changed "
out . append ( { " metric " : name , " status " : status , " current " : value , " baseline " : old } )
return out
def metric_priority ( item : Mapping [ str , Any ] ) - > tuple [ int , str ] :
status_order = { " regressed " : 0 , " improved " : 1 , " newly_available " : 2 , " changed " : 3 , " unchanged " : 4 }
return status_order . get ( str ( item . get ( " status " ) ) , 9 ) , str ( item . get ( " metric " ) )
def render_location ( location : Any ) - > str :
if not isinstance ( location , Mapping ) :
return " - "
entity = location . get ( " entity_kind " ) or " array "
index = location . get ( " entity_index " )
component = location . get ( " component_index " )
if component is None :
return f " { entity } [ { index } ] "
return f " { entity } [ { index } ] component= { component } "
def render_context ( report_path : Path | None , report : Mapping [ str , Any ] | None , baseline : Mapping [ str , Any ] | None ) - > str :
generated = datetime . now ( timezone . utc ) . isoformat ( )
if report is None or report_path is None :
return " \n " . join (
[
" # GPU RANS Loop Diagnostic Context " ,
" " ,
f " Generated: { generated } " ,
" " ,
" No verifier report was found under repo tmp/ or /tmp GPU RANS work directories. " ,
" Next action: run `scripts/verify_gpu_rans_solver.sh --work <work> --report <work>/report.json` or the focused verifier, then rerun this hook. " ,
" " ,
]
)
first = report . get ( " first_divergence_summary " ) if isinstance ( report . get ( " first_divergence_summary " ) , Mapping ) else { }
artifacts = report . get ( " intermediate_artifacts " , { } ) if isinstance ( report . get ( " intermediate_artifacts " ) , Mapping ) else { }
families = artifacts . get ( " families " , [ ] ) if isinstance ( artifacts . get ( " families " ) , list ) else [ ]
metrics = extract_metrics ( report )
deltas = classify_delta ( metrics , baseline )
checks = artifact_checks ( report )
fields = field_checks ( report )
2026-07-29 09:37:03 +00:00
trace = report . get ( " differential_trace " , { } ) if isinstance ( report . get ( " differential_trace " ) , Mapping ) else { }
trace_first = trace . get ( " first_divergence " ) if isinstance ( trace . get ( " first_divergence " ) , Mapping ) else { }
trace_substitution = trace . get ( " substitution " ) if isinstance ( trace . get ( " substitution " ) , Mapping ) else { }
trace_checks = differential_trace_checks ( report )
2026-07-27 08:35:44 +00:00
preconditioner = first_nested_key ( report , " preconditioner_diagnostic " )
solver_trace = first_nested_key ( report , " linear_solver_trace " )
lines = [
" # GPU RANS Loop Diagnostic Context " ,
" " ,
f " Generated: { generated } " ,
f " Latest report: ` { report_path } ` " ,
f " Report status: ` { report . get ( ' status ' ) } ` " ,
f " Verifier evidence passed: ` { get_path ( report , ' verifier_evidence.passed ' ) } ` " ,
" " ,
" ## First divergence " ,
" " ,
f " - Target: ` { first . get ( ' first_target ' ) if first else None } ` " ,
f " - Artifact family: ` { first . get ( ' artifact_family ' ) if first else None } ` " ,
f " - Stage group: ` { first . get ( ' stage_group ' ) if first else None } ` " ,
f " - Evidence path: ` { first . get ( ' evidence_path ' ) if first else None } ` " ,
f " - Field/reason: ` { first . get ( ' field ' ) if first else None } ` / ` { first . get ( ' reason ' ) if first else None } ` " ,
" " ,
2026-07-29 09:37:03 +00:00
" ## Differential trace " ,
" " ,
f " - Status: ` { trace . get ( ' status ' ) if trace else None } ` " ,
f " - First checkpoint: ` { trace_first . get ( ' name ' ) if trace_first else None } ` " ,
f " - Lifecycle: ` { get_path ( trace_first , ' metadata.lifecycle_phase ' ) if trace_first else None } ` " ,
f " - max_abs / rms_abs: ` { fmt ( trace_first . get ( ' max_abs ' ) if trace_first else None ) } ` / ` { fmt ( trace_first . get ( ' rms_abs ' ) if trace_first else None ) } ` " ,
f " - Substitution requested/loaded: ` { trace_substitution . get ( ' requested ' ) if trace_substitution else [ ] } ` / ` { trace_substitution . get ( ' loaded ' ) if trace_substitution else [ ] } ` " ,
f " - Source artifacts: reference=` { get_path ( trace , ' roles.reference.path ' ) if trace else None } `, candidate=` { get_path ( trace , ' roles.candidate.path ' ) if trace else None } ` " ,
2026-07-27 08:35:44 +00:00
" " ,
2026-07-29 09:37:03 +00:00
" | Checkpoint | Status | Lifecycle | max_abs | rms_abs | Location | Substitute? | " ,
" |---|---:|---|---:|---:|---|---:| " ,
2026-07-27 08:35:44 +00:00
]
2026-07-29 09:37:03 +00:00
for check in trace_checks [ : 12 ] :
lines . append (
" | {name} | {status} | {phase} | {max_abs} | {rms_abs} | {location} | {substitute} | " . format (
name = check . get ( " name " ) ,
status = check . get ( " status " ) ,
phase = check . get ( " lifecycle_phase " ) ,
max_abs = fmt ( check . get ( " max_abs " ) ) ,
rms_abs = fmt ( check . get ( " rms_abs " ) ) ,
location = render_location ( check . get ( " location " ) ) ,
substitute = status_word ( check . get ( " substitution_supported " ) ) ,
)
)
lines . extend ( [ " " , " ## Solver phase evidence " , " " , " | Family | Status | Why | " , " |---|---:|---| " ] )
2026-07-27 08:35:44 +00:00
for family in families :
if not isinstance ( family , Mapping ) :
continue
lines . append ( f " | { family . get ( ' name ' ) } | { family . get ( ' status ' ) } | { family . get ( ' why ' ) } | " )
lines . extend ( [ " " , " ## Numeric artifact comparisons " , " " , " | Family | Check | Status | max_abs | mean_abs | rms_abs | Location | " , " |---|---|---:|---:|---:|---:|---| " ] )
for check in checks [ : 16 ] :
lines . append (
" | {family} | {name} | {status} | {max_abs} | {mean_abs} | {rms_abs} | {location} | " . format (
family = check . get ( " family " ) ,
name = check . get ( " name " ) ,
status = status_word ( check . get ( " allclose " ) ) ,
max_abs = fmt ( check . get ( " max_abs " ) ) ,
mean_abs = fmt ( check . get ( " mean_abs " ) ) ,
rms_abs = fmt ( check . get ( " rms_abs " ) ) ,
location = render_location ( check . get ( " location " ) ) ,
)
)
lines . extend ( [ " " , " ## Field comparison symptoms " , " " , " | Mode | Field | Status | max_abs | mean_abs | rms_abs | Location | " , " |---|---|---:|---:|---:|---:|---| " ] )
for check in fields [ : 12 ] :
lines . append (
" | {mode} | {field} | {status} | {max_abs} | {mean_abs} | {rms_abs} | {location} | " . format (
mode = check . get ( " mode " ) ,
field = check . get ( " field " ) ,
status = status_word ( check . get ( " allclose " ) ) ,
max_abs = fmt ( check . get ( " max_abs " ) ) ,
mean_abs = fmt ( check . get ( " mean_abs " ) ) ,
rms_abs = fmt ( check . get ( " rms_abs " ) ) ,
location = render_location ( check . get ( " location " ) ) ,
)
)
lines . extend ( [ " " , " ## Linear solver and preconditioner trace " , " " ] )
if preconditioner is None :
lines . append ( " No preconditioner diagnostic artifact found in the latest report. " )
else :
path , data = preconditioner
lines . extend (
[
f " Preconditioner evidence path: ` { path } ` " ,
f " - GPU DILU vs OpenFOAM reference max_abs: ` { fmt ( get_path ( data , ' gpu_dilu_vs_openfoam_reference.max_abs ' ) ) } ` " ,
f " - GPU DILU vs OpenFOAM reference rms_abs: ` { fmt ( get_path ( data , ' gpu_dilu_vs_openfoam_reference.rms_abs ' ) ) } ` " ,
f " - Diagonal/current vs OpenFOAM reference max_abs: ` { fmt ( get_path ( data , ' diagonal_vs_openfoam_reference.max_abs ' ) ) } ` " ,
f " - Residual entering preconditioner recorded: ` { data . get ( ' residual_entering_preconditioner ' ) is not None } ` " ,
]
)
if solver_trace is not None :
path , data = solver_trace
lines . append ( f " Solver trace path: ` { path } ` " )
for point in data . get ( " trace_points " , [ ] ) if isinstance ( data . get ( " trace_points " ) , list ) else [ ] :
if isinstance ( point , Mapping ) :
lines . append ( f " - ` { point . get ( ' name ' ) } `: { point . get ( ' step ' ) } " )
lines . extend ( [ " " , " ## Delta versus retained baseline " , " " , " | Metric | Status | Current | Baseline | " , " |---|---:|---:|---:| " ] )
for item in sorted ( deltas , key = metric_priority ) [ : 24 ] :
lines . append ( f " | ` { item . get ( ' metric ' ) } ` | { item . get ( ' status ' ) } | { fmt ( item . get ( ' current ' ) ) } | { fmt ( item . get ( ' baseline ' ) ) } | " )
lines . extend (
[
" " ,
" ## Next target hint " ,
" " ,
f " Focus first on ` { first . get ( ' first_target ' ) if first else ' unknown ' } `. Treat downstream field symptoms as unreliable until that artifact or missing evidence closes. " ,
" " ,
]
)
return " \n " . join ( lines )
def parse_args ( argv : list [ str ] | None = None ) - > argparse . Namespace :
parser = argparse . ArgumentParser ( description = __doc__ )
parser . add_argument ( " --root " , type = Path , default = ROOT )
parser . add_argument ( " --report " , type = Path , default = None , help = " Explicit report path; otherwise discover newest verifier report " )
parser . add_argument ( " --context " , type = Path , default = DEFAULT_CONTEXT )
parser . add_argument ( " --baseline " , type = Path , default = DEFAULT_BASELINE )
return parser . parse_args ( argv )
def main ( argv : list [ str ] | None = None ) - > int :
args = parse_args ( argv )
root = args . root . resolve ( )
if args . report is not None :
report_path = args . report . resolve ( )
loaded = load_json ( report_path )
report = loaded if is_verifier_report ( loaded ) else None
else :
report_path , report = discover_latest_report ( root )
baseline = load_json ( args . baseline ) if args . baseline . exists ( ) else None
baseline_mapping = baseline if isinstance ( baseline , Mapping ) else None
args . context . parent . mkdir ( parents = True , exist_ok = True )
args . context . write_text ( render_context ( report_path , report , baseline_mapping ) , encoding = " utf-8 " )
if report is not None and report_path is not None :
current = {
" updated_at " : datetime . now ( timezone . utc ) . isoformat ( ) ,
" report " : str ( report_path ) ,
" metrics " : extract_metrics ( report ) ,
" first_divergence_summary " : report . get ( " first_divergence_summary " ) ,
2026-07-29 09:37:03 +00:00
" differential_trace " : report . get ( " differential_trace " ) ,
2026-07-27 08:35:44 +00:00
}
args . baseline . parent . mkdir ( parents = True , exist_ok = True )
args . baseline . write_text ( json . dumps ( current , indent = 2 , sort_keys = True ) + " \n " , encoding = " utf-8 " )
print ( f " updated diagnostic context: { args . context } from { report_path } " )
else :
print ( f " updated diagnostic context without report: { args . context } " )
return 0
if __name__ == " __main__ " :
raise SystemExit ( main ( ) )