"""Compare identical native models with the old fixed-point, previous and current pipe solvers. Run with Python 3.12 from the repository; generated programs/results stay in --output-dir. The baseline ref is an explicit Git revision, never a frozen numerical result substituted for execution. """ from __future__ import annotations import argparse from hashlib import sha256 import json from pathlib import Path import statistics import subprocess import sys ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) from app.main import compile_system_xml_network from app.simulation.backends import simulation_config from app.simulation.native_codegen import build as builder from app.simulation.native_codegen.compiler import compile_native_program from app.simulation.native_codegen.input import load_input from app.simulation.native_codegen.runner import execute_native OLD_ITERATION = '''double rough_limit=pipe_rough_limit(rr); double base=area*p*cm/sqrt(T),q=sqrt(d/(length*.02))*base; for(int i=0;i<(kind==0?64:16);i++) { double next=sqrt(d/(length*pipe_friction_prepared(4*fabs(q)/den,rr,rough_limit)))*base; if(fabs(next-q)<=fmax(1e-12,fabs(q)*1e-9)) return sign*next; q=.5*(q+next); } return sign*q;''' NEW_CALL = '''double base=area*p*cm/sqrt(T),K=pow(4*base/den,2)*d/length; return sign*native_pipe_resistance(K,rr,den/4,NULL)*den/4;''' def pipe_source_path(native: Path) -> Path: """Locate executable pipe formulas, never a modular aggregation entry.""" module = native / 'components/modules/pipe.c' legacy = native / 'components/kernels.c' if module.is_file(): target = module elif (native / 'components/modules').exists(): raise ValueError(f'Incomplete modular native snapshot: {module} is missing') else: target = legacy if not target.is_file(): raise ValueError(f'Native snapshot has no audited pipe source: {native}') source = target.read_text() if source.count('double native_pipe_resistance(') != 1 or source.count('double native_pipe_flow(') != 1: raise ValueError(f'Unsupported pipe source layout: {target}; refusing an unmodified or uninstrumented run') return target def working_native_paths() -> list[str]: return sorted('native/' + path.relative_to(ROOT / 'native').as_posix() for path in (ROOT / 'native').rglob('*') if path.is_file()) def build_snapshot(program, native: Path, cache: Path, *, required_sources=()): """Build exact snapshot sources and verify the modified files were compiled. Historical monolithic trees retain their original set of C translation units. This temporary selector override is confined to the standalone manual process; production source selection is always restored afterwards. An incompatible old model/runtime ABI fails compilation explicitly. """ native = native.resolve() pipe = pipe_source_path(native) required = (pipe, *required_sources) old_native = builder.NATIVE old_selector = getattr(builder, '_runtime_sources', None) legacy = pipe.name == 'kernels.c' try: builder.NATIVE = native if legacy and old_selector is not None: builder._runtime_sources = lambda _: sorted(native.rglob('*.c')) elif not legacy and old_selector is None: raise ValueError('A modular snapshot requires the incremental native source selector; use its matching checkout') selected = (builder._runtime_sources(program) if old_selector is not None else sorted(native.rglob('*.c'))) if any(path.resolve() not in {source.resolve() for source in selected} for path in required): raise ValueError('The model/build selector does not compile the modified pipe/RHS source; refusing a misleading run') try: build = builder.build_native(program, cache_dir=cache) except RuntimeError as exc: raise RuntimeError( f'Cannot build this native snapshot ({native}). Its source layout or model/runtime ABI may be incompatible; ' 'use matching compiler/runtime revisions. No benchmark was executed.' ) from exc recorded = build.manifest.get('sourceHashes', {}) for path in required: suffix = 'native/' + path.resolve().relative_to(native).as_posix() matches = [digest for name, digest in recorded.items() if name == suffix or name.replace('\\', '/').endswith('/' + suffix)] if matches != [sha256(path.read_bytes()).hexdigest()]: if hasattr(build, 'close'): build.close() raise ValueError(f'Build manifest does not prove the modified source was compiled: {suffix}') return build finally: builder.NATIVE = old_native if old_selector is not None: builder._runtime_sources = old_selector def main(): parser=argparse.ArgumentParser(description=__doc__) parser.add_argument('input',type=Path) parser.add_argument('--output-dir',type=Path,required=True) parser.add_argument('--baseline-ref',default='5d5a2e1') parser.add_argument('--runs',type=int,default=3) args=parser.parse_args() if args.runs<1:parser.error('--runs must be positive') out=args.output_dir.resolve();out.mkdir(parents=True,exist_ok=True) if (out/'summary.json').exists() or any((out/name).exists() for name in ('fixed-point','previous-newton','guarded-newton')): parser.error('Choose a fresh output directory; existing variant sources must not be mixed') revision=subprocess.check_output(['git','rev-parse',args.baseline_ref],cwd=ROOT,text=True).strip() xml,doc=load_input(args.input) program=compile_native_program(compile_system_xml_network(doc)) config=simulation_config(doc.simulation) (out/'input.xml').write_bytes(xml) (out/('input'+args.input.suffix)).write_bytes(args.input.read_bytes()) paths=subprocess.check_output(['git','ls-tree','-r','--name-only',revision,'native'],cwd=ROOT,text=True).splitlines() variants={} original_native=builder.NATIVE try: for variant in ('fixed-point','previous-newton','guarded-newton'): directory=out/variant variant_paths = working_native_paths() if variant == 'guarded-newton' else paths for name in variant_paths: if variant=='guarded-newton':data=(ROOT/name).read_bytes() else:data=subprocess.check_output(['git','show',f'{revision}:{name}'],cwd=ROOT) target=directory/name;target.parent.mkdir(parents=True,exist_ok=True);target.write_bytes(data) if variant=='fixed-point': target=pipe_source_path(directory/'native');source=target.read_text() if source.count(NEW_CALL)!=1:raise ValueError('Baseline pipe flow layout does not match the audited fixed-point substitution') target.write_text(source.replace(NEW_CALL,OLD_ITERATION)) variants[variant]=build_snapshot(program,directory/'native',out/'cache') finally:builder.NATIVE=original_native rows=[] for index in range(args.runs+1): order=list(variants) if index%2:order.reverse() for variant in order: data=execute_native(variants[variant],config,doc.simulation.sample_step, run_dir=out/variant/('warmup' if index==0 else f'run-{index}'),timeout=120) row={key:value for key,value in data.items() if key not in ('series','final','finalState')} row.update(variant=variant,run=index) rows.append(row);print(json.dumps(row,ensure_ascii=False),flush=True) if not data['success']:raise RuntimeError(f'{variant} did not complete: {data["message"]}') summary={'baselineRef':revision,'input':str(args.input.resolve()), 'inputSha256':sha256(args.input.read_bytes()).hexdigest(),'xmlSha256':sha256(xml).hexdigest(), 'settings':vars(config),'sampleStep':doc.simulation.sample_step,'rows':rows, 'variants':{name:{'sourceSha256':sha256(pipe_source_path(out/name/'native').read_bytes()).hexdigest(), 'pipeSource':str(pipe_source_path(out/name/'native').relative_to(out/name)), 'nativeSourceHashes':build.manifest['sourceHashes'], 'buildKey':build.manifest['buildKey'], 'medianSolveSeconds':statistics.median(row['solveSeconds'] for row in rows if row['variant']==name and row['run']>0), 'medianProcessSeconds':statistics.median(row['processWallSeconds'] for row in rows if row['variant']==name and row['run']>0)} for name,build in variants.items()}} (out/'summary.json').write_text(json.dumps(summary,ensure_ascii=False,indent=2)+'\n') print(json.dumps(summary['variants'],ensure_ascii=False,indent=2)) if __name__=='__main__':main()