"""Own SVG figures from saved data and actual conditional analysis; real plots explicitly local.""" import argparse,html,json,math,time from pathlib import Path import numpy as np from analyze import load_saved from thin_disk_reference import speed_squared_dimensionless COLORS=['#6956a4','#167f96','#bd7132','#47774d'] def chart(path,title,x_label,y_label,series,width=820,height=360,points=None): left=65;right=20;top=104;bottom=52 all_x=[value for item in series for value in item['x']];all_y=[value for item in series for value in item['y']] if points: all_x+=points['x'];all_y += [value+error for value,error in zip(points['y'],points['error'])]+[value-error for value,error in zip(points['y'],points['error'])] xmin=min(all_x);xmax=max(all_x);ymin=min(0,min(all_y));ymax=max(all_y);ymax=ymin+(ymax-ymin)*1.12 def x(value):return left+(width-left-right)*(value-xmin)/(xmax-xmin) def y(value):return height-bottom-(height-top-bottom)*(value-ymin)/(ymax-ymin) title_left=20 if width<500 else left;title_size=14 if width<500 else 16 elements=[f'',f'{html.escape(title)}'] for index in range(5): vx=xmin+(xmax-xmin)*index/4;vy=ymin+(ymax-ymin)*index/4 elements += [f'',f'{vx:.3g}',f'{vy:.3g}'] for index,item in enumerate(series): color=COLORS[index%len(COLORS)];coords=' '.join(f'{x(a):.4f},{y(b):.4f}' for a,b in zip(item['x'],item['y'])) elements.append(f'') legend_y=49+(index//2)*18;legend_x=left+(index%2)*(width-left-right)/2;elements += [f'',f'{html.escape(item["label"])}'] if points: for a,b,error in zip(points['x'],points['y'],points['error']):elements += [f'',f''] elements += [f'{html.escape(x_label)}',f'{html.escape(y_label)}'] path.write_text(f''+''.join(elements)+'') def main(): parser=argparse.ArgumentParser();parser.add_argument('--input',type=Path,required=True);parser.add_argument('--analysis',type=Path,required=True);parser.add_argument('--output',type=Path,required=True);parser.add_argument('--allow-local-observations',action='store_true');args=parser.parse_args();started=time.monotonic() record=load_saved(args.input);real=record.get('galaxy')=='NGC2403' if real and not args.allow_local_observations:raise ValueError('Measured-derived plots require explicit reader-local acknowledgement') analysis=json.loads((args.analysis/'analysis.json').read_text());args.output.mkdir(parents=True,exist_ok=False) rows=np.asarray(record['rows']);best=analysis['fits']['nfw'];prediction=best['predictedRecord'];radius=prediction['radiusKpc'] curves=[{'label':'Total NFW model','x':radius,'y':prediction['circularVelocityKms']},{'label':'Stellar equivalent','x':radius,'y':np.sqrt(prediction['diskSignedSpeedSquared']).tolist()},{'label':'Gas equivalent','x':radius,'y':np.sign(prediction['gasSignedSpeedSquared'])*np.sqrt(np.abs(prediction['gasSignedSpeedSquared']))},{'label':'Halo equivalent','x':radius,'y':np.sqrt(prediction['haloSpeedSquared']).tolist()}] profile=[] for kind in ['nfw','core']: values=analysis['profileMassToLight'][kind];minimum=analysis['fits'][kind]['logWorkingGaussianLikelihood'];profile.append({'label':kind.upper()+' profile','x':[item['massToLight'] for item in values],'y':[-2*(item['logWorkingGaussianLikelihood']-minimum) for item in values]}) dimensionless=np.linspace(.1,8,120);disk=[speed_squared_dimensionless(value) for value in dimensionless];spherical=[(1-(1+value)*math.exp(-value))/value for value in dimensionless] for suffix,width,height in [('',820,360),('-mobile',360,330)]: chart(args.output/('curve'+suffix+'.svg'),'LOCAL measured source' if real else 'Own synthetic observations and model','Radius [kpc]','Speed [km/s]',curves,width,height,{'x':rows[:,0].tolist(),'y':rows[:,1].tolist(),'error':rows[:,2].tolist()}) chart(args.output/('profile'+suffix+'.svg'),'Conditional mass-to-light profiles','Stellar mass-to-light ratio','Twice logL difference',profile,width,height) chart(args.output/('disk-sphere'+suffix+'.svg'),'One disk, two geometric calculations','Radius / disk scale','Speed squared / (GM / scale)',[{'label':'Thin disk','x':dimensionless,'y':disk},{'label':'Wrong sphere','x':dimensionless,'y':spherical}],width,height) result={'status':'PASS','privateMeasuredDerived':real,'sourceSha256':analysis['inputSha256'],'ownAnalyticGeometryFigure':True,'files':[str(path.name) for path in args.output.iterdir()],'wallSeconds':time.monotonic()-started,'scope':'Own synthetic or explicitly local measured saved-record figures, not a galaxy matter image or confidence region'} (args.output/'figure-receipt.json').write_text(json.dumps(result,indent=2)+'\n');print(json.dumps(result)) if __name__=='__main__':main()