"""Bounded two-straightedge inversion using recovered source chart coordinates.
Coordinates are graph geometry only; tick values retain their printed physical units.
"""
from pathlib import Path
import json,math
P=Path(__file__).resolve().parent
CHARTS={
'pump':{'units':{'expansion':'in','limit':'lbf','diameter':'nominal in, standard weight only','inertia':'in^4','length':'ft'},'x':{'expansion':590,'pivot':1153,'limit':1720,'length':1958,'section':2633},'ticks':{'expansion':[[.2,1618],[.3,1480],[.4,1386],[.5,1313],[.6,1252],[.7,1200],[.8,1158],[.9,1118],[1.,1083],[1.5,948],[2.,853]],'limit':[[1000,909],[1200,979],[1500,1044],[2000,1140],[2500,1212],[3000,1285]],'length':[[10,1345],[15,1255],[20,1191],[25,1141],[30,1100],[40,1037],[50,986]],'diameter':[[10,1228],[12,1155],[14,1114],[16,1060],[18,1011],[20,968],[24,890]],'inertia':[[200,1197],[300,1144],[500,1077],[700,1028],[1000,983],[2000,890]]},'source_domain':'expansion .2..2in,force1000..3000lbf; length10..50ft; standard weight nominal10..24in or inertia200..2000in^4. No extrapolation.'},
'vessel':{'units':{'expansion':'in','limit':'psi','diameter':'nominal in','length':'ft'},'x':{'expansion':770,'pivot':1134,'length':1623,'limit':2198,'section':2648},'ticks':{'expansion':[[.1,1824],[.2,1668],[.3,1578],[.4,1512],[.5,1464],[.7,1389],[1.,1311],[1.5,1219],[2.,1157]],'limit':[[10000,588],[12000,709],[14000,814],[15000,850],[16000,903],[18000,978],[20000,1050]],'length':[[10,1407],[20,1252],[30,1162],[40,1099],[50,1047],[70,974]],'diameter':[[12,1187],[14,1145],[16,1102],[18,1064],[20,1030],[24,977],[30,904],[36,845]]},'source_domain':'expansion .1..2in,stress10000..20000psi,nominal12..36in; length10..70ft. No extrapolation.'}}
def ordinate(ticks,value):
 for (v1,y1),(v2,y2) in zip(ticks,ticks[1:]):
  if v1<=value<=v2:return y1+(y2-y1)*math.log(value/v1)/math.log(v2/v1)
 raise ValueError('Input outside recovered scale')
def value_at(ticks,y):
 for (v1,y1),(v2,y2) in zip(ticks,ticks[1:]):
  if min(y1,y2)<=y<=max(y1,y2):return v1*(v2/v1)**((y-y1)/(y2-y1))
 raise ValueError('Output outside recovered scale')
def invert(chart,expansion,limit,section,section_basis='diameter'):
 c=CHARTS[chart];x=c['x'];ticks=c['ticks']
 allowed_sections={'pump':{'diameter','inertia'},'vessel':{'diameter'}}
 if section_basis not in allowed_sections[chart]:raise ValueError('Invalid section axis for this chart')
 ye=ordinate(ticks['expansion'],expansion);yf=ordinate(ticks['limit'],limit);ys=ordinate(ticks[section_basis],section)
 yp=ye+(yf-ye)*(x['pivot']-x['expansion'])/(x['limit']-x['expansion'])
 yl=yp+(ys-yp)*(x['length']-x['pivot'])/(x['section']-x['pivot'])
 return {'chart':chart,'inputs':{'expansion':expansion,'limit':limit,'section':section,'section_basis':section_basis},'chart_ordinates':{'expansion':ye,'limit':yf,'pivot':yp,'section':ys,'length':yl},'approximate_length_ft':value_at(ticks['length'],yl),'graphical_read_band_ft':[value_at(ticks['length'],yl+14),value_at(ticks['length'],yl-14)],'qualification':'approximate recovered-scale graphical result; ±14 ordinate-pixel envelope, not engineering confidence or source numerical formula'}
def main():
 (P/'c09-nomograph-data.json').write_text(json.dumps({'schema':'bounded-original-scale-nomograph/v1','charts':CHARTS,'coordinate_basis':'Pump chart coordinate frame3299x2550; vessel chart coordinate frame3314x2569. Labelled tick centres define graphical calibration. Piecewise interpolation logarithmic in value between labelled ticks; chart coordinates linear for straightedges.','limits':'Selected domains only; no exact underlying law fitted; no scale extrapolation; graph read band retained; source planning criteria not code/vendor allowables'},indent=2)+'\n')
 results=[invert('pump',.713,2000,12),invert('pump',.506,2000,12),invert('vessel',.179,14000,30),invert('vessel',.5,14000,30)]
 (P/'c09-nomograph-calculations.json').write_text(json.dumps({'cases':results,'source_examples':'Pump/vessel input pairs are historical exemplars; output is independently reconstructed graphical read, not recertified source23/21/18ft conclusions. Last vessel.5in is a declared hypothetical sensitivity case.'},indent=2)+'\n')
 print(json.dumps(results,indent=2))
if __name__=='__main__':main()
