"""Hypothetical calibration and transition readout models; no material data."""
import csv,json,math
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
P=Path(__file__).resolve().parent
sources=json.loads((P/'sources.json').read_text(encoding='utf8'))

def linear(t,u=.001):
 if not math.isfinite(t) or not 0<=t<=100 or not math.isfinite(u) or u<0:raise ValueError('Temperature in 0..100 C; nonnegative finite readout uncertainty')
 s=.2+.01*t
 return dict(temperature_C=t,signal_V=s,inferred_temperature_C=(s-.2)/.01,readout_standard_uncertainty_V=u,temperature_readout_standard_uncertainty_K=u/.01)

def inverse(s):
 if not math.isfinite(s) or not 0<s<1:raise ValueError('Normalized signal must be strictly between 0 and 1')
 return 4+.02*math.log(s/(1-s))

def transition(t):
 if not math.isfinite(t) or not 3.8<=t<=4.2:raise ValueError('Temperature in 3.8..4.2 K required')
 s=1/(1+math.exp(-(t-4)/.02));slope=s*(1-s)/.02
 return dict(temperature_K=t,normalized_signal=s,sensitivity_per_K=slope,inferred_temperature_K=inverse(s),readout_standard_uncertainty=.001,linearized_temperature_readout_standard_uncertainty_K=.001/slope if .05<=s<=.95 else None,uncertainty_view_valid=.05<=s<=.95)
lin=[linear(i/2) for i in range(201)];tr=[transition(3.8+i*.001) for i in range(401)]
for name,rows in [('calibration.csv',lin),('transition.csv',tr)]:
 with (P/name).open('w',newline='') as f:w=csv.DictWriter(f,fieldnames=rows[0]);w.writeheader();w.writerows(rows)
linerr=max(abs(r['inferred_temperature_C']-r['temperature_C']) for r in lin)
trerr=max(abs(r['inferred_temperature_K']-r['temperature_K']) for r in tr)
assert linerr<1e-12 and trerr<1e-11
assert all(a['signal_V']<b['signal_V'] for a,b in zip(lin,lin[1:]))
assert all(a['normalized_signal']<b['normalized_signal'] for a,b in zip(tr,tr[1:]))
# Independent centred finite difference of response, avoiding restricted domain endpoints.
def signal(t):return 1/(1+math.exp(-(t-4)/.02))
fdmax=0
for r in tr[1:-1]:
 h=1e-6;fd=(signal(r['temperature_K']+h)-signal(r['temperature_K']-h))/(2*h)
 fdmax=max(fdmax,abs(fd-r['sensitivity_per_K'])/r['sensitivity_per_K'])
assert fdmax<1e-6
assert transition(4)['normalized_signal']==.5 and transition(4)['sensitivity_per_K']==12.5
assert transition(3.8)['linearized_temperature_readout_standard_uncertainty_K'] is None
assert transition(4.2)['linearized_temperature_readout_standard_uncertainty_K'] is None
bad=0
for s in [0,1,-.1,1.1,math.nan,math.inf]:
 try:inverse(s)
 except ValueError:bad+=1
assert bad==6
# Compare linearized uncertainty against symmetric finite signal displacements within plotting range.
localmax=0
for r in tr:
 if r['uncertainty_view_valid']:
  s=r['normalized_signal'];u=.001;halfspan=(inverse(s+u)-inverse(s-u))/2
  localmax=max(localmax,abs(halfspan/r['linearized_temperature_readout_standard_uncertainty_K']-1))
assert localmax<.001
plt.rcParams.update({'font.family':'DejaVu Sans','font.size':10,'axes.spines.top':False,'axes.spines.right':False,'svg.fonttype':'none','pdf.fonttype':42})
fig,ax=plt.subplots(1,3,figsize=(13,4.5))
x=[r['temperature_C'] for r in lin];y=[r['signal_V'] for r in lin]
ax[0].plot(x,y,color='#078390',lw=2);ax[0].set(xlabel='Temperature (C)',ylabel='Readout (V)',title='A  |  A calibrated linear response')
ax[0].text(.06,.9,'S = 0.2 V + (0.01 V/K) T\nAssigned readout uncertainty: 1 mV\nTemperature contribution: 0.1 K',transform=ax[0].transAxes,va='top',fontsize=8)
x=[r['temperature_K'] for r in tr];s=[r['normalized_signal'] for r in tr]
ax[1].plot(x,s,color='#7250a1',lw=2);ax[1].axvline(4,color='#777',ls=':',lw=1);ax[1].set(xlabel='Temperature (K)',ylabel='Normalized readout',title='B  |  A narrow response region')
valid=[r for r in tr if r['uncertainty_view_valid']]
ax[1].axhspan(.05,.95,color='#7250a1',alpha=.08)
ax[2].plot([r['normalized_signal'] for r in valid],[r['linearized_temperature_readout_standard_uncertainty_K']*1000 for r in valid],color='#ac447e',lw=2)
ax[2].set(xlabel='Normalized readout (restricted range)',ylabel='Local temperature uncertainty (mK)',title='C  |  Readout noise divided by slope',xlim=(.05,.95))
for a in ax:a.grid(alpha=.2)
fig.suptitle('Reading temperature requires a signal and a calibrated relation',fontsize=15)
fig.text(.5,.05,'All responses and uncertainties are hypothetical. Transition midpoint 4 K, width parameter 0.02 K; no named material is represented.',ha='center',fontsize=9)
fig.text(.5,.014,'Panel C: first-order readout contribution only, standard uncertainty 0.001; reference, drift, field, bias and calibration errors excluded.',ha='center',fontsize=9)
fig.tight_layout(rect=[0,.09,1,.93])
for ext in ['png','svg','pdf']:fig.savefig(P/f'temperature-readout.{ext}',dpi=190)
meta={'title':'Hypothetical calibration and bounded transition readout','status':'Analytical teaching models; no measured thermometer or material','rows':{'calibration':201,'transition':401},'parameters':{'linear_intercept_V':.2,'linear_slope_V_per_K':.01,'linear_temperature_C_range':[0,100],'linear_readout_standard_uncertainty_V':.001,'transition_midpoint_K':4,'transition_width_parameter_K':.02,'transition_temperature_K_range':[3.8,4.2],'transition_normalized_readout_standard_uncertainty':.001,'uncertainty_signal_domain':[.05,.95]},'equations':{'linear':'S=a+b*T_C; inferred T_C=(S-a)/b','linear_readout_uncertainty':'uT=uS/abs(b), a and b treated exact','transition':'s=1/(1+exp(-(T_K-T0)/w))','inverse':'T_K=T0+w*ln(s/(1-s)), strictly 0<s<1','sensitivity':'ds/dT=s*(1-s)/w','local_uncertainty':'uT=us/(ds/dT), shown only for 0.05<=s<=0.95'},'checks':{'all_pass':True,'linear_inverse_max_error_K':linerr,'transition_inverse_max_error_K':trerr,'derivative_finite_difference_max_relative_error':fdmax,'linearization_symmetric_span_max_relative_difference':localmax,'invalid_inverse_inputs_rejected':bad,'outside_uncertainty_view_returns_null':True},'limitations':['Assigned mathematical responses, not properties of a named crystal or superconducting specimen.','The logistic response is phenomenological and has no exact zero-resistance plateau. It is not a microscopic superconductivity model.','Only readout standard uncertainty is propagated; calibration coefficients and midpoint/width are treated exact. No total accuracy claim follows.','The local uncertainty approximation is deliberately restricted away from saturation; no finite inverse exists at normalized signal 0 or 1.','No thermal lag, contact error, self-heating, magnetic-field dependence, hysteresis or electrical feedback is modeled.','A binary threshold indicates a side of a transition; an analog calibrated transition response can resolve temperature locally.','Linear example uses Celsius values relative to its chosen zero; temperature differences in C and K have equal numerical size.'],'sources':sources}
(P/'study.json').write_text(json.dumps(meta,indent=2)+'\n');print(json.dumps(meta['checks']))
